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

83 lines
2.3 KiB
Go

package backtest
import (
"context"
"stock-analysis/internal/model"
)
// FundamentalFilter 正股基本面过滤器,用于排除信用风险过高的转债。
// 过滤规则:
// - ST / *ST 正股对应的转债一律排除
// - 资产负债率超过阈值的转债排除
// - 净利润为负(连续亏损)的转债排除
type FundamentalFilter struct {
MaxDebtRatio float64 // 资产负债率上限(%),默认 70
stock2bond map[string]string
enabled bool
}
// NewFundamentalFilter 创建基本面过滤器。
// stock2bond 为正股代码到转债代码的映射(一只正股可能对应多只转债,取任意一只即可)。
func NewFundamentalFilter(stock2bond map[string]string) *FundamentalFilter {
return &FundamentalFilter{
MaxDebtRatio: 70,
stock2bond: stock2bond,
enabled: true,
}
}
// SetEnabled 启用/禁用过滤器。
func (f *FundamentalFilter) SetEnabled(v bool) { f.enabled = v }
// Apply 给定当日 universe 行情和基本面数据,返回过滤后的行情列表。
// fundamentals 的 key 为正股代码。
func (f *FundamentalFilter) Apply(ctx context.Context, universe []Quote, fundamentals map[string]*model.StockFundamental) []Quote {
if !f.enabled || len(fundamentals) == 0 {
return universe
}
// 构建转债代码到正股代码的映射(反向查找)。
bond2stock := make(map[string]string, len(f.stock2bond))
for stock, bond := range f.stock2bond {
bond2stock[bond] = stock
}
filtered := make([]Quote, 0, len(universe))
for _, q := range universe {
stockCode, ok := bond2stock[q.Code]
if !ok {
// 无正股映射,允许通过(不因数据缺失误杀)。
filtered = append(filtered, q)
continue
}
fund, ok := fundamentals[stockCode]
if !ok {
// 无基本面数据,允许通过。
filtered = append(filtered, q)
continue
}
if f.isBlocked(fund) {
continue
}
filtered = append(filtered, q)
}
return filtered
}
// isBlocked 判断正股基本面是否触发过滤条件。
func (f *FundamentalFilter) isBlocked(fund *model.StockFundamental) bool {
// ST / *ST 一律排除。
if fund.Status >= 2 {
return true
}
// 资产负债率超过阈值。
if f.MaxDebtRatio > 0 && fund.DebtRatio > f.MaxDebtRatio {
return true
}
// 净利润为负(连续亏损)。
if fund.NetProfit < 0 {
return true
}
return false
}