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

88 lines
2.8 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 提供内置回测策略(MVC Service 层)。
// 用户可在 backtest.Strategy 接口基础上扩展自定义轮动/网格等策略。
package strategy
import (
"context"
"fmt"
"sort"
"stock-analysis/internal/backtest"
)
// DoubleLowParams 双低轮动策略参数。
type DoubleLowParams struct {
TopN int // 持仓数量
Rebalance int // 调仓周期(交易日)
MinPrice float64 // 价格下限(过滤异常低价/退市风险债)
MaxPrice float64 // 价格上限
MaxPremium float64 // 溢价率上限(过滤高溢价债,0 表示不限制)
}
// DefaultDoubleLowParams 返回推荐默认参数。
func DefaultDoubleLowParams() DoubleLowParams {
return DoubleLowParams{TopN: 10, Rebalance: 21, MinPrice: 90, MaxPrice: 130, MaxPremium: 30}
}
// DoubleLow 双低轮动策略:每个调仓日选择"双低值 = 转债价 + 转股溢价率(%)"
// 最低的 TopN 只转债等权持有。该策略兼顾"低价"的债性保护与"低溢价"的股性弹性,
// 是经典的稳健型可转债轮动框架。
type DoubleLow struct {
params DoubleLowParams
day int // 已执行的交易日计数
}
// NewDoubleLow 创建双低轮动策略。
func NewDoubleLow(p DoubleLowParams) *DoubleLow {
return &DoubleLow{params: p}
}
// Name 返回策略名称。
func (s *DoubleLow) Name() string {
return fmt.Sprintf("双低轮动(top%d,每%d交易日)", s.params.TopN, s.params.Rebalance)
}
// OnBar 实现 backtest.Strategy:交易日递增,到调仓日时按双低值选股返回目标持仓。
func (s *DoubleLow) 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 // 非调仓日
}
type cand struct {
code string
doubleLow float64
}
candidates := make([]cand, 0, len(universe))
for _, q := range universe {
if q.Price < s.params.MinPrice || q.Price > s.params.MaxPrice {
continue
}
// 溢价率缺失(未入库)或非正时为 0/负值,不参与双低排序。
if q.PremiumRate <= 0 {
continue
}
if s.params.MaxPremium > 0 && q.PremiumRate > s.params.MaxPremium {
continue
}
candidates = append(candidates, cand{
code: q.Code,
doubleLow: q.Price + q.PremiumRate,
})
}
sort.Slice(candidates, func(i, j int) bool { return candidates[i].doubleLow < candidates[j].doubleLow })
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 _, c := range candidates {
targets = append(targets, backtest.Target{Code: c.code, Weight: weight})
}
return targets, nil
}