package backtest // Account 模拟账户:现金 + 持仓,负责调仓成交与逐日市值结算。 // 可转债交易规则:面值 100 元/张,1 手 = 10 张,买入须为整手; // T+0、无印花税,仅计单边佣金与可选滑点。 type Account struct { initialCash float64 cash float64 holdings map[string]float64 // code → 持有张数 commission float64 slippage float64 curve []EquityPoint equity float64 // 最近一次结算的净值 } // NewAccount 创建初始账户。 func NewAccount(p Params) *Account { return &Account{ initialCash: p.InitialCash, cash: p.InitialCash, holdings: make(map[string]float64), commission: p.Commission, slippage: p.Slippage, equity: p.InitialCash, // 首个交易日即可建仓 } } // priceFn 返回标的指定日期(或最近)价格。封装 History.CloseOn 签名。 type priceFn func(code, date string) (float64, bool) // Rebalance 按目标权重调仓:先卖后买,张数向下取整到整手(10 张)。 // 已退市/当日停牌标的按最后已知价结算。 func (a *Account) Rebalance(date string, targets []Target, price priceFn) { total := a.equity if total <= 0 { return } // 目标持仓权重表。 want := make(map[string]float64) for _, t := range targets { if t.Weight > 0 { want[t.Code] = t.Weight } } // 先卖出:超出目标权重的部分。 for code, qty := range a.holdings { p, ok := price(code, date) if !ok || p <= 0 { continue // 无价格信息,暂不处理该持仓 } targetQty := lots(total*want[code]/(p*10)) * 10 if targetQty < qty { sell := qty - targetQty proceeds := sell * p * (1 - a.slippage) a.cash += proceeds - proceeds*a.commission if targetQty == 0 { delete(a.holdings, code) } else { a.holdings[code] = targetQty } } } // 再买入:未达目标权重的部分。 for code, weight := range want { p, ok := price(code, date) if !ok || p <= 0 { continue } targetQty := lots(total*weight/(p*10)) * 10 cur := a.holdings[code] if targetQty <= cur { continue } buy := targetQty - cur if avail := a.buyableLots(p); avail < buy/10 { buy = avail * 10 } if buy <= 0 { continue } cost := buy * p * (1 + a.slippage) a.cash -= cost + cost*a.commission a.holdings[code] = cur + buy } } // buyableLots 返回当前现金可买的手数(整手向下取整)。 func (a *Account) buyableLots(price float64) float64 { unitCost := price * 10 * (1 + a.slippage) * (1 + a.commission) if unitCost <= 0 { return 0 } return lots(a.cash / unitCost) } // lots 向下取整到整数手。 func lots(v float64) float64 { return float64(int(v)) } // MarkToMarket 按收盘价结算当日净值并追加到净值曲线。 func (a *Account) MarkToMarket(date string, price priceFn) { value := a.cash for code, qty := range a.holdings { if p, ok := price(code, date); ok { value += qty * p } } a.equity = value init := a.initialCash if init <= 0 { init = 1 } a.curve = append(a.curve, EquityPoint{ Date: date, Value: value, Return: value/init - 1, }) } // Equity 返回最近一次结算的净值。 func (a *Account) Equity() float64 { return a.equity } // Curve 返回累计净值曲线。 func (a *Account) Curve() []EquityPoint { return a.curve }