同步本地回测与tqsdk回测

This commit is contained in:
2025-06-29 12:03:43 +08:00
parent 4521939b95
commit 70c3b8186a
20 changed files with 29892 additions and 23140 deletions

View File

@@ -9,7 +9,7 @@ from ..core_data import PortfolioSnapshot, Trade, Bar
def calculate_metrics(
snapshots: List[PortfolioSnapshot], trades: List[Trade], initial_capital: float
snapshots: List[PortfolioSnapshot], trades: List[Trade], initial_capital: float
) -> Dict[str, Any]:
"""
纯函数:根据投资组合快照和交易历史计算关键绩效指标。
@@ -124,27 +124,30 @@ def calculate_metrics(
"亏损交易次数": losing_count,
"平均每次盈利": avg_profit_per_trade,
"平均每次亏损": avg_loss_per_trade, # 这个值是负数
"InitialCapital": initial_capital,
"FinalCapital": final_value,
"TotalReturn": total_return,
"AnnualizedReturn": annualized_return,
"MaxDrawdown": max_drawdown,
"SharpeRatio": sharpe_ratio,
"CalmarRatio": calmar_ratio,
"TotalTrades": len(trades), # All buy and sell trades
"TransactionCosts": total_commissions,
"TotalRealizedPNL": total_realized_pnl, # New
"WinRate": win_rate,
"ProfitLossRatio": profit_loss_ratio,
"WinningTradesCount": winning_count,
"LosingTradesCount": losing_count,
"AvgProfitPerTrade": avg_profit_per_trade,
"AvgLossPerTrade": avg_loss_per_trade, # This value is negative
"initial_capital": initial_capital,
"final_capital": final_value,
"total_return": total_return,
"annualized_return": annualized_return,
"max_drawdown": max_drawdown,
"sharpe_ratio": sharpe_ratio,
"calmar_ratio": calmar_ratio,
"total_trades": len(trades), # All buy and sell trades
"transaction_costs": total_commissions,
"total_realized_pnl": total_realized_pnl, # New
"win_rate": win_rate,
"profit_loss_ratio": profit_loss_ratio,
"winning_trades_count": winning_count,
"losing_trades_count": losing_count,
"avg_profit_per_trade": avg_profit_per_trade,
"avg_loss_per_trade": avg_loss_per_trade, # This value is negative
}
def plot_equity_and_drawdown_chart(snapshots: List[PortfolioSnapshot], initial_capital: float,
title: str = "Portfolio Equity and Drawdown Curve") -> None:
def plot_equity_and_drawdown_chart(
snapshots: List[PortfolioSnapshot],
initial_capital: float,
title: str = "Portfolio Equity and Drawdown Curve",
) -> None:
"""
Plots the portfolio equity curve and drawdown. X-axis points are equally spaced.
@@ -157,35 +160,45 @@ def plot_equity_and_drawdown_chart(snapshots: List[PortfolioSnapshot], initial_c
print("No portfolio snapshots available to plot equity and drawdown.")
return
df_equity = pd.DataFrame([
{'datetime': s.datetime, 'total_value': s.total_value}
for s in snapshots
])
df_equity = pd.DataFrame(
[{"datetime": s.datetime, "total_value": s.total_value} for s in snapshots]
)
equity_curve = df_equity['total_value'] / initial_capital
equity_curve = df_equity["total_value"] / initial_capital
rolling_max = equity_curve.cummax()
drawdown = (rolling_max - equity_curve) / rolling_max
plt.style.use('seaborn-v0_8-darkgrid')
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True, gridspec_kw={'height_ratios': [3, 1]})
plt.style.use("seaborn-v0_8-darkgrid")
fig, (ax1, ax2) = plt.subplots(
2, 1, figsize=(14, 10), sharex=True, gridspec_kw={"height_ratios": [3, 1]}
)
x_axis_indices = np.arange(len(df_equity))
# Equity Curve Plot
ax1.plot(x_axis_indices, equity_curve, label='Equity Curve', color='blue', linewidth=1.5)
ax1.set_ylabel('Equity', fontsize=12)
ax1.legend(loc='upper left')
ax1.plot(
x_axis_indices, equity_curve, label="Equity Curve", color="blue", linewidth=1.5
)
ax1.set_ylabel("Equity", fontsize=12)
ax1.legend(loc="upper left")
ax1.grid(True)
ax1.set_title(title, fontsize=16)
# Drawdown Curve Plot
ax2.fill_between(x_axis_indices, 0, drawdown, color='red', alpha=0.3)
ax2.plot(x_axis_indices, drawdown, color='red', linewidth=1.0, linestyle='--', label='Drawdown')
ax2.set_ylabel('Drawdown Rate', fontsize=12)
ax2.set_xlabel('Data Point Index (Date Labels Below)', fontsize=12)
ax2.set_title('Portfolio Drawdown Curve', fontsize=14)
ax2.legend(loc='upper left')
ax2.fill_between(x_axis_indices, 0, drawdown, color="red", alpha=0.3)
ax2.plot(
x_axis_indices,
drawdown,
color="red",
linewidth=1.0,
linestyle="--",
label="Drawdown",
)
ax2.set_ylabel("Drawdown Rate", fontsize=12)
ax2.set_xlabel("Data Point Index (Date Labels Below)", fontsize=12)
ax2.set_title("Portfolio Drawdown Curve", fontsize=14)
ax2.legend(loc="upper left")
ax2.grid(True)
ax2.set_ylim(0, max(drawdown.max() * 1.1, 0.05))
@@ -193,9 +206,12 @@ def plot_equity_and_drawdown_chart(snapshots: List[PortfolioSnapshot], initial_c
num_ticks = 10
if len(df_equity) > 0:
tick_positions = np.linspace(0, len(df_equity) - 1, num_ticks, dtype=int)
tick_labels = [df_equity['datetime'].iloc[i].strftime('%Y-%m-%d %H:%M') for i in tick_positions]
tick_labels = [
df_equity["datetime"].iloc[i].strftime("%Y-%m-%d %H:%M")
for i in tick_positions
]
ax1.set_xticks(tick_positions)
ax1.set_xticklabels(tick_labels, rotation=45, ha='right')
ax1.set_xticklabels(tick_labels, rotation=45, ha="right")
plt.tight_layout()
plt.show()
@@ -213,30 +229,38 @@ def plot_close_price_chart(bars: List[Bar], title: str = "Close Price Chart") ->
print("No bar data available to plot close price.")
return
df_prices = pd.DataFrame([
{'datetime': b.datetime, 'close_price': b.close}
for b in bars
])
df_prices = pd.DataFrame(
[{"datetime": b.datetime, "close_price": b.close} for b in bars]
)
plt.style.use('seaborn-v0_8-darkgrid')
plt.style.use("seaborn-v0_8-darkgrid")
fig, ax = plt.subplots(1, 1, figsize=(14, 7)) # Single subplot
x_axis_indices = np.arange(len(df_prices))
ax.plot(x_axis_indices, df_prices['close_price'], label='Close Price', color='orange', linewidth=1.5)
ax.set_ylabel('Price', fontsize=12)
ax.set_xlabel('Data Point Index (Date Labels Below)', fontsize=12)
ax.plot(
x_axis_indices,
df_prices["close_price"],
label="Close Price",
color="orange",
linewidth=1.5,
)
ax.set_ylabel("Price", fontsize=12)
ax.set_xlabel("Data Point Index (Date Labels Below)", fontsize=12)
ax.set_title(title, fontsize=16)
ax.legend(loc='upper left')
ax.legend(loc="upper left")
ax.grid(True)
# Set X-axis ticks to show actual dates at intervals
num_ticks = 10
if len(df_prices) > 0:
tick_positions = np.linspace(0, len(df_prices) - 1, num_ticks, dtype=int)
tick_labels = [df_prices['datetime'].iloc[i].strftime('%Y-%m-%d %H:%M') for i in tick_positions]
tick_labels = [
df_prices["datetime"].iloc[i].strftime("%Y-%m-%d %H:%M")
for i in tick_positions
]
ax.set_xticks(tick_positions)
ax.set_xticklabels(tick_labels, rotation=45, ha='right')
ax.set_xticklabels(tick_labels, rotation=45, ha="right")
plt.tight_layout()
plt.show()
@@ -244,7 +268,7 @@ def plot_close_price_chart(bars: List[Bar], title: str = "Close Price Chart") ->
# 辅助函数:计算单笔交易的盈亏
def calculate_trade_pnl(
trade: Trade, entry_price: float, exit_price: float, direction: str
trade: Trade, entry_price: float, exit_price: float, direction: str
) -> float:
if direction == "LONG":
pnl = (exit_price - entry_price) * trade.volume