1、新增傅里叶策略

2、新增策略管理、策略重启功能
This commit is contained in:
2025-11-20 16:10:16 +08:00
parent 2ae9f2db9e
commit 2c917a467a
19 changed files with 3368 additions and 6643 deletions

View File

@@ -122,166 +122,117 @@ class ResultAnalyzer:
def analyze_indicators(self, profit_offset: float = 0.0) -> None:
"""
分析所有平仓交易的指标值与实现盈亏的关系,并绘制累积盈亏曲线
图表将展示指标值区间与对应累积盈亏的关系,帮助找出具有概率优势的指标区间
同时会标记出最大和最小累积盈亏对应的指标值,并优化标注位置以避免重叠。
分析开仓时的指标值与平仓时实现盈亏的关系,并绘制累积盈亏曲线。
简化假设:每笔开仓(is_open_trade=True)都会在后续被一笔平仓(is_close_trade=True)全平
"""
close_trades = [trade for trade in self.trade_history if trade.is_close_trade]
# 1. 分离开仓和平仓交易
open_trades = [t for t in self.trade_history if t.is_open_trade]
close_trades = [t for t in self.trade_history if t.is_close_trade]
if not close_trades:
print(
"没有平仓交易可供分析。请确保 trade_history 中有 is_close_trade 为 True 的交易。"
)
print("没有平仓交易可供分析。请确保策略有平仓行为。")
return
# 2. 配对数量(取较小值)
num_pairs = min(len(open_trades), len(close_trades))
if num_pairs == 0:
print("开仓和平仓交易数量不匹配,无法配对分析。")
return
print(f"将进行 {num_pairs} 组开仓-平仓配对分析")
for indicator in self.indicator_list:
# 假设每个 indicator 对象都有一个 get_name() 方法
indicator_name = indicator.get_name()
# 收集指标的所有值和对应的实现盈亏
# 3. 按配对顺序收集指标值和盈亏
indi_values = []
pnls = []
for trade in close_trades:
# 确保 trade.indicator_dict 中包含当前指标的值
# 并且这个值是可用的非None或NaN
if (
trade.indicator_dict is not None
and indicator_name in trade.indicator_dict
and trade.indicator_dict[indicator_name] is not None
):
# 检查是否为 NaN如果使用 np.nan则需要 isinstance(value, float) and np.isnan(value)
# 为了简化,这里假设非 None 即为有效数值
if not (
isinstance(trade.indicator_dict[indicator_name], float)
and np.isnan(trade.indicator_dict[indicator_name])
):
indi_values.append(trade.indicator_dict[indicator_name])
pnls.append(trade.realized_pnl - profit_offset)
for i in range(num_pairs):
open_trade = open_trades[i]
close_trade = close_trades[i]
if (open_trade.indicator_dict is not None and
indicator_name in open_trade.indicator_dict):
value = open_trade.indicator_dict[indicator_name]
if not (isinstance(value, float) and np.isnan(value)):
indi_values.append(value)
pnls.append(close_trade.realized_pnl - profit_offset)
if not indi_values:
print(f"指标 '{indicator_name}' 没有对应的有效平仓交易数据跳过绘图。")
print(f"指标 '{indicator_name}' 没有有效数据跳过绘图。")
continue
# 将收集到的数据转换为 Pandas DataFrame 进行更便捷的处理
# DataFrame 的结构为:['indicator_value', 'realized_pnl']
df = pd.DataFrame({"indicator_value": indi_values, "realized_pnl": pnls})
# 4. 数据清洗与准备
df = pd.DataFrame({
"indicator_value": indi_values,
"realized_pnl": pnls
})
def remove_extreme(df, col='indicator_value', k=3):
"""IQR 稳健过滤,返回过滤后的 df 和被剔除的行数"""
q1, q3 = df[col].quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - k * iqr, q3 + k * iqr
mask = df[col].between(lower, upper)
n_remove = (~mask).sum()
return df[mask].copy(), n_remove
mask = df[col].between(q1 - k * iqr, q3 + k * iqr)
return df[mask].copy(), (~mask).sum()
df, n_drop = remove_extreme(df) # 默认 k=2.5
df, n_drop = remove_extreme(df)
if n_drop:
print(f"指标 '{indicator_name}' 过滤掉 {n_drop} 个极端异常值 "
f"({n_drop / len(df) * 100:.1f}%),剩余 {len(df)} 条样本。")
print(f"指标 '{indicator_name}' 过滤掉 {n_drop} 个极端值,剩余 {len(df)} 条样本。")
if df.empty:
print(f"指标 '{indicator_name}' 过滤后无数据,跳过绘图。")
continue
# 确保数据框不为空
if df.empty:
print(f"指标 '{indicator_name}' 的数据框为空,跳过绘图。")
continue
# 按照指标值进行排序,这是计算累积和的关键步骤
# 5. 计算累积盈亏曲线数据
df = df.sort_values(by="indicator_value").reset_index(drop=True)
# --- 绘制累积收益曲线 ---
plt.figure(figsize=(12, 7)) # 创建一个新的图表
# 获取指标值的范围用于生成X轴的等距点
# 定义 max_val 和 min_val这是修复的关键
min_val = df["indicator_value"].min()
max_val = df["indicator_value"].max()
# 特殊处理:如果所有指标值都相同
# 处理所有值相同的情况
if min_val == max_val:
total_pnl = df["realized_pnl"].sum()
print(
f"指标 '{indicator_name}' 的所有值都相同 ({min_val:.2f}),无法创建区间图,绘制一个点表示总收益。"
)
plt.plot(min_val, total_pnl, "ro", markersize=8) # 绘制一个点
plt.title(
f"{indicator_name} Value vs. Cumulative PnL (All values are {min_val:.2f})"
)
plt.figure(figsize=(12, 7))
plt.plot(min_val, total_pnl, "ro", markersize=8)
plt.title(f"{indicator_name} Value vs. Cumulative PnL (All values are {min_val:.2f})")
plt.xlabel(f"{indicator_name} Value")
plt.ylabel("Cumulative Realized PnL")
plt.grid(True)
plt.text(
min_val,
total_pnl,
f" Total PnL: {total_pnl:.2f}",
ha="center",
va="bottom",
)
plt.text(min_val, total_pnl, f" Total PnL: {total_pnl:.2f}", ha="center", va="bottom")
plt.show()
continue
# 生成X轴上的100个等距点这些点代表了指标值的不同阈值
x_points = np.linspace(min_val, max_val, 100)
y_cumulative_pnl = [df[df["indicator_value"] <= xp]["realized_pnl"].sum() for xp in x_points]
# 计算Y轴的值对于每个X轴点 xpY轴值是所有 'indicator_value' <= xp 的 'realized_pnl' 之和
y_cumulative_pnl = []
for xp in x_points:
# 筛选出指标值小于等于当前x_point的所有交易并求和它们的 realized_pnl
cumulative_pnl = df[df["indicator_value"] <= xp]["realized_pnl"].sum()
y_cumulative_pnl.append(cumulative_pnl)
# 6. 绘图(完整版)
plt.figure(figsize=(12, 7))
plt.plot(x_points, y_cumulative_pnl, marker="o", markersize=3,
label=f"Cumulative PnL for {indicator_name}", alpha=0.8)
# 绘制累积盈亏曲线
plt.plot(
x_points,
y_cumulative_pnl,
marker="o",
linestyle="-",
markersize=3,
label=f"Cumulative PnL for {indicator_name}",
alpha=0.8,
)
# 标记累积盈亏的最大值点
# 标记最大值点
optimal_index = np.argmax(y_cumulative_pnl)
optimal_indi_value = x_points[optimal_index]
max_cumulative_pnl = y_cumulative_pnl[optimal_index]
# 标记累积盈亏的最小值点
min_pnl_index = np.argmin(y_cumulative_pnl[:optimal_index]) if len(y_cumulative_pnl[:optimal_index]) > 0 else 0
# 标记最小值点(在最大值左侧找)
min_pnl_index = np.argmin(y_cumulative_pnl[:optimal_index]) if optimal_index > 0 else 0
min_indi_value_at_pnl = x_points[min_pnl_index]
min_cumulative_pnl = y_cumulative_pnl[min_pnl_index]
# 动态调整标注位置以避免重叠
offset_x = (max_val - min_val) * 0.05 # 水平偏移量
# 动态调整标注位置使用已定义的max_val和min_val
offset_x = (max_val - min_val) * 0.05
# 默认标注为右侧对齐,文本在点的左侧
max_ha = "right"
max_xytext_x = optimal_indi_value - offset_x
min_ha = "right"
min_xytext_x = min_indi_value_at_pnl - offset_x
# 如果最大值点在最小值点右侧,则最大值标注放左侧,最小值标注放右侧
# 这样可以避免标注文本重叠
if optimal_indi_value > min_indi_value_at_pnl:
max_ha = "left"
max_xytext_x = optimal_indi_value + offset_x
min_ha = "right"
min_xytext_x = min_indi_value_at_pnl - offset_x
else: # 如果最大值点在最小值点左侧或重合
max_ha = "right"
max_xytext_x = optimal_indi_value - offset_x
min_ha = "left"
min_xytext_x = min_indi_value_at_pnl + offset_x
max_ha, max_xytext_x = "left", optimal_indi_value + offset_x
min_ha, min_xytext_x = "right", min_indi_value_at_pnl - offset_x
else:
max_ha, max_xytext_x = "right", optimal_indi_value - offset_x
min_ha, min_xytext_x = "left", min_indi_value_at_pnl + offset_x
# 绘制最大值垂直线和标注
plt.axvline(
optimal_indi_value,
color="red",
linestyle="--",
label=f"Max PnL Threshold: {optimal_indi_value:.2f}",
alpha=0.7,
)
# 绘制最大值标注
plt.axvline(optimal_indi_value, color="red", linestyle="--", alpha=0.7)
plt.annotate(
f"Max Cum. PnL: {max_cumulative_pnl:.2f}",
xy=(optimal_indi_value, max_cumulative_pnl),
@@ -289,24 +240,12 @@ class ResultAnalyzer:
arrowprops=dict(facecolor="red", shrink=0.05),
horizontalalignment=max_ha,
verticalalignment="bottom",
color="red",
color="red"
)
# 绘制最小值垂直线和标注
plt.axvline(
min_indi_value_at_pnl,
color="blue",
linestyle=":",
label=f"Min PnL Threshold: {min_indi_value_at_pnl:.2f}",
alpha=0.7,
)
# 垂直偏移最小值标注,避免与曲线重叠
min_text_y_offset = (
-(max_cumulative_pnl - min_cumulative_pnl) * 0.1
if max_cumulative_pnl != min_cumulative_pnl
else -0.05
)
# 绘制最小值标注
plt.axvline(min_indi_value_at_pnl, color="blue", linestyle=":", alpha=0.7)
min_text_y_offset = -(max_cumulative_pnl - min_cumulative_pnl) * 0.1
plt.annotate(
f"Min Cum. PnL: {min_cumulative_pnl:.2f}",
xy=(min_indi_value_at_pnl, min_cumulative_pnl),
@@ -314,7 +253,7 @@ class ResultAnalyzer:
arrowprops=dict(facecolor="blue", shrink=0.05),
horizontalalignment=min_ha,
verticalalignment="top",
color="blue",
color="blue"
)
plt.title(f"{indicator_name} Value vs. Cumulative Realized PnL")
@@ -322,7 +261,7 @@ class ResultAnalyzer:
plt.ylabel("Cumulative Realized PnL")
plt.grid(True)
plt.legend()
plt.tight_layout() # 自动调整图表参数,使之更紧凑
plt.tight_layout()
plt.show()
print("\n所有指标分析图表已生成。")
print("\n所有指标分析成。")