Files
stock-analysis/internal/strategy/premium.go
WarrenZhou e28bfec957 Initial commit: 可转债轮动分析系统
- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换)
- 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制)
- 增量同步:支持 -incr/-since 参数按存档日期增量入库
- 策略回测:净值曲线/10指标/基准对比,含基本面过滤
- 用户认证:用户名密码 + 邮箱验证码双登录方式
- 前端:三Tab SPA(实时行情/策略回测/数据管理)
2026-08-17 20:23:22 +08:00

79 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}