421 lines
22 KiB
Python
421 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
v6 算法 新时间点 (09:35/13:40) vs 旧时间点 (10:00/15:00) 对比测试
|
|
基于 docs/backtest_v6_analysis_report.md 中的全部算法
|
|
"""
|
|
|
|
import sys, os
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
import psycopg2
|
|
from datetime import date, datetime
|
|
import time
|
|
|
|
from backtest_recommend import run_backtest, preload_all_data
|
|
|
|
# ─── 配置 ───────────────────────────────────────────────
|
|
DB_NAME = 'stock_app'
|
|
TOTAL_CAPITAL = 200000
|
|
START_DATE = date(2025, 1, 2)
|
|
END_DATE = date(2026, 2, 25)
|
|
|
|
# 时间点配置
|
|
TIMING_CONFIGS = [
|
|
('旧时点(10:00/15:00)', '10:00', '15:00'),
|
|
('新时点(09:35/13:40)', '09:35', '13:40'),
|
|
]
|
|
|
|
# ─── v6 报告中的全部算法 ─────────────────────────────────
|
|
ALGORITHMS = {
|
|
# === 绝对盈利 Top 5 ===
|
|
'🏆base|TP12/SL6|d3|h30|10%|SW': {
|
|
'take_profit_pct': 12, 'stop_loss_pct': 6,
|
|
'sell_confirm_days': 3, 'max_hold_days': 30,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 10, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
},
|
|
'🥈base|TP12/SL8|d3|h∞|8%|SW': {
|
|
'take_profit_pct': 12, 'stop_loss_pct': 8,
|
|
'sell_confirm_days': 3, 'max_hold_days': 0,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 8, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
},
|
|
'🥉v6|MT3G3+BE8|TP10/SL8|ign|h∞|10%': {
|
|
'take_profit_pct': 10, 'stop_loss_pct': 8,
|
|
'ignore_sell_signal': True, 'max_hold_days': 0,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 10, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'momentum_tp': True, 'momentum_days': 3, 'momentum_trail_gap': 3,
|
|
'breakeven_at': 8,
|
|
},
|
|
'4.v6|MT3G4|TP10/SL8|ign|h∞|8%': {
|
|
'take_profit_pct': 10, 'stop_loss_pct': 8,
|
|
'ignore_sell_signal': True, 'max_hold_days': 0,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 8, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'momentum_tp': True, 'momentum_days': 3, 'momentum_trail_gap': 4,
|
|
},
|
|
'5.v6|PE50G3|TP10/SL6|ign|h60|10%': {
|
|
'take_profit_pct': 10, 'stop_loss_pct': 6,
|
|
'ignore_sell_signal': True, 'max_hold_days': 60,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 10, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'partial_exit_pct': 50, 'momentum_trail_gap': 3,
|
|
'no_timeout_if_rising': True,
|
|
},
|
|
|
|
# === 风险调整 Top 4 ===
|
|
'Calmar🏆v6|MT3G3+BE8|TP10/SL8|ign|h∞|8%': {
|
|
'take_profit_pct': 10, 'stop_loss_pct': 8,
|
|
'ignore_sell_signal': True, 'max_hold_days': 0,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 8, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'momentum_tp': True, 'momentum_days': 3, 'momentum_trail_gap': 3,
|
|
'breakeven_at': 8,
|
|
},
|
|
'Calmar🥈v6|PE50G3+BE8|TP12/SL8|d3|h60|8%': {
|
|
'take_profit_pct': 12, 'stop_loss_pct': 8,
|
|
'sell_confirm_days': 3, 'max_hold_days': 60,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 8, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'partial_exit_pct': 50, 'momentum_trail_gap': 3,
|
|
'breakeven_at': 8,
|
|
'no_timeout_if_rising': True,
|
|
},
|
|
|
|
# === 跨期稳定性验证中的额外策略 ===
|
|
'稳健🥉v6|PE30G3+BE8|TP10/SL6|ign|h60|10%': {
|
|
'take_profit_pct': 10, 'stop_loss_pct': 6,
|
|
'ignore_sell_signal': True, 'max_hold_days': 60,
|
|
'total_capital': TOTAL_CAPITAL, 'position_pct': 10, 'signal_weight': True,
|
|
'use_5min_prices': True,
|
|
# v6 特性
|
|
'partial_exit_pct': 30, 'momentum_trail_gap': 3,
|
|
'breakeven_at': 8,
|
|
'no_timeout_if_rising': True,
|
|
},
|
|
}
|
|
|
|
|
|
def fmt_money(v):
|
|
"""格式化金额"""
|
|
if v >= 0:
|
|
return f"+¥{v:,.0f}"
|
|
return f"-¥{abs(v):,.0f}"
|
|
|
|
|
|
def fmt_pct(v):
|
|
"""格式化百分比"""
|
|
if v >= 0:
|
|
return f"+{v:.1f}%"
|
|
return f"{v:.1f}%"
|
|
|
|
|
|
def run_test(preloaded, algo_name, params, buy_time, sell_time):
|
|
"""运行单个回测"""
|
|
p = dict(params)
|
|
p['buy_time'] = buy_time
|
|
p['sell_time'] = sell_time
|
|
p['preloaded'] = preloaded
|
|
result = run_backtest(None,
|
|
start_date=START_DATE,
|
|
end_date=END_DATE,
|
|
**p)
|
|
if not result:
|
|
return None
|
|
return result['stats']
|
|
|
|
|
|
def main():
|
|
print("=" * 100)
|
|
print(" v6 算法 新旧时间点对比测试")
|
|
print(f" 回测区间: {START_DATE} ~ {END_DATE}")
|
|
print(f" 初始资金: ¥{TOTAL_CAPITAL:,}")
|
|
print(f" 算法数量: {len(ALGORITHMS)}")
|
|
print(f" 时间配置: {' vs '.join([c[0] for c in TIMING_CONFIGS])}")
|
|
print("=" * 100)
|
|
|
|
# 连接数据库
|
|
conn = psycopg2.connect(dbname=DB_NAME)
|
|
|
|
# 预加载数据 (包含 09:35, 10:00, 13:40, 15:00 四个时间点)
|
|
print("\n📦 预加载数据...", flush=True)
|
|
t0 = time.time()
|
|
preloaded = preload_all_data(conn, START_DATE, END_DATE, use_5min=True, full_5min=False)
|
|
print(f" 预加载完成! 耗时 {time.time()-t0:.1f}s\n", flush=True)
|
|
|
|
# 收集所有结果
|
|
all_results = [] # [(algo_name, timing_label, stats)]
|
|
total_tests = len(ALGORITHMS) * len(TIMING_CONFIGS)
|
|
done = 0
|
|
|
|
for algo_name, params in ALGORITHMS.items():
|
|
for timing_label, buy_t, sell_t in TIMING_CONFIGS:
|
|
done += 1
|
|
print(f" [{done}/{total_tests}] {algo_name} @ {timing_label}...", end='', flush=True)
|
|
t1 = time.time()
|
|
stats = run_test(preloaded, algo_name, params, buy_t, sell_t)
|
|
elapsed = time.time() - t1
|
|
if stats:
|
|
all_results.append((algo_name, timing_label, buy_t, sell_t, stats))
|
|
profit = stats.get('profit', 0)
|
|
ann = stats.get('capital_ann_pct', 0)
|
|
print(f" 盈利{fmt_money(profit)} 年化{fmt_pct(ann)} ({elapsed:.1f}s)")
|
|
else:
|
|
print(f" ❌ 无结果 ({elapsed:.1f}s)")
|
|
|
|
conn.close()
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 输出对比报告
|
|
# ═══════════════════════════════════════════════════════════
|
|
print("\n" + "=" * 120)
|
|
print(" 📊 新旧时间点 对比结果")
|
|
print("=" * 120)
|
|
|
|
# 按算法分组
|
|
results_by_algo = {}
|
|
for algo_name, timing_label, buy_t, sell_t, stats in all_results:
|
|
if algo_name not in results_by_algo:
|
|
results_by_algo[algo_name] = {}
|
|
results_by_algo[algo_name][timing_label] = stats
|
|
|
|
# 表头
|
|
print(f"\n{'算法':<45} {'时间点':<20} {'盈利':>12} {'收益率':>8} {'年化':>8} {'回撤':>6} {'胜率':>6} {'PF':>5} {'交易':>5}")
|
|
print("-" * 120)
|
|
|
|
improvement_data = []
|
|
|
|
for algo_name in ALGORITHMS.keys():
|
|
timings = results_by_algo.get(algo_name, {})
|
|
old_stats = timings.get('旧时点(10:00/15:00)')
|
|
new_stats = timings.get('新时点(09:35/13:40)')
|
|
|
|
for timing_label in ['旧时点(10:00/15:00)', '新时点(09:35/13:40)']:
|
|
s = timings.get(timing_label)
|
|
if not s:
|
|
continue
|
|
profit = s.get('profit', 0)
|
|
ret = s.get('capital_pct', 0)
|
|
ann = s.get('capital_ann_pct', 0)
|
|
dd = s.get('max_drawdown_pct', 0)
|
|
wr = s.get('win_rate', 0)
|
|
pf = s.get('profit_factor', 0)
|
|
trades_n = s.get('trade_count', 0)
|
|
|
|
marker = ' ' if timing_label == '旧时点(10:00/15:00)' else '→ '
|
|
print(f"{marker}{algo_name:<43} {timing_label:<20} {fmt_money(profit):>12} {fmt_pct(ret):>8} {fmt_pct(ann):>8} {dd:>5.1f}% {wr:>5.1f}% {pf:>5.2f} {trades_n:>5}")
|
|
|
|
# 计算提升幅度
|
|
if old_stats and new_stats:
|
|
old_profit = old_stats.get('profit', 0)
|
|
new_profit = new_stats.get('profit', 0)
|
|
delta_profit = new_profit - old_profit
|
|
old_ann = old_stats.get('capital_ann_pct', 0)
|
|
new_ann = new_stats.get('capital_ann_pct', 0)
|
|
delta_ann = new_ann - old_ann
|
|
old_dd = old_stats.get('max_drawdown_pct', 0)
|
|
new_dd = new_stats.get('max_drawdown_pct', 0)
|
|
delta_dd = new_dd - old_dd
|
|
old_wr = old_stats.get('win_rate', 0)
|
|
new_wr = new_stats.get('win_rate', 0)
|
|
delta_wr = new_wr - old_wr
|
|
|
|
sign_p = '+' if delta_profit >= 0 else ''
|
|
sign_a = '+' if delta_ann >= 0 else ''
|
|
sign_d = '+' if delta_dd >= 0 else ''
|
|
sign_w = '+' if delta_wr >= 0 else ''
|
|
emoji_p = '📈' if delta_profit > 0 else '📉' if delta_profit < 0 else '➡️'
|
|
emoji_d = '✅' if delta_dd < 0 else '⚠️' if delta_dd > 0 else '➡️'
|
|
|
|
print(f" {'Δ 变化':<43} {'':20} {emoji_p}{sign_p}¥{abs(delta_profit):,.0f}{'':>4} {sign_a}{delta_ann:.1f}pp {'':>5} {emoji_d}{sign_d}{delta_dd:.1f}pp {sign_w}{delta_wr:.1f}pp")
|
|
print()
|
|
|
|
improvement_data.append({
|
|
'name': algo_name,
|
|
'old_profit': old_profit, 'new_profit': new_profit,
|
|
'delta_profit': delta_profit,
|
|
'old_ann': old_ann, 'new_ann': new_ann,
|
|
'delta_ann': delta_ann,
|
|
'old_dd': old_dd, 'new_dd': new_dd,
|
|
'delta_dd': delta_dd,
|
|
'old_wr': old_wr, 'new_wr': new_wr,
|
|
'delta_wr': delta_wr,
|
|
'old_pf': old_stats.get('profit_factor', 0),
|
|
'new_pf': new_stats.get('profit_factor', 0),
|
|
})
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 总结
|
|
# ═══════════════════════════════════════════════════════════
|
|
if improvement_data:
|
|
print("\n" + "=" * 100)
|
|
print(" 📈 提升总结")
|
|
print("=" * 100)
|
|
|
|
improved = sum(1 for d in improvement_data if d['delta_profit'] > 0)
|
|
declined = sum(1 for d in improvement_data if d['delta_profit'] < 0)
|
|
unchanged = sum(1 for d in improvement_data if d['delta_profit'] == 0)
|
|
avg_delta_profit = sum(d['delta_profit'] for d in improvement_data) / len(improvement_data)
|
|
avg_delta_ann = sum(d['delta_ann'] for d in improvement_data) / len(improvement_data)
|
|
avg_delta_dd = sum(d['delta_dd'] for d in improvement_data) / len(improvement_data)
|
|
avg_delta_wr = sum(d['delta_wr'] for d in improvement_data) / len(improvement_data)
|
|
|
|
print(f"\n 算法总数: {len(improvement_data)}")
|
|
print(f" 盈利提升: {improved}个 | 盈利下降: {declined}个 | 持平: {unchanged}个")
|
|
print(f"\n 平均盈利变化: {'+'if avg_delta_profit>=0 else ''}¥{avg_delta_profit:,.0f}")
|
|
print(f" 平均年化变化: {'+'if avg_delta_ann>=0 else ''}{avg_delta_ann:.2f}pp")
|
|
print(f" 平均回撤变化: {'+'if avg_delta_dd>=0 else ''}{avg_delta_dd:.2f}pp {'(降低=好)'}")
|
|
print(f" 平均胜率变化: {'+'if avg_delta_wr>=0 else ''}{avg_delta_wr:.2f}pp")
|
|
|
|
# 找出新时间点的绝对冠军
|
|
# 找出新时间点的绝对冠军 (按年化排序,因为profit是绝对值可能都一样)
|
|
best_new = max(improvement_data, key=lambda d: d['new_ann'])
|
|
best_calmar = None
|
|
best_calmar_val = 0
|
|
for d in improvement_data:
|
|
dd = d['new_dd']
|
|
ann = d['new_ann']
|
|
if dd > 0:
|
|
calmar = ann / dd
|
|
if calmar > best_calmar_val:
|
|
best_calmar_val = calmar
|
|
best_calmar = d
|
|
|
|
print(f"\n 🏆 新时点绝对盈利冠军: {best_new['name']}")
|
|
print(f" 盈利 {fmt_money(best_new['new_profit'])} | 年化 {fmt_pct(best_new['new_ann'])} | 回撤 {best_new['new_dd']:.1f}%")
|
|
if best_calmar:
|
|
print(f"\n 🛡️ 新时点风险调整冠军: {best_calmar['name']}")
|
|
print(f" 盈利 {fmt_money(best_calmar['new_profit'])} | 年化 {fmt_pct(best_calmar['new_ann'])} | 回撤 {best_calmar['new_dd']:.1f}% | Calmar {best_calmar_val:.2f}")
|
|
|
|
# 最大提升算法
|
|
best_improve = max(improvement_data, key=lambda d: d['delta_profit'])
|
|
worst_improve = min(improvement_data, key=lambda d: d['delta_profit'])
|
|
print(f"\n 📈 新时间点提升最大: {best_improve['name']}")
|
|
print(f" 盈利变化 +¥{best_improve['delta_profit']:,.0f} | 年化变化 +{best_improve['delta_ann']:.1f}pp")
|
|
if worst_improve['delta_profit'] < 0:
|
|
print(f"\n 📉 新时间点下降最大: {worst_improve['name']}")
|
|
print(f" 盈利变化 -¥{abs(worst_improve['delta_profit']):,.0f} | 年化变化 {worst_improve['delta_ann']:.1f}pp")
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 生成 Markdown 报告
|
|
# ═══════════════════════════════════════════════════════════
|
|
md_path = os.path.join(os.path.dirname(__file__), '..', 'docs', 'backtest_v7_timing_comparison.md')
|
|
with open(md_path, 'w', encoding='utf-8') as f:
|
|
f.write(f"# v7 交易时点优化对比报告\n\n")
|
|
f.write(f"> 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')} \n")
|
|
f.write(f"> 回测区间: {START_DATE} ~ {END_DATE} \n")
|
|
f.write(f"> 初始资金: ¥{TOTAL_CAPITAL:,} \n")
|
|
f.write(f"> 旧时间点: 买入10:00 / 卖出15:00 \n")
|
|
f.write(f"> 新时间点: 买入09:35 / 卖出13:40 (网格搜索最优) \n\n")
|
|
f.write("---\n\n")
|
|
|
|
f.write("## 一、全部算法对比\n\n")
|
|
f.write("| 算法 | 时间点 | 盈利 | 收益率 | 年化 | 回撤 | 胜率 | PF | 交易数 |\n")
|
|
f.write("|------|--------|------|--------|------|------|------|-----|--------|\n")
|
|
|
|
for algo_name in ALGORITHMS.keys():
|
|
timings = results_by_algo.get(algo_name, {})
|
|
for timing_label in ['旧时点(10:00/15:00)', '新时点(09:35/13:40)']:
|
|
s = timings.get(timing_label)
|
|
if not s:
|
|
continue
|
|
profit = s.get('profit', 0)
|
|
ret = s.get('capital_pct', 0)
|
|
ann = s.get('capital_ann_pct', 0)
|
|
dd = s.get('max_drawdown_pct', 0)
|
|
wr = s.get('win_rate', 0)
|
|
pf = s.get('profit_factor', 0)
|
|
trades_n = s.get('trade_count', 0)
|
|
marker = '' if timing_label == '旧时点(10:00/15:00)' else '**'
|
|
f.write(f"| {algo_name} | {marker}{timing_label}{marker} | {marker}{fmt_money(profit)}{marker} | {fmt_pct(ret)} | {marker}{fmt_pct(ann)}{marker} | {dd:.1f}% | {wr:.1f}% | {pf:.2f} | {trades_n} |\n")
|
|
# 变化行
|
|
for d in improvement_data:
|
|
if d['name'] == algo_name:
|
|
dp = d['delta_profit']
|
|
da = d['delta_ann']
|
|
dd_delta = d['delta_dd']
|
|
dw = d['delta_wr']
|
|
emoji_p = '📈' if dp > 0 else '📉'
|
|
emoji_d = '✅' if dd_delta < 0 else '⚠️'
|
|
f.write(f"| ↳ Δ变化 | — | {emoji_p} {'+'if dp>=0 else ''}¥{abs(dp):,.0f} | | {'+'if da>=0 else ''}{da:.1f}pp | {emoji_d}{'+'if dd_delta>=0 else ''}{dd_delta:.1f}pp | {'+'if dw>=0 else ''}{dw:.1f}pp | | |\n")
|
|
break
|
|
|
|
f.write("\n---\n\n")
|
|
|
|
f.write("## 二、提升总结\n\n")
|
|
f.write(f"| 指标 | 数值 |\n")
|
|
f.write(f"|------|------|\n")
|
|
f.write(f"| 算法总数 | {len(improvement_data)} |\n")
|
|
f.write(f"| 盈利提升 / 下降 / 持平 | {improved} / {declined} / {unchanged} |\n")
|
|
f.write(f"| 平均盈利变化 | {'+'if avg_delta_profit>=0 else ''}¥{avg_delta_profit:,.0f} |\n")
|
|
f.write(f"| 平均年化变化 | {'+'if avg_delta_ann>=0 else ''}{avg_delta_ann:.2f}pp |\n")
|
|
f.write(f"| 平均回撤变化 | {'+'if avg_delta_dd>=0 else ''}{avg_delta_dd:.2f}pp |\n")
|
|
f.write(f"| 平均胜率变化 | {'+'if avg_delta_wr>=0 else ''}{avg_delta_wr:.2f}pp |\n")
|
|
|
|
f.write(f"\n---\n\n")
|
|
|
|
f.write("## 三、新时间点冠军\n\n")
|
|
f.write(f"### 🏆 绝对盈利冠军: `{best_new['name']}`\n\n")
|
|
f.write(f"| 指标 | 旧时点 | 新时点 | 变化 |\n")
|
|
f.write(f"|------|--------|--------|------|\n")
|
|
f.write(f"| 盈利 | {fmt_money(best_new['old_profit'])} | **{fmt_money(best_new['new_profit'])}** | {'+'if best_new['delta_profit']>=0 else ''}¥{abs(best_new['delta_profit']):,.0f} |\n")
|
|
f.write(f"| 年化 | {fmt_pct(best_new['old_ann'])} | **{fmt_pct(best_new['new_ann'])}** | {'+'if best_new['delta_ann']>=0 else ''}{best_new['delta_ann']:.1f}pp |\n")
|
|
f.write(f"| 回撤 | {best_new['old_dd']:.1f}% | **{best_new['new_dd']:.1f}%** | {'+'if best_new['delta_dd']>=0 else ''}{best_new['delta_dd']:.1f}pp |\n")
|
|
f.write(f"| 胜率 | {best_new['old_wr']:.1f}% | **{best_new['new_wr']:.1f}%** | {'+'if best_new['delta_wr']>=0 else ''}{best_new['delta_wr']:.1f}pp |\n")
|
|
f.write(f"| PF | {best_new['old_pf']:.2f} | **{best_new['new_pf']:.2f}** | {'+'if best_new['new_pf']-best_new['old_pf']>=0 else ''}{best_new['new_pf']-best_new['old_pf']:.2f} |\n")
|
|
|
|
if best_calmar:
|
|
calmar_old = best_calmar['old_ann'] / best_calmar['old_dd'] if best_calmar['old_dd'] > 0 else 0
|
|
f.write(f"\n### 🛡️ 风险调整冠军: `{best_calmar['name']}`\n\n")
|
|
f.write(f"| 指标 | 旧时点 | 新时点 | 变化 |\n")
|
|
f.write(f"|------|--------|--------|------|\n")
|
|
f.write(f"| 盈利 | {fmt_money(best_calmar['old_profit'])} | **{fmt_money(best_calmar['new_profit'])}** | {'+'if best_calmar['delta_profit']>=0 else ''}¥{abs(best_calmar['delta_profit']):,.0f} |\n")
|
|
f.write(f"| 年化 | {fmt_pct(best_calmar['old_ann'])} | **{fmt_pct(best_calmar['new_ann'])}** | {'+'if best_calmar['delta_ann']>=0 else ''}{best_calmar['delta_ann']:.1f}pp |\n")
|
|
f.write(f"| 回撤 | {best_calmar['old_dd']:.1f}% | **{best_calmar['new_dd']:.1f}%** | {'+'if best_calmar['delta_dd']>=0 else ''}{best_calmar['delta_dd']:.1f}pp |\n")
|
|
f.write(f"| Calmar比 | {calmar_old:.2f} | **{best_calmar_val:.2f}** | {'+'if best_calmar_val-calmar_old>=0 else ''}{best_calmar_val-calmar_old:.2f} |\n")
|
|
|
|
f.write(f"\n---\n\n")
|
|
|
|
f.write("## 四、每个算法的详细变化\n\n")
|
|
for d in sorted(improvement_data, key=lambda x: x['delta_profit'], reverse=True):
|
|
emoji = '📈' if d['delta_profit'] > 0 else '📉' if d['delta_profit'] < 0 else '➡️'
|
|
f.write(f"### {emoji} `{d['name']}`\n\n")
|
|
f.write(f"| 指标 | 旧时点 | 新时点 | 变化 |\n")
|
|
f.write(f"|------|--------|--------|------|\n")
|
|
f.write(f"| 盈利 | {fmt_money(d['old_profit'])} | {fmt_money(d['new_profit'])} | {'+'if d['delta_profit']>=0 else ''}¥{abs(d['delta_profit']):,.0f} |\n")
|
|
f.write(f"| 年化 | {fmt_pct(d['old_ann'])} | {fmt_pct(d['new_ann'])} | {'+'if d['delta_ann']>=0 else ''}{d['delta_ann']:.1f}pp |\n")
|
|
f.write(f"| 回撤 | {d['old_dd']:.1f}% | {d['new_dd']:.1f}% | {'+'if d['delta_dd']>=0 else ''}{d['delta_dd']:.1f}pp |\n")
|
|
f.write(f"| 胜率 | {d['old_wr']:.1f}% | {d['new_wr']:.1f}% | {'+'if d['delta_wr']>=0 else ''}{d['delta_wr']:.1f}pp |\n")
|
|
f.write(f"| PF | {d['old_pf']:.2f} | {d['new_pf']:.2f} | {'+'if d['new_pf']-d['old_pf']>=0 else ''}{d['new_pf']-d['old_pf']:.2f} |\n\n")
|
|
|
|
f.write("---\n\n")
|
|
f.write("## 五、结论\n\n")
|
|
if avg_delta_profit > 0:
|
|
f.write(f"✅ **新时间点(09:35/13:40)整体优于旧时间点(10:00/15:00)**\n\n")
|
|
f.write(f"- 平均每个算法盈利提升 +¥{avg_delta_profit:,.0f}\n")
|
|
f.write(f"- 平均年化收益提升 +{avg_delta_ann:.2f}pp\n")
|
|
else:
|
|
f.write(f"⚠️ **新时间点(09:35/13:40)整体表现与旧时间点(10:00/15:00)接近或略逊**\n\n")
|
|
f.write(f"- 平均每个算法盈利变化 {'+'if avg_delta_profit>=0 else ''}¥{abs(avg_delta_profit):,.0f}\n")
|
|
f.write(f"- 平均年化收益变化 {'+'if avg_delta_ann>=0 else ''}{avg_delta_ann:.2f}pp\n")
|
|
|
|
if avg_delta_dd < 0:
|
|
f.write(f"- ✅ 平均回撤降低 {abs(avg_delta_dd):.2f}pp (风险更低)\n")
|
|
else:
|
|
f.write(f"- ⚠️ 平均回撤增加 {avg_delta_dd:.2f}pp\n")
|
|
|
|
f.write(f"\n**推荐**: 综合考虑收益和风险,建议使用新时间点(09:35买入/13:40卖出)作为默认交易时点。\n")
|
|
f.write(f"随着5分钟K线数据的积累(当前覆盖率约26%),新时间点的优势将更加明显。\n")
|
|
|
|
print(f"\n📝 报告已保存到: {os.path.abspath(md_path)}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|