1、新增傅里叶策略
2、新增策略管理、策略重启功能
This commit is contained in:
@@ -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轴点 xp,Y轴值是所有 '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所有指标分析完成。")
|
||||
|
||||
Reference in New Issue
Block a user