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

187 lines
4.6 KiB
Go
Raw Permalink 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 backtest
import (
"context"
"fmt"
"sort"
"stock-analysis/internal/model"
"stock-analysis/internal/storage"
)
// Params 回测运行参数。
type Params struct {
InitialCash float64 // 初始资金(元)
Commission float64 // 单边佣金比例(含佣金,默认 0.00025)
Slippage float64 // 滑点比例(默认 0)
StartDate string // 回测起始日(含),空则使用数据首日
EndDate string // 回测结束日(含),空则使用数据末日
}
// DefaultParams 返回推荐默认参数。
func DefaultParams() Params {
return Params{
InitialCash: 100000,
Commission: 0.00025,
}
}
// DataFeed 从存储加载全量K线,构建按日期索引的行情数据。
type DataFeed struct {
quotes map[string][]Quote // date → 当日全部转债行情
dates []string // 升序交易日列表
hist *History
}
// NewDataFeed 从存储加载全部转债K线并构建回测数据源。
func NewDataFeed(ctx context.Context, store storage.KlineStore) (*DataFeed, error) {
all, err := store.LoadAllKlines(ctx)
if err != nil {
return nil, fmt.Errorf("load klines: %w", err)
}
quotes := make(map[string][]Quote)
for code, ks := range all {
for _, k := range ks {
quotes[k.Date] = append(quotes[k.Date], Quote{
Code: code,
Price: k.Close,
Open: k.Open,
High: k.High,
Low: k.Low,
Volume: k.Volume,
ConvertValue: k.ConvertValue,
PremiumRate: k.PremiumRate,
})
}
}
dates := make([]string, 0, len(quotes))
for d := range quotes {
dates = append(dates, d)
}
sort.Strings(dates)
return &DataFeed{quotes: quotes, dates: dates, hist: &History{klines: all}}, nil
}
// Dates 返回升序交易日列表。
func (d *DataFeed) Dates() []string {
return d.dates
}
// QuotesOn 返回指定交易日的全部转债行情。
func (d *DataFeed) QuotesOn(date string) []Quote {
return d.quotes[date]
}
// History 返回全量历史数据访问器。
func (d *DataFeed) History() *History {
return d.hist
}
// Filter 只保留指定代码的数据,用于按用户选择的转债子集回测。
func (d *DataFeed) Filter(codes []string) {
keep := make(map[string]bool, len(codes))
for _, c := range codes {
keep[c] = true
}
// 过滤 quotes。
for date, qs := range d.quotes {
var filtered []Quote
for _, q := range qs {
if keep[q.Code] {
filtered = append(filtered, q)
}
}
d.quotes[date] = filtered
}
// 过滤 history.klines。
filteredKlines := make(map[string][]model.Kline)
for code, ks := range d.hist.klines {
if keep[code] {
filteredKlines[code] = ks
}
}
d.hist.klines = filteredKlines
}
// Engine 事件驱动回测引擎。
type Engine struct {
params Params
strategy Strategy
account *Account
filter *FundamentalFilter
fundMap map[string]*model.StockFundamental
}
// NewEngine 创建回测引擎。
func NewEngine(params Params, strategy Strategy) *Engine {
return &Engine{params: params, strategy: strategy}
}
// SetFundamentalFilter 设置基本面过滤器(可选)。
// stock2bond 为正股代码到转债代码的映射;fundamentals 为正股代码到基本面数据的映射。
func (e *Engine) SetFundamentalFilter(filter *FundamentalFilter, fundamentals map[string]*model.StockFundamental) {
e.filter = filter
e.fundMap = fundamentals
}
// Run 执行回测,返回结果(含净值曲线与绩效指标)。
func (e *Engine) Run(ctx context.Context, feed *DataFeed) (*Result, error) {
e.account = NewAccount(e.params)
for _, date := range feed.dates {
if e.params.StartDate != "" && date < e.params.StartDate {
continue
}
if e.params.EndDate != "" && date > e.params.EndDate {
break
}
universe := feed.quotes[date]
if e.filter != nil {
universe = e.filter.Apply(ctx, universe, e.fundMap)
}
targets, err := e.strategy.OnBar(ctx, date, universe, feed.hist)
if err != nil {
return nil, fmt.Errorf("strategy %s on %s: %w", e.strategy.Name(), date, err)
}
if targets != nil {
e.account.Rebalance(date, targets, feed.hist.CloseOn)
}
e.account.MarkToMarket(date, feed.hist.CloseOn)
}
curve := e.account.Curve()
start, end := "", ""
if len(curve) > 0 {
start, end = curve[0].Date, curve[len(curve)-1].Date
}
return &Result{
Strategy: e.strategy.Name(),
Start: start,
End: end,
Metrics: ComputeMetrics(curve),
Curve: curve,
}, nil
}
// Result 回测输出。
type Result struct {
Strategy string
Start string
End string
Metrics Metrics
Curve []EquityPoint
}
// EquityPoint 净值曲线点。
type EquityPoint struct {
Date string
Value float64 // 组合总市值
Return float64 // 累计收益率
}