package strategy import ( "context" "fmt" "sort" "stock-analysis/internal/backtest" ) // PremiumParams 溢价率轮动策略参数。 type PremiumParams struct { TopN int // 持仓数量 Rebalance int // 调仓周期(交易日) MinPrice float64 // 价格下限 MaxPrice float64 // 价格上限 } // DefaultPremiumParams 返回推荐默认参数(价格放宽到 90-150)。 func DefaultPremiumParams() PremiumParams { return PremiumParams{TopN: 10, Rebalance: 21, MinPrice: 90, MaxPrice: 150} } // Premium 溢价率轮动策略:每 Rebalance 个交易日,选择转股溢价率最低的 TopN // 只转债等权持有。溢价率低意味着转债价格与正股联动紧密,正股涨则转债跟涨, // 适合正股上涨行情,避免"正股涨转债不涨"的尴尬。 type Premium struct { params PremiumParams day int } // NewPremium 创建溢价率轮动策略。 func NewPremium(p PremiumParams) *Premium { return &Premium{params: p} } // Name 返回策略名称。 func (s *Premium) Name() string { return fmt.Sprintf("溢价率轮动(top%d,每%d交易日)", s.params.TopN, s.params.Rebalance) } // OnBar 实现 backtest.Strategy:到调仓日时按溢价率升序选股。 // 注意:东财历史K线API不返回转股溢价率,当前数据库中的 PremiumRate 均为 0。 // 因此策略暂以"价格"作为溢价率的代理排序指标(低价债通常溢价率更低), // 待后续接入正股K线数据后将改为按实际溢价率排序。 func (s *Premium) OnBar(ctx context.Context, date string, universe []backtest.Quote, hist *backtest.History) ([]backtest.Target, error) { s.day++ if s.day > 1 && (s.day-1)%s.params.Rebalance != 0 { return nil, nil } candidates := make([]backtest.Quote, 0, len(universe)) for _, q := range universe { if q.Price >= s.params.MinPrice && q.Price <= s.params.MaxPrice { candidates = append(candidates, q) } } // 按溢价率排序:有实际溢价率数据时优先使用,否则回退到价格排序。 sort.Slice(candidates, func(i, j int) bool { pi, pj := candidates[i].PremiumRate, candidates[j].PremiumRate if pi > 0 && pj > 0 { return pi < pj } return candidates[i].Price < candidates[j].Price }) if len(candidates) > s.params.TopN { candidates = candidates[:s.params.TopN] } if len(candidates) == 0 { return nil, nil } weight := 1.0 / float64(len(candidates)) targets := make([]backtest.Target, 0, len(candidates)) for _, q := range candidates { targets = append(targets, backtest.Target{Code: q.Code, Weight: weight}) } return targets, nil }