feat: 自动候选筛选与股票研究工作台
This commit is contained in:
+17
-16
@@ -45,22 +45,23 @@ type CandidateContent struct {
|
||||
}
|
||||
|
||||
type CandidateRevision struct {
|
||||
ID string `db:"id" json:"id"`
|
||||
CandidateID string `db:"candidate_id" json:"candidate_id"`
|
||||
Revision int `db:"revision" json:"revision"`
|
||||
Version int64 `db:"version" json:"version"`
|
||||
Status string `db:"status" json:"status"`
|
||||
DataMode string `db:"data_mode" json:"data_mode"`
|
||||
Snapshot CandidateContent `db:"snapshot" json:"content"`
|
||||
AuthorID string `db:"author_id" json:"author_id"`
|
||||
ObservedAt time.Time `db:"observed_at" json:"observed_at"`
|
||||
ValidUntil time.Time `db:"valid_until" json:"valid_until"`
|
||||
PublishedAt *time.Time `db:"published_at" json:"published_at"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
IsLatest bool `json:"is_latest"`
|
||||
WithdrawalReason string `json:"withdrawal_reason"`
|
||||
IsActivePublication bool `json:"is_active_publication"`
|
||||
Screening *StrategyScreening `json:"screening,omitempty"`
|
||||
ID string `db:"id" json:"id"`
|
||||
CandidateID string `db:"candidate_id" json:"candidate_id"`
|
||||
Revision int `db:"revision" json:"revision"`
|
||||
Version int64 `db:"version" json:"version"`
|
||||
Status string `db:"status" json:"status"`
|
||||
DataMode string `db:"data_mode" json:"data_mode"`
|
||||
Snapshot CandidateContent `db:"snapshot" json:"content"`
|
||||
AuthorID string `db:"author_id" json:"author_id"`
|
||||
ObservedAt time.Time `db:"observed_at" json:"observed_at"`
|
||||
ValidUntil time.Time `db:"valid_until" json:"valid_until"`
|
||||
PublishedAt *time.Time `db:"published_at" json:"published_at"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
IsLatest bool `json:"is_latest"`
|
||||
WithdrawalReason string `json:"withdrawal_reason"`
|
||||
IsActivePublication bool `json:"is_active_publication"`
|
||||
}
|
||||
|
||||
func (CandidateRevision) TableName() string { return "veyra_candidate_revision" }
|
||||
|
||||
@@ -16,16 +16,18 @@ type Notification struct {
|
||||
func (Notification) TableName() string { return "veyra_notification" }
|
||||
|
||||
type QuoteSnapshot struct {
|
||||
ID string `db:"id" json:"id"`
|
||||
Symbol string `db:"symbol" json:"symbol"`
|
||||
Exchange string `db:"exchange" json:"exchange"`
|
||||
DataMode string `db:"data_mode" json:"data_mode"`
|
||||
PriceMinor int64 `db:"price_minor" json:"-"`
|
||||
Price string `json:"price"`
|
||||
ObservedAt time.Time `db:"observed_at" json:"observed_at"`
|
||||
Version int64 `db:"version" json:"version"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
SourceID *string `db:"source_id" json:"source_id,omitempty"`
|
||||
SourceVersion *int64 `db:"source_version" json:"source_version,omitempty"`
|
||||
ID string `db:"id" json:"id"`
|
||||
Symbol string `db:"symbol" json:"symbol"`
|
||||
Exchange string `db:"exchange" json:"exchange"`
|
||||
DataMode string `db:"data_mode" json:"data_mode"`
|
||||
PriceMinor int64 `db:"price_minor" json:"-"`
|
||||
Price string `json:"price"`
|
||||
ObservedAt time.Time `db:"observed_at" json:"observed_at"`
|
||||
Version int64 `db:"version" json:"version"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (QuoteSnapshot) TableName() string { return "veyra_quote_snapshot" }
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -3,11 +3,12 @@ package model
|
||||
import "time"
|
||||
|
||||
type StrategyParameters struct {
|
||||
ShortWindow int `json:"short_window"`
|
||||
LongWindow int `json:"long_window"`
|
||||
FeeBPS int `json:"fee_bps"`
|
||||
SlippageBPS int `json:"slippage_bps"`
|
||||
LotSize int64 `json:"lot_size"`
|
||||
Screening *ScreeningParameters `json:"screening,omitempty"`
|
||||
ShortWindow int `json:"short_window"`
|
||||
LongWindow int `json:"long_window"`
|
||||
FeeBPS int `json:"fee_bps"`
|
||||
SlippageBPS int `json:"slippage_bps"`
|
||||
LotSize int64 `json:"lot_size"`
|
||||
}
|
||||
type Strategy struct {
|
||||
ID string `db:"id" json:"id"`
|
||||
|
||||
Reference in New Issue
Block a user