feat: 自动候选筛选与股票研究工作台

This commit is contained in:
2026-10-09 20:39:23 +08:00
parent 92329ddce2
commit 57882ff568
32 changed files with 1343 additions and 87 deletions
+79
View File
@@ -0,0 +1,79 @@
package model
import (
"encoding/json"
"time"
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
)
// Screening rules belong to an immutable strategy version, and are opt-in.
type ScreeningSecurity struct {
Symbol string `json:"symbol"`
Exchange string `json:"exchange"`
}
type ScreeningParameters struct {
Securities []ScreeningSecurity `json:"securities"`
EntryBandBPS int64 `json:"entry_band_bps"`
StopLossBPS int64 `json:"stop_loss_bps"`
TargetBPS int64 `json:"target_bps"`
ValidHours int `json:"valid_hours"`
}
func ValidateScreening(p *ScreeningParameters) error {
if p == nil {
return nil
}
if len(p.Securities) < 1 || len(p.Securities) > 100 || p.EntryBandBPS < 0 || p.EntryBandBPS > 1000 || p.StopLossBPS <= p.EntryBandBPS || p.StopLossBPS > 5000 || p.TargetBPS <= p.EntryBandBPS || p.TargetBPS > 10000 || p.ValidHours < 1 || p.ValidHours > 168 {
return apperror.ErrInvalidInput
}
seen := map[ScreeningSecurity]bool{}
for _, v := range p.Securities {
if !symbolPattern.MatchString(v.Symbol) || (v.Exchange != "SSE" && v.Exchange != "SZSE") || seen[v] {
return apperror.ErrInvalidInput
}
seen[v] = true
}
return nil
}
type ScreeningTarget struct {
StrategyID string
StrategyName string
Version StrategyVersion
Instrument Instrument
}
type StrategyScreening struct {
ID string `db:"id" json:"id"`
StrategyVersionID string `db:"strategy_version_id" json:"strategy_version_id"`
StrategyName string `json:"strategy_name"`
StrategyRevision int `json:"strategy_revision"`
Symbol string `db:"symbol" json:"symbol"`
Exchange string `db:"exchange" json:"exchange"`
BarDate string `db:"bar_date" json:"bar_date"`
SourceID string `db:"source_id" json:"source_id"`
SourceVersion int64 `db:"source_version" json:"source_version"`
SourceAdapter string `db:"source_adapter" json:"source_adapter"`
ObservedAt time.Time `db:"observed_at" json:"observed_at"`
ReceivedAt time.Time `db:"received_at" json:"received_at"`
InputHash string `db:"input_hash" json:"input_hash"`
Input json.RawMessage `db:"input" json:"input,omitempty"`
Status string `db:"status" json:"status"`
CandidateRevisionID *string `db:"candidate_revision_id" json:"candidate_revision_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func (StrategyScreening) TableName() string { return "veyra_strategy_screening" }
// Worker state is process-local; durable results remain in StrategyScreening.
type ScreeningWorkerState struct {
State string `json:"state"`
StartedAt *time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at"`
Targets int `json:"targets"`
Completed int `json:"completed"`
Generated int `json:"generated"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
Message string `json:"message"`
}