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"` }