新增日内方向分析功能:基于三层打分数据由 AI 批量生成下一个交易日方向判断
This commit is contained in:
@@ -144,6 +144,126 @@ type Candle struct {
|
||||
PreClose float64 `json:"pre_close"`
|
||||
}
|
||||
|
||||
// ── Daily Direction ────────────────────────────────────────────────
|
||||
|
||||
// DailyDirection 日内方向分析结果。
|
||||
type DailyDirection struct {
|
||||
ID string `json:"id"`
|
||||
Symbol string `json:"symbol"`
|
||||
TradeDate string `json:"trade_date"`
|
||||
TargetDate string `json:"target_date"`
|
||||
Direction string `json:"direction"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Support string `json:"support"` // JSONB → string
|
||||
Resistance string `json:"resistance"` // JSONB → string
|
||||
Reasoning string `json:"reasoning"`
|
||||
RiskNote string `json:"risk_note"`
|
||||
PromptSnapshot string `json:"prompt_snapshot,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// EnsureDailyDirectionTable 建 daily_direction 表(幂等)。
|
||||
func (s *FuturesStore) EnsureDailyDirectionTable() error {
|
||||
_, err := s.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS daily_direction (
|
||||
id UUID DEFAULT uuidv7() PRIMARY KEY,
|
||||
symbol TEXT NOT NULL,
|
||||
trade_date TEXT NOT NULL,
|
||||
target_date TEXT NOT NULL,
|
||||
direction TEXT NOT NULL,
|
||||
confidence REAL,
|
||||
support JSONB,
|
||||
resistance JSONB,
|
||||
reasoning TEXT,
|
||||
risk_note TEXT,
|
||||
prompt_snapshot TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (symbol, trade_date)
|
||||
)
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveDailyDirection 写入(upsert)一条方向分析。
|
||||
func (s *FuturesStore) SaveDailyDirection(dd *DailyDirection) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO daily_direction
|
||||
(symbol, trade_date, target_date, direction, confidence, support, resistance, reasoning, risk_note, prompt_snapshot)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
ON CONFLICT (symbol, trade_date) DO UPDATE SET
|
||||
target_date = EXCLUDED.target_date,
|
||||
direction = EXCLUDED.direction,
|
||||
confidence = EXCLUDED.confidence,
|
||||
support = EXCLUDED.support,
|
||||
resistance = EXCLUDED.resistance,
|
||||
reasoning = EXCLUDED.reasoning,
|
||||
risk_note = EXCLUDED.risk_note,
|
||||
prompt_snapshot = EXCLUDED.prompt_snapshot,
|
||||
created_at = CURRENT_TIMESTAMP
|
||||
`, dd.Symbol, dd.TradeDate, dd.TargetDate, dd.Direction, dd.Confidence,
|
||||
dd.Support, dd.Resistance, dd.Reasoning, dd.RiskNote, dd.PromptSnapshot)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListDailyDirections 查询方向分析列表。
|
||||
func (s *FuturesStore) ListDailyDirections(symbol, start, end string, limit int) ([]DailyDirection, error) {
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 50
|
||||
}
|
||||
q := `SELECT id, symbol, trade_date, target_date, direction, confidence,
|
||||
COALESCE(support::text, '[]'), COALESCE(resistance::text, '[]'),
|
||||
reasoning, risk_note, COALESCE(prompt_snapshot, ''),
|
||||
COALESCE(created_at::text, '')
|
||||
FROM daily_direction WHERE 1=1`
|
||||
args := []any{}
|
||||
n := 0
|
||||
next := func() string { n++; return fmt.Sprintf("$%d", n) }
|
||||
if symbol != "" {
|
||||
q += " AND symbol = " + next()
|
||||
args = append(args, symbol)
|
||||
}
|
||||
if start != "" {
|
||||
q += " AND trade_date >= " + next()
|
||||
args = append(args, start)
|
||||
}
|
||||
if end != "" {
|
||||
q += " AND trade_date <= " + next()
|
||||
args = append(args, end)
|
||||
}
|
||||
q += " ORDER BY trade_date DESC, symbol ASC LIMIT " + next()
|
||||
args = append(args, limit)
|
||||
|
||||
rows, err := s.db.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []DailyDirection{}
|
||||
for rows.Next() {
|
||||
var dd DailyDirection
|
||||
if err := rows.Scan(&dd.ID, &dd.Symbol, &dd.TradeDate, &dd.TargetDate,
|
||||
&dd.Direction, &dd.Confidence, &dd.Support, &dd.Resistance,
|
||||
&dd.Reasoning, &dd.RiskNote, &dd.PromptSnapshot, &dd.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, dd)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// GetActiveTsCode 通过 scores 表查找某品种在指定日期的活跃合约代码。
|
||||
func (s *FuturesStore) GetActiveTsCode(symbol, tradeDate string) (string, error) {
|
||||
var tsCode string
|
||||
err := s.db.QueryRow(
|
||||
`SELECT ts_code FROM scores WHERE trade_date = $1 AND ts_code LIKE $2 || '%' ORDER BY ts_code DESC LIMIT 1`,
|
||||
tradeDate, symbol,
|
||||
).Scan(&tsCode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no active contract for %s on %s: %w", symbol, tradeDate, err)
|
||||
}
|
||||
return tsCode, nil
|
||||
}
|
||||
|
||||
func (s *FuturesStore) ListCandles(tsCode, start, end string) ([]Candle, error) {
|
||||
if tsCode == "" {
|
||||
return nil, ErrMissingTsCode
|
||||
|
||||
Reference in New Issue
Block a user