python实现简易期货回测框架
·
博主用AI+手工修复,实现了一个基于目标持仓信号的期货交易回测框架,采用先进先出(FIFO)原则逐笔管理多空持仓明细,同时对代码生成结果逐一测试,修复完成后写个博客记录一下。
回测逻辑
1. 交易执行逻辑
依据前一交易日收盘生成的目标持仓信号,在次日开盘价执行交易,支持开仓、平仓、加仓、减仓及多空双向操作。
2. 持仓管理
利用栈结构存储每笔开仓的手数和价格,并按 FIFO 顺序(优先平掉最早开立的仓位)处理平仓。
3. 盈亏计算
- 平仓时按开盘价与持仓结算价之差计算已实现盈亏。
- 每日收盘后按结算价计算未实现浮动盈亏。
- 逐日累计总权益,并输出每日收益与累计收益。
- 费用处理:可分别设置开仓、平仓的固定手续费(按手数收取)。
4. 输出结果
- result_df:每日统计表,包含交易信号、持仓调整量、当日持仓、当日收益、累计收益等字段。
- trade_df:逐笔成交明细表,记录每笔交易的方向、手数、价格、手续费及备注(如开多、平空、减多等)。
完整代码
下面的代码可直接运行:
from copy import deepcopy
import pandas as pd
def futures_backtest_fifo(
trade_dataframe,
open_col,
settle_col,
signal_col,
multiplier=10000,
open_comm=0.0,
close_comm=0.0,
round_precision=5,
):
"""
基于目标持仓信号的期货回测框架(FIFO 持仓明细 + 交易明细)5
Parameters
----------
trade_dataframe : pd.DataFrame
必须包含日期索引或列、开盘价、结算价、目标持仓信号列。
open_col : str
用于执行交易的开盘价列名。
settle_col : str
用于逐日盯市的结算价列名。
signal_col : str
目标持仓列,当日收盘决定,次日开盘执行。
multiplier : float
合约乘数
open_comm : float, default 0.0
开仓每手固定手续费。
close_comm : float, default 0.0
平仓每手固定手续费。
round_precision: float, default 5.0
计算精度
Returns
-------
result_df : pd.DataFrame
每日统计表,包含列:日期、真实目标值、预测目标值、交易信号、
持仓调整量、当日持仓、当日收益、累计收益。
trade_df : pd.DataFrame
逐笔成交明细表,包含列:日期、方向、手数、价格、手续费、备注。
"""
trade_dataframe = deepcopy(trade_dataframe)
# 处理合约乘数
if open_col != settle_col:
trade_dataframe[open_col] = trade_dataframe[open_col] * multiplier
trade_dataframe[settle_col] = trade_dataframe[settle_col] * multiplier
# 持仓明细栈:存储 (手数, 开仓价),手数正为多,负为空
position_stack = []
total_position = 0
prev_equity = 0.0
prev_settle = None
result_rows = []
trades = [] # 记录每一笔成交
for i in range(len(trade_dataframe)):
row = trade_dataframe.iloc[i]
open_price = row[open_col]
settle_price = row[settle_col]
signal = row[signal_col]
trade_volume = 0
realized_pnl = 0.0
fee = 0.0 # 费用计算
# ---------- 执行前一日的目标持仓信号 ----------
if i > 0:
# prev_signal = df.iloc[i - 1][signal_col]
today_target_vol = trade_dataframe.iloc[i - 1][signal_col] # 今日的目标仓位,来源于昨日交易信号
adjust = today_target_vol - total_position
trade_volume = adjust
# 修改当日结算
for _i in range(len(position_stack)):
position_stack[_i] = (position_stack[_i][0], prev_settle)
if adjust != 0:
# 情况1:平掉全部持仓(目标与持仓反向或目标为0)
if total_position != 0 and today_target_vol * total_position <= 0:
while position_stack:
_trade_volume, _trade_cost = position_stack.pop(0)
if _trade_volume > 0:
pnl = round((open_price - prev_settle) * abs(_trade_volume), round_precision) # 今日浮盈浮亏
direction = "卖出"
note = "平多"
else:
pnl = round((prev_settle - open_price) * abs(_trade_volume), round_precision)
direction = "买入"
note = "平空"
realized_pnl += pnl
fee += abs(_trade_volume) * close_comm
total_position -= _trade_volume
# 记录平仓明细
trades.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"方向": direction,
"手数": abs(_trade_volume),
"价格": open_price,
"手续费": abs(_trade_volume) * close_comm,
"备注": note
})
# 此时 total_position 应为 0
# 如果目标不为 0,则开新仓至目标手数
if today_target_vol != 0:
open_qty = today_target_vol - total_position # 追加量
if open_qty > 0:
position_stack.append((open_qty, open_price))
direction = "买入"
note = "开多"
elif open_qty < 0:
position_stack.append((open_qty, open_price))
direction = "卖出"
note = "开空"
fee += abs(open_qty) * open_comm
total_position = deepcopy(today_target_vol)
# 记录开仓明细
trades.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"方向": direction,
"手数": abs(open_qty),
"价格": open_price,
"手续费": abs(open_qty) * open_comm,
"备注": note
})
# 情况2:同向调整(加仓或减仓),且目标不为 0
elif total_position != 0 and today_target_vol * total_position > 0:
diff = today_target_vol - total_position
if abs(today_target_vol) > abs(total_position):
# 加仓
add_qty = diff
position_stack.append((add_qty, open_price))
fee += abs(add_qty) * open_comm
total_position = today_target_vol
trades.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"方向": "买入" if add_qty > 0 else "卖出",
"手数": abs(add_qty),
"价格": open_price,
"手续费": abs(add_qty) * open_comm,
"备注": "加多" if add_qty > 0 else "加空"
})
elif abs(today_target_vol) < abs(total_position):
# 减仓:按 FIFO 平掉部分持仓
to_close = abs(diff)
remaining_to_close = to_close
while remaining_to_close > 0 and position_stack:
_trade_volume, _trade_cost = position_stack[0]
abs_lot = abs(_trade_volume)
if abs_lot <= remaining_to_close:
position_stack.pop(0)
if _trade_volume > 0:
pnl = (open_price - prev_settle) * abs_lot
direction = "卖出"
note = "减多"
else:
pnl = (prev_settle - open_price) * abs_lot
direction = "买入"
note = "减空"
realized_pnl += pnl
fee += abs_lot * close_comm
remaining_to_close -= abs_lot
total_position -= _trade_volume
trades.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"方向": direction,
"手数": abs_lot,
"价格": open_price,
"手续费": abs_lot * close_comm,
"备注": note
})
else:
if _trade_volume > 0:
close_qty = remaining_to_close
pnl = (open_price - prev_settle) * close_qty
position_stack[0] = (_trade_volume - close_qty, _trade_cost)
direction = "卖出"
note = "减多"
else:
close_qty = -remaining_to_close
pnl = (prev_settle - open_price) * remaining_to_close
position_stack[0] = (_trade_volume - close_qty, _trade_cost)
direction = "买入"
note = "减空"
realized_pnl += pnl
fee += remaining_to_close * close_comm
total_position -= close_qty
trades.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"方向": direction,
"手数": remaining_to_close,
"价格": open_price,
"手续费": remaining_to_close * close_comm,
"备注": note
})
remaining_to_close = 0
# ---------- 计算当日浮动盈亏与权益 ----------
if total_position != 0:
# 以当日结算价统计全部的持仓标的
total_cost = sum(lot * cost for lot, cost in position_stack)
float_pnl = round((settle_price * total_position) - total_cost, round_precision) # 今日盈亏
else:
float_pnl = 0.0
# cumulative_pnl = prev_equity + realized_pnl - fee + (float_pnl - prev_float_pnl)
cumulative_pnl = round(prev_equity + realized_pnl - fee + float_pnl, round_precision)
# daily_pnl = equity - prev_equity
# ---------- 记录每日统计 ----------
result_rows.append({
"日期": row.name if trade_dataframe.index.name is not None else i,
"交易信号": signal,
"持仓调整量": trade_volume,
"当日持仓": total_position,
"当日收益": deepcopy(round(cumulative_pnl - prev_equity, round_precision)),
"累计收益": deepcopy(round(cumulative_pnl, round_precision)),
})
# ---------- 更新状态 ----------
# prev_float_pnl = deepcopy(float_pnl)
prev_settle = deepcopy(settle_price)
prev_equity = deepcopy(cumulative_pnl)
# 构造 DataFrame
result_df = pd.DataFrame(result_rows)
trade_df = pd.DataFrame(trades)
return result_df, trade_df
def main():
"""回测逻辑
当天出信号,第二天交易,target_signal表示
"""
df = pd.DataFrame({
"date": pd.date_range("2022-01-01", freq="D", periods=11),
"open": [100.59, 100.48, 99.67, 99.86, 98.99, 99.51, 99.31, 100.17, 98.79, 99.03, 100.83],
"settle": [100.3, 100.95, 100.04, 100.58, 100.5, 100.81, 100.3, 100.88, 100.13, 100.58, 100.25],
"target_signal": [1, 2, 2, 2, 2, 0, -2, -1, 0, 2, 1],
})
df.set_index("date", inplace=True)
df.index = pd.to_datetime(df.index)
result, trade_df = futures_backtest_fifo(
df, open_col="open", settle_col="settle", signal_col="target_signal", open_comm=2, close_comm=2, multiplier=10000)
print(result)
if __name__ == '__main__':
main()
更多推荐


所有评论(0)