Merge remote-tracking branch 'origin/main'

# Conflicts:
#	strategy/data_loader.py
#	strategy/indicators.py
This commit is contained in:
27942
2026-02-25 02:09:52 +08:00
8 changed files with 4943 additions and 46 deletions

156
strategy/backtest_2023.py Normal file
View File

@@ -0,0 +1,156 @@
"""
2023 年回测入口 - 用训练出的最优参数在 2023 全年数据上回测
"""
import json
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pandas as pd
import numpy as np
from strategy.data_loader import load_klines
from strategy.indicators import compute_all_indicators
from strategy.strategy_signal import (
generate_indicator_signals, compute_composite_score,
apply_htf_filter,
)
from strategy.backtest_engine import BacktestEngine
def main():
# 加载最佳参数
params_path = os.path.join(os.path.dirname(__file__), 'best_params_2020_2022.json')
if not os.path.exists(params_path):
print(f"错误: 找不到参数文件 {params_path}")
print("请先运行 train.py 进行训练")
return
with open(params_path, 'r') as f:
params = json.load(f)
print("=" * 70)
print("2023 年真实回测 (样本外)")
print("=" * 70)
# 加载数据 (多加载一些前置数据用于指标预热)
print("\n加载数据...")
df_5m = load_klines('5m', '2022-11-01', '2024-01-01')
df_1h = load_klines('1h', '2022-11-01', '2024-01-01')
print(f" 5m: {len(df_5m)} 条, 1h: {len(df_1h)}")
# 计算指标
print("计算指标...")
df_5m = compute_all_indicators(df_5m, params)
df_1h = compute_all_indicators(df_1h, params)
# 生成信号
print("生成信号...")
df_5m = generate_indicator_signals(df_5m, params)
df_1h = generate_indicator_signals(df_1h, params)
# 综合得分
score = compute_composite_score(df_5m, params)
score = apply_htf_filter(score, df_1h, params)
# 截取 2023 年数据
mask = (df_5m.index >= '2023-01-01') & (df_5m.index < '2024-01-01')
df_2023 = df_5m.loc[mask]
score_2023 = score.loc[mask]
print(f" 2023年数据: {len(df_2023)}")
# 回测
print("\n开始回测...")
engine = BacktestEngine(
initial_capital=1000.0,
margin_per_trade=25.0,
leverage=50,
fee_rate=0.0005,
rebate_ratio=0.70,
max_daily_drawdown=50.0,
min_hold_bars=1,
stop_loss_pct=params['stop_loss_pct'],
take_profit_pct=params['take_profit_pct'],
max_positions=int(params.get('max_positions', 3)),
)
result = engine.run(df_2023, score_2023, open_threshold=params['open_threshold'])
# ============================================================
# 输出结果
# ============================================================
print("\n" + "=" * 70)
print("2023 年回测结果")
print("=" * 70)
print(f" 初始资金: 1000.00 U")
print(f" 最终资金: {result['final_capital']:.2f} U")
print(f" 总收益: {result['total_pnl']:.2f} U")
print(f" 总手续费: {result['total_fee']:.2f} U")
print(f" 总返佣: {result['total_rebate']:.2f} U")
print(f" 交易次数: {result['num_trades']}")
print(f" 胜率: {result['win_rate']:.2%}")
print(f" 盈亏比: {result['profit_factor']:.2f}")
print(f" 日均收益: {result['avg_daily_pnl']:.2f} U")
print(f" 最大日回撤: {result['max_daily_dd']:.2f} U")
# 月度统计
daily_pnl = result['daily_pnl']
if daily_pnl:
df_daily = pd.DataFrame(list(daily_pnl.items()), columns=['date', 'pnl'])
df_daily['date'] = pd.to_datetime(df_daily['date'])
df_daily['month'] = df_daily['date'].dt.to_period('M')
monthly = df_daily.groupby('month')['pnl'].agg(['sum', 'count', 'mean', 'min'])
monthly.columns = ['月收益', '交易天数', '日均收益', '最大日亏损']
print("\n" + "-" * 70)
print("月度统计:")
print("-" * 70)
for idx, row in monthly.iterrows():
status = "" if row['月收益'] > 0 else ""
dd_status = "" if row['最大日亏损'] > -50 else "⚠️"
print(f" {idx} | 收益: {row['月收益']:>8.2f}U | "
f"日均: {row['日均收益']:>7.2f}U | "
f"最大日亏: {row['最大日亏损']:>7.2f}U {dd_status} | {status}")
# 日均收益是否达标
avg_daily = df_daily['pnl'].mean()
days_above_50 = (df_daily['pnl'] >= 50).sum()
days_below_neg50 = (df_daily['pnl'] < -50).sum()
print(f"\n 日均收益: {avg_daily:.2f}U {'✅ 达标' if avg_daily >= 50 else '❌ 未达标'}")
print(f" 日收益>=50U的天数: {days_above_50} / {len(df_daily)}")
print(f" 日回撤>50U的天数: {days_below_neg50} / {len(df_daily)}")
# 保存逐日 PnL
output_dir = os.path.dirname(__file__)
if daily_pnl:
df_daily_out = pd.DataFrame(list(daily_pnl.items()), columns=['date', 'pnl'])
df_daily_out['cumulative_pnl'] = df_daily_out['pnl'].cumsum()
daily_csv = os.path.join(output_dir, 'backtest_2023_daily_pnl.csv')
df_daily_out.to_csv(daily_csv, index=False)
print(f"\n逐日PnL已保存: {daily_csv}")
# 保存交易记录
if result['trades']:
trades_data = []
for t in result['trades']:
trades_data.append({
'entry_time': t.entry_time,
'exit_time': t.exit_time,
'direction': '' if t.direction == 1 else '',
'entry_price': t.entry_price,
'exit_price': t.exit_price,
'pnl': round(t.pnl, 4),
'fee': round(t.fee, 4),
'rebate': round(t.rebate, 4),
'holding_bars': t.holding_bars,
})
df_trades = pd.DataFrame(trades_data)
trades_csv = os.path.join(output_dir, 'backtest_2023_trades.csv')
df_trades.to_csv(trades_csv, index=False)
print(f"交易记录已保存: {trades_csv}")
print("\n" + "=" * 70)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,366 @@
date,pnl,cumulative_pnl
2023-01-01,-13.24030780391167,-13.24030780391167
2023-01-02,-6.446144625588336,-19.686452429500005
2023-01-03,1.604645378778668,-18.081807050721338
2023-01-04,56.24999999999943,38.168192949278094
2023-01-05,0.33406192478092844,38.50225487405902
2023-01-06,-4.28936549280675,34.21288938125227
2023-01-07,14.786004985419915,48.99889436667218
2023-01-08,74.57347500987392,123.5723693765461
2023-01-09,100.43932814185042,224.01169751839655
2023-01-10,-11.400950315324355,212.61074720307218
2023-01-11,119.73808656927638,332.34883377234854
2023-01-12,-46.87500000000008,285.4738337723485
2023-01-13,24.95301706493165,310.42685083728014
2023-01-14,36.87499999999923,347.3018508372794
2023-01-15,-42.164085212923396,305.137765624356
2023-01-16,-42.37550401931327,262.76226160504274
2023-01-17,-39.23788027501831,223.52438133002443
2023-01-18,10.968990345277774,234.4933716753022
2023-01-19,-17.519099732290336,216.97427194301187
2023-01-20,258.1249999999992,475.0992719430111
2023-01-21,-26.63800028548694,448.46127165752415
2023-01-22,-27.862031912052718,420.59923974547144
2023-01-23,-40.570948096946864,380.0282916485246
2023-01-24,153.52954075490175,533.5578324034263
2023-01-25,76.9770888682049,610.5349212716312
2023-01-26,-43.12500000000006,567.4099212716312
2023-01-27,4.574004296634158,571.9839255682654
2023-01-28,110.89323045990369,682.8771560281691
2023-01-29,65.39337129909306,748.2705273272621
2023-01-30,89.15573047359129,837.4262578008534
2023-01-31,-33.65661196432259,803.7696458365308
2023-02-01,-42.346269783529834,761.423376053001
2023-02-02,5.6249999999996945,767.0483760530007
2023-02-03,-38.015635992701625,729.032740060299
2023-02-04,-6.061609860875804,722.9711301994232
2023-02-05,91.32767729532108,814.2988074947443
2023-02-06,12.340770370556534,826.6395778653009
2023-02-07,-38.31565728321921,788.3239205820817
2023-02-08,-10.754771409427375,777.5691491726543
2023-02-09,180.00000000000034,957.5691491726546
2023-02-10,90.62500000000017,1048.1941491726548
2023-02-11,-40.23338038017373,1007.9607687924811
2023-02-12,50.843005130216255,1058.8037739226972
2023-02-13,-43.12499999999999,1015.6787739226972
2023-02-14,-45.000000000000014,970.6787739226972
2023-02-15,211.8749999999989,1182.553773922696
2023-02-16,123.74999999999923,1306.3037739226954
2023-02-17,-43.1250000000002,1263.1787739226952
2023-02-18,-30.38874055427764,1232.7900333684177
2023-02-19,2.5357914472640948,1235.3258248156817
2023-02-20,10.13433097511785,1245.4601557907995
2023-02-21,41.5960974135976,1287.056253204397
2023-02-22,39.9999999999999,1327.056253204397
2023-02-23,-13.478092269295054,1313.578160935102
2023-02-24,-33.61127571949922,1279.9668852156028
2023-02-25,56.87500000000026,1336.841885215603
2023-02-26,52.96295237320025,1389.8048375888034
2023-02-27,-21.012957685691507,1368.7918799031117
2023-02-28,-17.510525954953838,1351.2813539481579
2023-03-01,48.43861521106629,1399.719969159224
2023-03-02,5.370115987703678,1405.0900851469278
2023-03-03,114.81346298981391,1519.9035481367416
2023-03-04,-9.975555145718014,1509.9279929910235
2023-03-05,-40.070176144315376,1469.8578168467081
2023-03-06,-11.368260314393366,1458.4895565323147
2023-03-07,29.800169999101556,1488.2897265314164
2023-03-08,-41.066516251938125,1447.2232102794783
2023-03-09,178.12499999999966,1625.3482102794778
2023-03-10,69.54272408495805,1694.890934364436
2023-03-11,218.8441938252083,1913.7351281896442
2023-03-12,-43.124999999999986,1870.6101281896442
2023-03-13,31.874999999999282,1902.4851281896435
2023-03-14,71.24999999999882,1973.7351281896424
2023-03-15,-46.87499999999994,1926.8601281896424
2023-03-16,-47.19007322239124,1879.6700549672512
2023-03-17,249.37499999999858,2129.0450549672496
2023-03-18,-39.375000000000455,2089.670054967249
2023-03-19,-37.72054430463878,2051.9495106626105
2023-03-20,-48.125000000000256,2003.8245106626102
2023-03-21,-48.955919791756116,1954.8685908708542
2023-03-22,-41.34851470243514,1913.520076168419
2023-03-23,-40.53293708055611,1872.987139087863
2023-03-24,-40.62177198705648,1832.3653671008065
2023-03-25,-12.808008186129763,1819.5573589146768
2023-03-26,44.60456842447671,1864.1619273391534
2023-03-27,33.10077209532424,1897.2626994344776
2023-03-28,-40.54590870106257,1856.7167907334149
2023-03-29,32.68290319879213,1889.399693932207
2023-03-30,-42.18093929678437,1847.2187546354226
2023-03-31,-30.18435407486843,1817.034400560554
2023-04-01,-18.629080327821725,1798.4053202327323
2023-04-02,78.36987927566116,1876.7751995083934
2023-04-03,-14.310148754581672,1862.4650507538117
2023-04-04,67.49999999999953,1929.9650507538113
2023-04-05,40.981623971671745,1970.946674725483
2023-04-06,27.071471993660218,1998.0181467191433
2023-04-07,69.95707277743023,2067.9752194965736
2023-04-08,16.74780048539444,2084.723019981968
2023-04-09,47.28635900754121,2132.009378989509
2023-04-10,92.25687955666356,2224.2662585461726
2023-04-11,13.118453933996253,2237.3847124801687
2023-04-12,56.24999999999995,2293.6347124801687
2023-04-13,179.99999999999943,2473.634712480168
2023-04-14,-21.87500000000091,2451.7597124801673
2023-04-15,0.6060490696602718,2452.3657615498278
2023-04-16,69.62762541934998,2521.9933869691777
2023-04-17,65.86212708679903,2587.8555140559765
2023-04-18,11.498179506746062,2599.3536935627226
2023-04-19,223.1815368848387,2822.5352304475614
2023-04-20,-43.12499999999999,2779.4102304475614
2023-04-21,31.875000000000142,2811.2852304475614
2023-04-22,-29.84738767205142,2781.43784277551
2023-04-23,13.154733757123939,2794.5925765326338
2023-04-24,6.396324986843672,2800.9889015194776
2023-04-25,123.74011330692568,2924.7290148264033
2023-04-26,261.41402100210826,3186.1430358285115
2023-04-27,24.10664119340748,3210.249677021919
2023-04-28,-10.62612672957838,3199.6235502923405
2023-04-29,23.597281085864836,3223.2208313782053
2023-04-30,-44.289545693205795,3178.9312856849997
2023-05-01,54.37500000000002,3233.3062856849997
2023-05-02,-2.017144354733148,3231.2891413302664
2023-05-03,81.77174013147513,3313.0608814617417
2023-05-04,-25.04485318862535,3288.0160282731163
2023-05-05,171.61321250444536,3459.629240777562
2023-05-06,52.49999999999993,3512.129240777562
2023-05-07,-33.168505868798924,3478.960734908763
2023-05-08,8.124999999999917,3487.085734908763
2023-05-09,-12.584917211039063,3474.5008176977235
2023-05-10,52.019173246963206,3526.519990944687
2023-05-11,90.00000000000003,3616.519990944687
2023-05-12,126.17158084605737,3742.691571790744
2023-05-13,8.545669463922014,3751.237241254666
2023-05-14,1.8924095106008352,3753.129650765267
2023-05-15,10.943496294234567,3764.0731470595015
2023-05-16,-30.264218933238727,3733.808928126263
2023-05-17,30.026157944387133,3763.83508607065
2023-05-18,23.13394310654973,3786.9690291772
2023-05-19,15.149354042131993,3802.118383219332
2023-05-20,-16.41923661401725,3785.6991466053146
2023-05-21,7.846127328452241,3793.545273933767
2023-05-22,21.279945465818493,3814.8252193995854
2023-05-23,70.02437426214179,3884.8495936617273
2023-05-24,72.70217181804935,3957.551765479777
2023-05-25,-16.75708025521019,3940.794685224567
2023-05-26,18.74999999999976,3959.5446852245664
2023-05-27,39.20117578898695,3998.745861013553
2023-05-28,110.91041187532412,4109.6562728888775
2023-05-29,-32.49999999999996,4077.1562728888775
2023-05-30,10.449185839751497,4087.605458728629
2023-05-31,-10.136360635569764,4077.469098093059
2023-06-01,14.358833920817762,4091.8279320138768
2023-06-02,65.90793376617691,4157.735865780053
2023-06-03,2.796329514545665,4160.532195294599
2023-06-04,-6.651917557240656,4153.8802777373585
2023-06-05,168.7500000000002,4322.6302777373585
2023-06-06,-43.12500000000005,4279.5052777373585
2023-06-07,34.36109954050025,4313.866377277859
2023-06-08,-25.452505493132982,4288.413871784726
2023-06-09,7.08099194612123,4295.494863730848
2023-06-10,121.25000000000003,4416.744863730848
2023-06-11,-20.937848671894088,4395.807015058954
2023-06-12,-21.37821245611659,4374.428802602837
2023-06-13,-13.703676058378367,4360.725126544458
2023-06-14,166.27160175206745,4526.996728296526
2023-06-15,15.069476387286578,4542.066204683812
2023-06-16,106.96240346155682,4649.02860814537
2023-06-17,-11.250000000000341,4637.77860814537
2023-06-18,-10.120078428463955,4627.658529716906
2023-06-19,-38.97345934789618,4588.68507036901
2023-06-20,42.86461601305588,4631.549686382065
2023-06-21,187.49999999999937,4819.0496863820645
2023-06-22,31.80896255236462,4850.858648934429
2023-06-23,23.85220828852076,4874.71085722295
2023-06-24,-30.371879505034936,4844.338977717915
2023-06-25,12.45391150222293,4856.792889220138
2023-06-26,78.4973773289051,4935.290266549043
2023-06-27,-39.87042272482522,4895.419843824217
2023-06-28,88.12499999999999,4983.544843824217
2023-06-29,-40.51516243372697,4943.029681390491
2023-06-30,105.46931766571112,5048.498999056202
2023-07-01,-13.796315557439133,5034.702683498763
2023-07-02,-41.59344453195906,4993.109238966804
2023-07-03,28.89550853439942,5022.004747501203
2023-07-04,-5.3332824068334626,5016.6714650943695
2023-07-05,90.00000000000007,5106.6714650943695
2023-07-06,65.47404542743519,5172.145510521805
2023-07-07,-8.375646476725684,5163.769864045079
2023-07-08,2.0825098762478143,5165.852373921327
2023-07-09,-12.284261589217671,5153.568112332109
2023-07-10,4.7069840687455775,5158.275096400855
2023-07-11,29.744227743509978,5188.019324144365
2023-07-12,13.786259374056783,5201.805583518421
2023-07-13,179.07533869993108,5380.880922218353
2023-07-14,74.05844966531129,5454.939371883664
2023-07-15,-24.570748403829185,5430.368623479834
2023-07-16,6.803094664210427,5437.1717181440445
2023-07-17,27.410515235666534,5464.582233379711
2023-07-18,4.897911857902959,5469.480145237614
2023-07-19,6.308694855454021,5475.788840093068
2023-07-20,41.93544480774592,5517.7242849008135
2023-07-21,16.096325607887692,5533.820610508701
2023-07-22,-2.7025252562022395,5531.1180852524985
2023-07-23,-36.865989707872636,5494.252095544626
2023-07-24,86.80264475362813,5581.054740298255
2023-07-25,-24.16611590079934,5556.888624397456
2023-07-26,-12.803087110558725,5544.085537286897
2023-07-27,29.206711819629362,5573.2922491065265
2023-07-28,13.812242058805946,5587.104491165333
2023-07-29,11.977355224058611,5599.081846389391
2023-07-30,-5.150633707859324,5593.931212681532
2023-07-31,-31.586985170360826,5562.344227511171
2023-08-01,34.57747987841326,5596.921707389584
2023-08-02,19.53988833101348,5616.461595720598
2023-08-03,26.963711901014904,5643.425307621613
2023-08-04,0.4840259877677071,5643.90933360938
2023-08-05,8.851832296486007,5652.761165905866
2023-08-06,-4.2929518635524,5648.468214042313
2023-08-07,4.423504364914294,5652.891718407227
2023-08-08,79.37499999999964,5732.266718407227
2023-08-09,-1.0555745361586295,5731.211143871068
2023-08-10,-15.802261213704188,5715.408882657364
2023-08-11,-19.434997227631737,5695.973885429733
2023-08-12,-14.180480342977727,5681.793405086755
2023-08-13,-25.33078616920967,5656.462618917545
2023-08-14,-29.365151992789308,5627.097466924756
2023-08-15,-1.25,5625.847466924756
2023-08-16,90.00000000000014,5715.847466924756
2023-08-17,181.8750000000003,5897.722466924756
2023-08-18,20.625000000000128,5918.347466924756
2023-08-19,-27.59032679226934,5890.757140132487
2023-08-20,-23.87577257083145,5866.881367561656
2023-08-21,-22.59314579170043,5844.288221769955
2023-08-22,113.75000000000014,5958.038221769955
2023-08-23,8.70637062854804,5966.744592398503
2023-08-24,22.993336529216194,5989.73792892772
2023-08-25,-41.837123197460336,5947.900805730259
2023-08-26,-20.238929404490822,5927.661876325768
2023-08-27,-2.006488273327664,5925.655388052441
2023-08-28,14.024404963407886,5939.679793015848
2023-08-29,143.13137132947674,6082.811164345325
2023-08-30,-43.125000000000064,6039.686164345325
2023-08-31,31.6249079218707,6071.311072267195
2023-09-01,56.25000000000017,6127.561072267195
2023-09-02,-27.62863635821808,6099.932435908977
2023-09-03,-18.380622263138324,6081.551813645839
2023-09-04,-4.629639415293694,6076.922174230545
2023-09-05,24.14433818655366,6101.066512417099
2023-09-06,-46.72867445132057,6054.337837965779
2023-09-07,-3.6562754869909058,6050.681562478788
2023-09-08,42.254582050520796,6092.936144529309
2023-09-09,-30.94267544057865,6061.99346908873
2023-09-10,53.2912479279699,6115.284717016701
2023-09-11,146.25000000000028,6261.534717016701
2023-09-12,-18.31728723379048,6243.21742978291
2023-09-13,-2.141383984249483,6241.07604579866
2023-09-14,56.24999999999978,6297.32604579866
2023-09-15,-37.98094850138423,6259.345097297276
2023-09-16,-33.38082020481237,6225.964277092464
2023-09-17,-2.737757522168219,6223.226519570296
2023-09-18,28.781772740126208,6252.008292310422
2023-09-19,4.273043833846757,6256.281336144269
2023-09-20,24.242189180030334,6280.523525324299
2023-09-21,90.00000000000011,6370.523525324299
2023-09-22,-30.115416338642312,6340.408108985656
2023-09-23,-24.28225302122884,6316.1258559644275
2023-09-24,-42.98193078708244,6273.143925177345
2023-09-25,-5.561737200674414,6267.582187976671
2023-09-26,19.48204133300013,6287.064229309671
2023-09-27,56.24999999999976,6343.314229309671
2023-09-28,109.05931606292351,6452.373545372594
2023-09-29,-3.7500000000000906,6448.623545372594
2023-09-30,34.11899846667325,6482.7425438392675
2023-10-01,94.78119432198413,6577.5237381612515
2023-10-02,-43.125,6534.3987381612515
2023-10-03,-1.875,6532.5237381612515
2023-10-04,60.137202072927415,6592.660940234179
2023-10-05,-47.15508896159986,6545.505851272579
2023-10-06,-41.50509357550347,6504.000757697076
2023-10-07,-21.848785430736374,6482.151972266339
2023-10-08,-2.133334555954726,6480.018637710384
2023-10-09,120.94752554130996,6600.966163251694
2023-10-10,-30.55517668632468,6570.410986565369
2023-10-11,12.918899632245676,6583.3298861976145
2023-10-12,86.41945469513863,6669.749340892753
2023-10-13,-38.92743296812289,6630.82190792463
2023-10-14,-9.664398308242207,6621.157509616388
2023-10-15,0.32442084565507256,6621.481930462043
2023-10-16,17.666860566236,6639.148791028279
2023-10-17,71.96782243080129,6711.11661345908
2023-10-18,-26.10871664471985,6685.007896814361
2023-10-19,27.131717309356045,6712.1396141237165
2023-10-20,104.9999999999995,6817.139614123716
2023-10-21,95.53563465609497,6912.67524877981
2023-10-22,-39.07986275419109,6873.5953860256195
2023-10-23,159.37499999999892,7032.970386025619
2023-10-24,125.89084826115247,7158.861234286771
2023-10-25,-39.7457464430455,7119.1154878437255
2023-10-26,76.84599913941024,7195.961486983136
2023-10-27,-40.93958561285608,7155.02190137028
2023-10-28,-0.12582720298443117,7154.896074167295
2023-10-29,1.7653990903884869,7156.661473257684
2023-10-30,14.80106712479257,7171.462540382477
2023-10-31,-0.7859893532677678,7170.676551029209
2023-11-01,45.63294697011628,7216.309497999325
2023-11-02,66.38708795013648,7282.696585949461
2023-11-03,7.818102574679843,7290.514688524141
2023-11-04,78.74999999999962,7369.264688524141
2023-11-05,93.530612820923,7462.795301345064
2023-11-06,-8.841261133494985,7453.954040211569
2023-11-07,13.587699262662081,7467.541739474231
2023-11-08,-27.39298530178767,7440.148754172443
2023-11-09,233.65340882150477,7673.802162993948
2023-11-10,-43.1250000000002,7630.677162993948
2023-11-11,65.04467136509504,7695.721834359043
2023-11-12,-24.51389709666313,7671.20793726238
2023-11-13,6.381878610566607,7677.589815872947
2023-11-14,124.7302217309696,7802.320037603917
2023-11-15,-15.4428344502457,7786.877203153671
2023-11-16,22.788163027994152,7809.665366181665
2023-11-17,-40.58878257444296,7769.076583607222
2023-11-18,10.05941544996627,7779.135999057188
2023-11-19,77.18072310694912,7856.316722164137
2023-11-20,-33.719119790508586,7822.597602373628
2023-11-21,-39.39362250328517,7783.203979870344
2023-11-22,62.49999999999903,7845.703979870343
2023-11-23,-43.750000000000135,7801.953979870343
2023-11-24,43.12499999999979,7845.078979870343
2023-11-25,2.7372815485791326,7847.816261418921
2023-11-26,-19.434424922416646,7828.381836496505
2023-11-27,74.22705085378658,7902.608887350291
2023-11-28,-21.10900157778203,7881.4998857725095
2023-11-29,1.584413397259107,7883.084299169768
2023-11-30,-5.586662127245132,7877.497637042523
2023-12-01,68.65652016602549,7946.154157208548
2023-12-02,116.24999999999937,8062.404157208547
2023-12-03,48.229742583832405,8110.633899792379
2023-12-04,13.529045410407036,8124.162945202786
2023-12-05,93.37552564770242,8217.538470850488
2023-12-06,-39.37279199427863,8178.1656788562095
2023-12-07,160.37637314576938,8338.54205200198
2023-12-08,-32.94183697847613,8305.600215023504
2023-12-09,-22.342388616671474,8283.257826406832
2023-12-10,-29.00075720912898,8254.257069197703
2023-12-11,112.22981231135489,8366.486881509058
2023-12-12,-46.76135746492969,8319.725524044128
2023-12-13,102.21323939340017,8421.938763437529
2023-12-14,33.74999999999983,8455.688763437529
2023-12-15,63.75916511664332,8519.447928554173
2023-12-16,-32.300370998108704,8487.147557556063
2023-12-17,17.066032222984127,8504.213589779047
2023-12-18,168.68048765961714,8672.894077438665
2023-12-19,25.118359089859197,8698.012436528525
2023-12-20,-43.080721035673186,8654.931715492852
2023-12-21,-31.737797271451193,8623.193918221401
2023-12-22,47.49999999999983,8670.693918221401
2023-12-23,20.47855147595199,8691.172469697352
2023-12-24,108.453203178383,8799.625672875736
2023-12-25,-34.74504529363933,8764.880627582097
2023-12-26,104.99999999999984,8869.880627582097
2023-12-27,204.66554953165667,9074.546177113754
2023-12-28,-5.606769621811815,9068.939407491942
2023-12-29,220.95172689013637,9289.891134382078
2023-12-30,-15.105228436109885,9274.785905945968
2023-12-31,-0.005881281931285898,9274.780024664036
1 date pnl cumulative_pnl
2 2023-01-01 -13.24030780391167 -13.24030780391167
3 2023-01-02 -6.446144625588336 -19.686452429500005
4 2023-01-03 1.604645378778668 -18.081807050721338
5 2023-01-04 56.24999999999943 38.168192949278094
6 2023-01-05 0.33406192478092844 38.50225487405902
7 2023-01-06 -4.28936549280675 34.21288938125227
8 2023-01-07 14.786004985419915 48.99889436667218
9 2023-01-08 74.57347500987392 123.5723693765461
10 2023-01-09 100.43932814185042 224.01169751839655
11 2023-01-10 -11.400950315324355 212.61074720307218
12 2023-01-11 119.73808656927638 332.34883377234854
13 2023-01-12 -46.87500000000008 285.4738337723485
14 2023-01-13 24.95301706493165 310.42685083728014
15 2023-01-14 36.87499999999923 347.3018508372794
16 2023-01-15 -42.164085212923396 305.137765624356
17 2023-01-16 -42.37550401931327 262.76226160504274
18 2023-01-17 -39.23788027501831 223.52438133002443
19 2023-01-18 10.968990345277774 234.4933716753022
20 2023-01-19 -17.519099732290336 216.97427194301187
21 2023-01-20 258.1249999999992 475.0992719430111
22 2023-01-21 -26.63800028548694 448.46127165752415
23 2023-01-22 -27.862031912052718 420.59923974547144
24 2023-01-23 -40.570948096946864 380.0282916485246
25 2023-01-24 153.52954075490175 533.5578324034263
26 2023-01-25 76.9770888682049 610.5349212716312
27 2023-01-26 -43.12500000000006 567.4099212716312
28 2023-01-27 4.574004296634158 571.9839255682654
29 2023-01-28 110.89323045990369 682.8771560281691
30 2023-01-29 65.39337129909306 748.2705273272621
31 2023-01-30 89.15573047359129 837.4262578008534
32 2023-01-31 -33.65661196432259 803.7696458365308
33 2023-02-01 -42.346269783529834 761.423376053001
34 2023-02-02 5.6249999999996945 767.0483760530007
35 2023-02-03 -38.015635992701625 729.032740060299
36 2023-02-04 -6.061609860875804 722.9711301994232
37 2023-02-05 91.32767729532108 814.2988074947443
38 2023-02-06 12.340770370556534 826.6395778653009
39 2023-02-07 -38.31565728321921 788.3239205820817
40 2023-02-08 -10.754771409427375 777.5691491726543
41 2023-02-09 180.00000000000034 957.5691491726546
42 2023-02-10 90.62500000000017 1048.1941491726548
43 2023-02-11 -40.23338038017373 1007.9607687924811
44 2023-02-12 50.843005130216255 1058.8037739226972
45 2023-02-13 -43.12499999999999 1015.6787739226972
46 2023-02-14 -45.000000000000014 970.6787739226972
47 2023-02-15 211.8749999999989 1182.553773922696
48 2023-02-16 123.74999999999923 1306.3037739226954
49 2023-02-17 -43.1250000000002 1263.1787739226952
50 2023-02-18 -30.38874055427764 1232.7900333684177
51 2023-02-19 2.5357914472640948 1235.3258248156817
52 2023-02-20 10.13433097511785 1245.4601557907995
53 2023-02-21 41.5960974135976 1287.056253204397
54 2023-02-22 39.9999999999999 1327.056253204397
55 2023-02-23 -13.478092269295054 1313.578160935102
56 2023-02-24 -33.61127571949922 1279.9668852156028
57 2023-02-25 56.87500000000026 1336.841885215603
58 2023-02-26 52.96295237320025 1389.8048375888034
59 2023-02-27 -21.012957685691507 1368.7918799031117
60 2023-02-28 -17.510525954953838 1351.2813539481579
61 2023-03-01 48.43861521106629 1399.719969159224
62 2023-03-02 5.370115987703678 1405.0900851469278
63 2023-03-03 114.81346298981391 1519.9035481367416
64 2023-03-04 -9.975555145718014 1509.9279929910235
65 2023-03-05 -40.070176144315376 1469.8578168467081
66 2023-03-06 -11.368260314393366 1458.4895565323147
67 2023-03-07 29.800169999101556 1488.2897265314164
68 2023-03-08 -41.066516251938125 1447.2232102794783
69 2023-03-09 178.12499999999966 1625.3482102794778
70 2023-03-10 69.54272408495805 1694.890934364436
71 2023-03-11 218.8441938252083 1913.7351281896442
72 2023-03-12 -43.124999999999986 1870.6101281896442
73 2023-03-13 31.874999999999282 1902.4851281896435
74 2023-03-14 71.24999999999882 1973.7351281896424
75 2023-03-15 -46.87499999999994 1926.8601281896424
76 2023-03-16 -47.19007322239124 1879.6700549672512
77 2023-03-17 249.37499999999858 2129.0450549672496
78 2023-03-18 -39.375000000000455 2089.670054967249
79 2023-03-19 -37.72054430463878 2051.9495106626105
80 2023-03-20 -48.125000000000256 2003.8245106626102
81 2023-03-21 -48.955919791756116 1954.8685908708542
82 2023-03-22 -41.34851470243514 1913.520076168419
83 2023-03-23 -40.53293708055611 1872.987139087863
84 2023-03-24 -40.62177198705648 1832.3653671008065
85 2023-03-25 -12.808008186129763 1819.5573589146768
86 2023-03-26 44.60456842447671 1864.1619273391534
87 2023-03-27 33.10077209532424 1897.2626994344776
88 2023-03-28 -40.54590870106257 1856.7167907334149
89 2023-03-29 32.68290319879213 1889.399693932207
90 2023-03-30 -42.18093929678437 1847.2187546354226
91 2023-03-31 -30.18435407486843 1817.034400560554
92 2023-04-01 -18.629080327821725 1798.4053202327323
93 2023-04-02 78.36987927566116 1876.7751995083934
94 2023-04-03 -14.310148754581672 1862.4650507538117
95 2023-04-04 67.49999999999953 1929.9650507538113
96 2023-04-05 40.981623971671745 1970.946674725483
97 2023-04-06 27.071471993660218 1998.0181467191433
98 2023-04-07 69.95707277743023 2067.9752194965736
99 2023-04-08 16.74780048539444 2084.723019981968
100 2023-04-09 47.28635900754121 2132.009378989509
101 2023-04-10 92.25687955666356 2224.2662585461726
102 2023-04-11 13.118453933996253 2237.3847124801687
103 2023-04-12 56.24999999999995 2293.6347124801687
104 2023-04-13 179.99999999999943 2473.634712480168
105 2023-04-14 -21.87500000000091 2451.7597124801673
106 2023-04-15 0.6060490696602718 2452.3657615498278
107 2023-04-16 69.62762541934998 2521.9933869691777
108 2023-04-17 65.86212708679903 2587.8555140559765
109 2023-04-18 11.498179506746062 2599.3536935627226
110 2023-04-19 223.1815368848387 2822.5352304475614
111 2023-04-20 -43.12499999999999 2779.4102304475614
112 2023-04-21 31.875000000000142 2811.2852304475614
113 2023-04-22 -29.84738767205142 2781.43784277551
114 2023-04-23 13.154733757123939 2794.5925765326338
115 2023-04-24 6.396324986843672 2800.9889015194776
116 2023-04-25 123.74011330692568 2924.7290148264033
117 2023-04-26 261.41402100210826 3186.1430358285115
118 2023-04-27 24.10664119340748 3210.249677021919
119 2023-04-28 -10.62612672957838 3199.6235502923405
120 2023-04-29 23.597281085864836 3223.2208313782053
121 2023-04-30 -44.289545693205795 3178.9312856849997
122 2023-05-01 54.37500000000002 3233.3062856849997
123 2023-05-02 -2.017144354733148 3231.2891413302664
124 2023-05-03 81.77174013147513 3313.0608814617417
125 2023-05-04 -25.04485318862535 3288.0160282731163
126 2023-05-05 171.61321250444536 3459.629240777562
127 2023-05-06 52.49999999999993 3512.129240777562
128 2023-05-07 -33.168505868798924 3478.960734908763
129 2023-05-08 8.124999999999917 3487.085734908763
130 2023-05-09 -12.584917211039063 3474.5008176977235
131 2023-05-10 52.019173246963206 3526.519990944687
132 2023-05-11 90.00000000000003 3616.519990944687
133 2023-05-12 126.17158084605737 3742.691571790744
134 2023-05-13 8.545669463922014 3751.237241254666
135 2023-05-14 1.8924095106008352 3753.129650765267
136 2023-05-15 10.943496294234567 3764.0731470595015
137 2023-05-16 -30.264218933238727 3733.808928126263
138 2023-05-17 30.026157944387133 3763.83508607065
139 2023-05-18 23.13394310654973 3786.9690291772
140 2023-05-19 15.149354042131993 3802.118383219332
141 2023-05-20 -16.41923661401725 3785.6991466053146
142 2023-05-21 7.846127328452241 3793.545273933767
143 2023-05-22 21.279945465818493 3814.8252193995854
144 2023-05-23 70.02437426214179 3884.8495936617273
145 2023-05-24 72.70217181804935 3957.551765479777
146 2023-05-25 -16.75708025521019 3940.794685224567
147 2023-05-26 18.74999999999976 3959.5446852245664
148 2023-05-27 39.20117578898695 3998.745861013553
149 2023-05-28 110.91041187532412 4109.6562728888775
150 2023-05-29 -32.49999999999996 4077.1562728888775
151 2023-05-30 10.449185839751497 4087.605458728629
152 2023-05-31 -10.136360635569764 4077.469098093059
153 2023-06-01 14.358833920817762 4091.8279320138768
154 2023-06-02 65.90793376617691 4157.735865780053
155 2023-06-03 2.796329514545665 4160.532195294599
156 2023-06-04 -6.651917557240656 4153.8802777373585
157 2023-06-05 168.7500000000002 4322.6302777373585
158 2023-06-06 -43.12500000000005 4279.5052777373585
159 2023-06-07 34.36109954050025 4313.866377277859
160 2023-06-08 -25.452505493132982 4288.413871784726
161 2023-06-09 7.08099194612123 4295.494863730848
162 2023-06-10 121.25000000000003 4416.744863730848
163 2023-06-11 -20.937848671894088 4395.807015058954
164 2023-06-12 -21.37821245611659 4374.428802602837
165 2023-06-13 -13.703676058378367 4360.725126544458
166 2023-06-14 166.27160175206745 4526.996728296526
167 2023-06-15 15.069476387286578 4542.066204683812
168 2023-06-16 106.96240346155682 4649.02860814537
169 2023-06-17 -11.250000000000341 4637.77860814537
170 2023-06-18 -10.120078428463955 4627.658529716906
171 2023-06-19 -38.97345934789618 4588.68507036901
172 2023-06-20 42.86461601305588 4631.549686382065
173 2023-06-21 187.49999999999937 4819.0496863820645
174 2023-06-22 31.80896255236462 4850.858648934429
175 2023-06-23 23.85220828852076 4874.71085722295
176 2023-06-24 -30.371879505034936 4844.338977717915
177 2023-06-25 12.45391150222293 4856.792889220138
178 2023-06-26 78.4973773289051 4935.290266549043
179 2023-06-27 -39.87042272482522 4895.419843824217
180 2023-06-28 88.12499999999999 4983.544843824217
181 2023-06-29 -40.51516243372697 4943.029681390491
182 2023-06-30 105.46931766571112 5048.498999056202
183 2023-07-01 -13.796315557439133 5034.702683498763
184 2023-07-02 -41.59344453195906 4993.109238966804
185 2023-07-03 28.89550853439942 5022.004747501203
186 2023-07-04 -5.3332824068334626 5016.6714650943695
187 2023-07-05 90.00000000000007 5106.6714650943695
188 2023-07-06 65.47404542743519 5172.145510521805
189 2023-07-07 -8.375646476725684 5163.769864045079
190 2023-07-08 2.0825098762478143 5165.852373921327
191 2023-07-09 -12.284261589217671 5153.568112332109
192 2023-07-10 4.7069840687455775 5158.275096400855
193 2023-07-11 29.744227743509978 5188.019324144365
194 2023-07-12 13.786259374056783 5201.805583518421
195 2023-07-13 179.07533869993108 5380.880922218353
196 2023-07-14 74.05844966531129 5454.939371883664
197 2023-07-15 -24.570748403829185 5430.368623479834
198 2023-07-16 6.803094664210427 5437.1717181440445
199 2023-07-17 27.410515235666534 5464.582233379711
200 2023-07-18 4.897911857902959 5469.480145237614
201 2023-07-19 6.308694855454021 5475.788840093068
202 2023-07-20 41.93544480774592 5517.7242849008135
203 2023-07-21 16.096325607887692 5533.820610508701
204 2023-07-22 -2.7025252562022395 5531.1180852524985
205 2023-07-23 -36.865989707872636 5494.252095544626
206 2023-07-24 86.80264475362813 5581.054740298255
207 2023-07-25 -24.16611590079934 5556.888624397456
208 2023-07-26 -12.803087110558725 5544.085537286897
209 2023-07-27 29.206711819629362 5573.2922491065265
210 2023-07-28 13.812242058805946 5587.104491165333
211 2023-07-29 11.977355224058611 5599.081846389391
212 2023-07-30 -5.150633707859324 5593.931212681532
213 2023-07-31 -31.586985170360826 5562.344227511171
214 2023-08-01 34.57747987841326 5596.921707389584
215 2023-08-02 19.53988833101348 5616.461595720598
216 2023-08-03 26.963711901014904 5643.425307621613
217 2023-08-04 0.4840259877677071 5643.90933360938
218 2023-08-05 8.851832296486007 5652.761165905866
219 2023-08-06 -4.2929518635524 5648.468214042313
220 2023-08-07 4.423504364914294 5652.891718407227
221 2023-08-08 79.37499999999964 5732.266718407227
222 2023-08-09 -1.0555745361586295 5731.211143871068
223 2023-08-10 -15.802261213704188 5715.408882657364
224 2023-08-11 -19.434997227631737 5695.973885429733
225 2023-08-12 -14.180480342977727 5681.793405086755
226 2023-08-13 -25.33078616920967 5656.462618917545
227 2023-08-14 -29.365151992789308 5627.097466924756
228 2023-08-15 -1.25 5625.847466924756
229 2023-08-16 90.00000000000014 5715.847466924756
230 2023-08-17 181.8750000000003 5897.722466924756
231 2023-08-18 20.625000000000128 5918.347466924756
232 2023-08-19 -27.59032679226934 5890.757140132487
233 2023-08-20 -23.87577257083145 5866.881367561656
234 2023-08-21 -22.59314579170043 5844.288221769955
235 2023-08-22 113.75000000000014 5958.038221769955
236 2023-08-23 8.70637062854804 5966.744592398503
237 2023-08-24 22.993336529216194 5989.73792892772
238 2023-08-25 -41.837123197460336 5947.900805730259
239 2023-08-26 -20.238929404490822 5927.661876325768
240 2023-08-27 -2.006488273327664 5925.655388052441
241 2023-08-28 14.024404963407886 5939.679793015848
242 2023-08-29 143.13137132947674 6082.811164345325
243 2023-08-30 -43.125000000000064 6039.686164345325
244 2023-08-31 31.6249079218707 6071.311072267195
245 2023-09-01 56.25000000000017 6127.561072267195
246 2023-09-02 -27.62863635821808 6099.932435908977
247 2023-09-03 -18.380622263138324 6081.551813645839
248 2023-09-04 -4.629639415293694 6076.922174230545
249 2023-09-05 24.14433818655366 6101.066512417099
250 2023-09-06 -46.72867445132057 6054.337837965779
251 2023-09-07 -3.6562754869909058 6050.681562478788
252 2023-09-08 42.254582050520796 6092.936144529309
253 2023-09-09 -30.94267544057865 6061.99346908873
254 2023-09-10 53.2912479279699 6115.284717016701
255 2023-09-11 146.25000000000028 6261.534717016701
256 2023-09-12 -18.31728723379048 6243.21742978291
257 2023-09-13 -2.141383984249483 6241.07604579866
258 2023-09-14 56.24999999999978 6297.32604579866
259 2023-09-15 -37.98094850138423 6259.345097297276
260 2023-09-16 -33.38082020481237 6225.964277092464
261 2023-09-17 -2.737757522168219 6223.226519570296
262 2023-09-18 28.781772740126208 6252.008292310422
263 2023-09-19 4.273043833846757 6256.281336144269
264 2023-09-20 24.242189180030334 6280.523525324299
265 2023-09-21 90.00000000000011 6370.523525324299
266 2023-09-22 -30.115416338642312 6340.408108985656
267 2023-09-23 -24.28225302122884 6316.1258559644275
268 2023-09-24 -42.98193078708244 6273.143925177345
269 2023-09-25 -5.561737200674414 6267.582187976671
270 2023-09-26 19.48204133300013 6287.064229309671
271 2023-09-27 56.24999999999976 6343.314229309671
272 2023-09-28 109.05931606292351 6452.373545372594
273 2023-09-29 -3.7500000000000906 6448.623545372594
274 2023-09-30 34.11899846667325 6482.7425438392675
275 2023-10-01 94.78119432198413 6577.5237381612515
276 2023-10-02 -43.125 6534.3987381612515
277 2023-10-03 -1.875 6532.5237381612515
278 2023-10-04 60.137202072927415 6592.660940234179
279 2023-10-05 -47.15508896159986 6545.505851272579
280 2023-10-06 -41.50509357550347 6504.000757697076
281 2023-10-07 -21.848785430736374 6482.151972266339
282 2023-10-08 -2.133334555954726 6480.018637710384
283 2023-10-09 120.94752554130996 6600.966163251694
284 2023-10-10 -30.55517668632468 6570.410986565369
285 2023-10-11 12.918899632245676 6583.3298861976145
286 2023-10-12 86.41945469513863 6669.749340892753
287 2023-10-13 -38.92743296812289 6630.82190792463
288 2023-10-14 -9.664398308242207 6621.157509616388
289 2023-10-15 0.32442084565507256 6621.481930462043
290 2023-10-16 17.666860566236 6639.148791028279
291 2023-10-17 71.96782243080129 6711.11661345908
292 2023-10-18 -26.10871664471985 6685.007896814361
293 2023-10-19 27.131717309356045 6712.1396141237165
294 2023-10-20 104.9999999999995 6817.139614123716
295 2023-10-21 95.53563465609497 6912.67524877981
296 2023-10-22 -39.07986275419109 6873.5953860256195
297 2023-10-23 159.37499999999892 7032.970386025619
298 2023-10-24 125.89084826115247 7158.861234286771
299 2023-10-25 -39.7457464430455 7119.1154878437255
300 2023-10-26 76.84599913941024 7195.961486983136
301 2023-10-27 -40.93958561285608 7155.02190137028
302 2023-10-28 -0.12582720298443117 7154.896074167295
303 2023-10-29 1.7653990903884869 7156.661473257684
304 2023-10-30 14.80106712479257 7171.462540382477
305 2023-10-31 -0.7859893532677678 7170.676551029209
306 2023-11-01 45.63294697011628 7216.309497999325
307 2023-11-02 66.38708795013648 7282.696585949461
308 2023-11-03 7.818102574679843 7290.514688524141
309 2023-11-04 78.74999999999962 7369.264688524141
310 2023-11-05 93.530612820923 7462.795301345064
311 2023-11-06 -8.841261133494985 7453.954040211569
312 2023-11-07 13.587699262662081 7467.541739474231
313 2023-11-08 -27.39298530178767 7440.148754172443
314 2023-11-09 233.65340882150477 7673.802162993948
315 2023-11-10 -43.1250000000002 7630.677162993948
316 2023-11-11 65.04467136509504 7695.721834359043
317 2023-11-12 -24.51389709666313 7671.20793726238
318 2023-11-13 6.381878610566607 7677.589815872947
319 2023-11-14 124.7302217309696 7802.320037603917
320 2023-11-15 -15.4428344502457 7786.877203153671
321 2023-11-16 22.788163027994152 7809.665366181665
322 2023-11-17 -40.58878257444296 7769.076583607222
323 2023-11-18 10.05941544996627 7779.135999057188
324 2023-11-19 77.18072310694912 7856.316722164137
325 2023-11-20 -33.719119790508586 7822.597602373628
326 2023-11-21 -39.39362250328517 7783.203979870344
327 2023-11-22 62.49999999999903 7845.703979870343
328 2023-11-23 -43.750000000000135 7801.953979870343
329 2023-11-24 43.12499999999979 7845.078979870343
330 2023-11-25 2.7372815485791326 7847.816261418921
331 2023-11-26 -19.434424922416646 7828.381836496505
332 2023-11-27 74.22705085378658 7902.608887350291
333 2023-11-28 -21.10900157778203 7881.4998857725095
334 2023-11-29 1.584413397259107 7883.084299169768
335 2023-11-30 -5.586662127245132 7877.497637042523
336 2023-12-01 68.65652016602549 7946.154157208548
337 2023-12-02 116.24999999999937 8062.404157208547
338 2023-12-03 48.229742583832405 8110.633899792379
339 2023-12-04 13.529045410407036 8124.162945202786
340 2023-12-05 93.37552564770242 8217.538470850488
341 2023-12-06 -39.37279199427863 8178.1656788562095
342 2023-12-07 160.37637314576938 8338.54205200198
343 2023-12-08 -32.94183697847613 8305.600215023504
344 2023-12-09 -22.342388616671474 8283.257826406832
345 2023-12-10 -29.00075720912898 8254.257069197703
346 2023-12-11 112.22981231135489 8366.486881509058
347 2023-12-12 -46.76135746492969 8319.725524044128
348 2023-12-13 102.21323939340017 8421.938763437529
349 2023-12-14 33.74999999999983 8455.688763437529
350 2023-12-15 63.75916511664332 8519.447928554173
351 2023-12-16 -32.300370998108704 8487.147557556063
352 2023-12-17 17.066032222984127 8504.213589779047
353 2023-12-18 168.68048765961714 8672.894077438665
354 2023-12-19 25.118359089859197 8698.012436528525
355 2023-12-20 -43.080721035673186 8654.931715492852
356 2023-12-21 -31.737797271451193 8623.193918221401
357 2023-12-22 47.49999999999983 8670.693918221401
358 2023-12-23 20.47855147595199 8691.172469697352
359 2023-12-24 108.453203178383 8799.625672875736
360 2023-12-25 -34.74504529363933 8764.880627582097
361 2023-12-26 104.99999999999984 8869.880627582097
362 2023-12-27 204.66554953165667 9074.546177113754
363 2023-12-28 -5.606769621811815 9068.939407491942
364 2023-12-29 220.95172689013637 9289.891134382078
365 2023-12-30 -15.105228436109885 9274.785905945968
366 2023-12-31 -0.005881281931285898 9274.780024664036

File diff suppressed because it is too large Load Diff

298
strategy/backtest_engine.py Normal file
View File

@@ -0,0 +1,298 @@
"""
回测引擎 - 完整模拟手续费、返佣延迟到账、每日回撤限制、持仓时间约束
支持同时持有多单并发,严格控制每日最大回撤
"""
import datetime
import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Trade:
entry_time: pd.Timestamp
exit_time: Optional[pd.Timestamp] = None
direction: int = 0
entry_price: float = 0.0
exit_price: float = 0.0
pnl: float = 0.0
fee: float = 0.0
rebate: float = 0.0
holding_bars: int = 0
@dataclass
class OpenPosition:
direction: int = 0
entry_price: float = 0.0
entry_time: pd.Timestamp = None
hold_bars: int = 0
class BacktestEngine:
def __init__(
self,
initial_capital: float = 1000.0,
margin_per_trade: float = 25.0,
leverage: int = 50,
fee_rate: float = 0.0005,
rebate_ratio: float = 0.70,
max_daily_drawdown: float = 50.0,
min_hold_bars: int = 1,
stop_loss_pct: float = 0.005,
take_profit_pct: float = 0.01,
max_positions: int = 3,
):
self.initial_capital = initial_capital
self.margin = margin_per_trade
self.leverage = leverage
self.notional = margin_per_trade * leverage
self.fee_rate = fee_rate
self.rebate_ratio = rebate_ratio
self.max_daily_dd = max_daily_drawdown
self.min_hold_bars = min_hold_bars
self.sl_pct = stop_loss_pct
self.tp_pct = take_profit_pct
self.max_positions = max_positions
def _close_position(self, pos, exit_price, t, today, trades, pending_rebates):
"""平仓一个持仓,返回 net_pnl"""
qty = self.notional / pos.entry_price
if pos.direction == 1:
raw_pnl = qty * (exit_price - pos.entry_price)
else:
raw_pnl = qty * (pos.entry_price - exit_price)
close_fee = self.notional * self.fee_rate
net_pnl = raw_pnl - close_fee
total_fee = self.notional * self.fee_rate * 2
rebate = total_fee * self.rebate_ratio
rebate_date = today + datetime.timedelta(days=1)
pending_rebates.append((rebate_date, rebate))
trades.append(Trade(
entry_time=pos.entry_time, exit_time=t,
direction=pos.direction, entry_price=pos.entry_price,
exit_price=exit_price, pnl=net_pnl, fee=total_fee,
rebate=rebate, holding_bars=pos.hold_bars,
))
return net_pnl
def _worst_unrealized(self, positions, h, lo):
"""计算所有持仓在本K线内的最坏浮动亏损用 high/low"""
worst = 0.0
for pos in positions:
qty = self.notional / pos.entry_price
if pos.direction == 1:
# 多单最坏情况: 价格跌到 low
worst += qty * (lo - pos.entry_price)
else:
# 空单最坏情况: 价格涨到 high
worst += qty * (pos.entry_price - h)
return worst
def run(self, df: pd.DataFrame, score: pd.Series, open_threshold: float = 0.3) -> dict:
capital = self.initial_capital
trades: List[Trade] = []
daily_pnl = {}
pending_rebates = []
positions: List[OpenPosition] = []
used_margin = 0.0
current_date = None
day_pnl = 0.0
day_stopped = False
close_arr = df['close'].values
high_arr = df['high'].values
low_arr = df['low'].values
times = df.index
scores = score.values
for i in range(len(df)):
t = times[i]
c = close_arr[i]
h = high_arr[i]
lo = low_arr[i]
s = scores[i]
today = t.date()
# --- 日切换 ---
if today != current_date:
if current_date is not None:
daily_pnl[current_date] = day_pnl
current_date = today
day_pnl = 0.0
day_stopped = False
arrived = []
remaining = []
for rd, ra in pending_rebates:
if today >= rd:
arrived.append(ra)
else:
remaining.append((rd, ra))
if arrived:
capital += sum(arrived)
pending_rebates = remaining
if day_stopped:
for pos in positions:
pos.hold_bars += 1
continue
# --- 正常止损止盈逻辑 ---
closed_indices = []
for pi, pos in enumerate(positions):
pos.hold_bars += 1
qty = self.notional / pos.entry_price
if pos.direction == 1:
sl_price = pos.entry_price * (1 - self.sl_pct)
tp_price = pos.entry_price * (1 + self.tp_pct)
hit_sl = lo <= sl_price
hit_tp = h >= tp_price
else:
sl_price = pos.entry_price * (1 + self.sl_pct)
tp_price = pos.entry_price * (1 - self.tp_pct)
hit_sl = h >= sl_price
hit_tp = lo <= tp_price
should_close = False
exit_price = c
# 止损始终生效(不受持仓时间限制)
if hit_sl:
should_close = True
exit_price = sl_price
elif pos.hold_bars >= self.min_hold_bars:
# 止盈和信号反转需要满足最小持仓时间
if hit_tp:
should_close = True
exit_price = tp_price
elif (pos.direction == 1 and s < -open_threshold) or \
(pos.direction == -1 and s > open_threshold):
should_close = True
exit_price = c
if should_close:
net = self._close_position(pos, exit_price, t, today, trades, pending_rebates)
capital += net
used_margin -= self.margin
day_pnl += net
closed_indices.append(pi)
# 每笔平仓后立即检查日回撤
if day_pnl < -self.max_daily_dd:
# 熔断剩余持仓
for pj, pos2 in enumerate(positions):
if pj not in closed_indices:
pos2.hold_bars += 1
net2 = self._close_position(pos2, c, t, today, trades, pending_rebates)
capital += net2
used_margin -= self.margin
day_pnl += net2
closed_indices.append(pj)
day_stopped = True
break
for pi in sorted(set(closed_indices), reverse=True):
positions.pop(pi)
if day_stopped:
continue
# --- 开仓 ---
if len(positions) < self.max_positions:
if np.isnan(s):
continue
# 开仓前检查: 当前所有持仓 + 新仓同时止损的最大亏损
n_after = len(positions) + 1
worst_total_sl = n_after * (self.notional * self.sl_pct + self.notional * self.fee_rate * 2)
if day_pnl - worst_total_sl < -self.max_daily_dd:
continue # 风险敞口太大
open_fee = self.notional * self.fee_rate
if capital - used_margin < self.margin + open_fee:
continue
new_dir = 0
if s > open_threshold:
new_dir = 1
elif s < -open_threshold:
new_dir = -1
if new_dir != 0:
positions.append(OpenPosition(
direction=new_dir, entry_price=c,
entry_time=t, hold_bars=0,
))
capital -= open_fee
used_margin += self.margin
day_pnl -= open_fee
# 最后一天
if current_date is not None:
daily_pnl[current_date] = day_pnl
# 强制平仓
if positions and len(df) > 0:
last_close = close_arr[-1]
for pos in positions:
qty = self.notional / pos.entry_price
if pos.direction == 1:
raw_pnl = qty * (last_close - pos.entry_price)
else:
raw_pnl = qty * (pos.entry_price - last_close)
fee = self.notional * self.fee_rate
net_pnl = raw_pnl - fee
capital += net_pnl
trades.append(Trade(
entry_time=pos.entry_time, exit_time=times[-1],
direction=pos.direction, entry_price=pos.entry_price,
exit_price=last_close, pnl=net_pnl,
fee=self.notional * self.fee_rate * 2,
rebate=0, holding_bars=pos.hold_bars,
))
remaining_rebate = sum(amt for _, amt in pending_rebates)
capital += remaining_rebate
return self._build_result(trades, daily_pnl, capital)
def _build_result(self, trades, daily_pnl, final_capital):
if not trades:
return {
'total_pnl': 0, 'final_capital': final_capital,
'num_trades': 0, 'win_rate': 0, 'avg_pnl': 0,
'max_daily_dd': 0, 'avg_daily_pnl': 0,
'profit_factor': 0, 'trades': [], 'daily_pnl': daily_pnl,
'total_fee': 0, 'total_rebate': 0,
}
pnls = [t.pnl for t in trades]
wins = [p for p in pnls if p > 0]
losses = [p for p in pnls if p <= 0]
daily_vals = list(daily_pnl.values())
total_fee = sum(t.fee for t in trades)
total_rebate = sum(t.rebate for t in trades)
gross_profit = sum(wins) if wins else 0
gross_loss = abs(sum(losses)) if losses else 1e-10
return {
'total_pnl': sum(pnls) + total_rebate,
'final_capital': final_capital,
'num_trades': len(trades),
'win_rate': len(wins) / len(trades) if trades else 0,
'avg_pnl': np.mean(pnls),
'max_daily_dd': min(daily_vals) if daily_vals else 0,
'avg_daily_pnl': np.mean(daily_vals) if daily_vals else 0,
'profit_factor': gross_profit / gross_loss,
'total_fee': total_fee,
'total_rebate': total_rebate,
'trades': trades,
'daily_pnl': daily_pnl,
}

View File

@@ -0,0 +1,53 @@
{
"bb_period": 36,
"bb_std": 3.3000000000000003,
"kc_period": 24,
"kc_mult": 1.3,
"dc_period": 41,
"ema_fast": 3,
"ema_slow": 15,
"macd_fast": 9,
"macd_slow": 34,
"macd_signal": 15,
"adx_period": 16,
"st_period": 5,
"st_mult": 1.4,
"rsi_period": 7,
"stoch_k": 18,
"stoch_d": 6,
"stoch_smooth": 3,
"cci_period": 12,
"wr_period": 9,
"wma_period": 47,
"bb_oversold": -0.19999999999999998,
"bb_overbought": 1.3,
"kc_oversold": 0.2,
"kc_overbought": 0.75,
"dc_oversold": 0.05,
"dc_overbought": 0.75,
"adx_threshold": 15.0,
"rsi_overbought": 70.0,
"rsi_oversold": 18.0,
"stoch_overbought": 89.0,
"stoch_oversold": 10.0,
"cci_overbought": 80.0,
"cci_oversold": -140.0,
"wr_overbought": -28.0,
"wr_oversold": -90.0,
"w_bb": 0.15000000000000002,
"w_kc": 0.4,
"w_dc": 0.0,
"w_ema": 0.8500000000000001,
"w_macd": 0.35000000000000003,
"w_adx": 0.0,
"w_st": 0.15000000000000002,
"w_rsi": 0.4,
"w_stoch": 0.15000000000000002,
"w_cci": 0.1,
"w_wr": 0.0,
"w_wma": 0.4,
"open_threshold": 0.22,
"max_positions": 3,
"take_profit_pct": 0.024999999999999998,
"stop_loss_pct": 0.008
}

View File

@@ -1,53 +1,69 @@
from __future__ import annotations """
数据加载模块 - 从 SQLite 加载多周期K线数据为 DataFrame
from dataclasses import dataclass """
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
import pandas as pd import pandas as pd
import sqlite3 from peewee import SqliteDatabase
from pathlib import Path
DB_PATH = Path(__file__).parent.parent / 'models' / 'database.db'
# 周期 -> 表名
PERIOD_MAP = {
'1m': 'bitmart_eth_1m',
'3m': 'bitmart_eth_3m',
'5m': 'bitmart_eth_5m',
'15m': 'bitmart_eth_15m',
'30m': 'bitmart_eth_30m',
'1h': 'bitmart_eth_1h',
}
@dataclass(frozen=True) def load_klines(period: str, start_date: str, end_date: str) -> pd.DataFrame:
class KlineSource: """
db_path: Path 加载指定周期、指定日期范围的K线数据
table_name: str :param period: '1m','3m','5m','15m','30m','1h'
:param start_date: 'YYYY-MM-DD'
:param end_date: 'YYYY-MM-DD' (不包含该日)
:return: DataFrame with columns: datetime, open, high, low, close
"""
table = PERIOD_MAP.get(period)
if not table:
raise ValueError(f"不支持的周期: {period}, 可选: {list(PERIOD_MAP.keys())}")
start_ts = int(pd.Timestamp(start_date).timestamp() * 1000)
end_ts = int(pd.Timestamp(end_date).timestamp() * 1000)
def _to_ms(dt: datetime) -> int: db = SqliteDatabase(str(DB_PATH))
if dt.tzinfo is None: db.connect()
dt = dt.replace(tzinfo=timezone.utc) cursor = db.execute_sql(
return int(dt.timestamp() * 1000) f'SELECT id, open, high, low, close FROM [{table}] '
f'WHERE id >= ? AND id < ? ORDER BY id',
(start_ts, end_ts)
)
rows = cursor.fetchall()
db.close()
df = pd.DataFrame(rows, columns=['timestamp_ms', 'open', 'high', 'low', 'close'])
def load_klines( df['datetime'] = pd.to_datetime(df['timestamp_ms'], unit='ms')
source: KlineSource, df.set_index('datetime', inplace=True)
start: datetime, df.drop(columns=['timestamp_ms'], inplace=True)
end: datetime, df = df.astype(float)
) -> pd.DataFrame:
start_ms = _to_ms(start)
end_ms = _to_ms(end)
con = sqlite3.connect(str(source.db_path))
try:
df = pd.read_sql_query(
f"SELECT id, open, high, low, close FROM {source.table_name} WHERE id >= ? AND id <= ? ORDER BY id ASC",
con,
params=(start_ms, end_ms),
)
finally:
con.close()
if df.empty:
return df
df["timestamp_ms"] = df["id"].astype("int64")
df["dt"] = pd.to_datetime(df["timestamp_ms"], unit="ms", utc=True)
df = df.drop(columns=["id"]).set_index("dt")
for c in ("open", "high", "low", "close"):
df[c] = pd.to_numeric(df[c], errors="coerce")
df = df.dropna(subset=["open", "high", "low", "close"])
return df return df
def load_multi_period(periods: list, start_date: str, end_date: str) -> dict:
"""
加载多个周期的数据
:return: {period: DataFrame}
"""
result = {}
for p in periods:
result[p] = load_klines(p, start_date, end_date)
print(f" 加载 {p}: {len(result[p])} 条 ({start_date} ~ {end_date})")
return result
if __name__ == '__main__':
data = load_multi_period(['5m', '15m', '1h'], '2020-01-01', '2024-01-01')
for k, v in data.items():
print(f"{k}: {v.shape}, {v.index[0]} ~ {v.index[-1]}")

125
strategy/strategy_signal.py Normal file
View File

@@ -0,0 +1,125 @@
"""
信号生成模块 - 多指标加权投票 + 多时间框架过滤
"""
import numpy as np
import pandas as pd
def generate_indicator_signals(df: pd.DataFrame, params: dict) -> pd.DataFrame:
"""
根据指标值生成每个指标的独立信号 (+1 做多 / -1 做空 / 0 中性)
df 必须已经包含 compute_all_indicators 计算出的列
"""
out = df.copy()
# --- 布林带 %B ---
out['sig_bb'] = 0
out.loc[out['bb_pct'] < params.get('bb_oversold', 0.0), 'sig_bb'] = 1
out.loc[out['bb_pct'] > params.get('bb_overbought', 1.0), 'sig_bb'] = -1
# --- 肯特纳通道 ---
out['sig_kc'] = 0
out.loc[out['kc_pct'] < params.get('kc_oversold', 0.0), 'sig_kc'] = 1
out.loc[out['kc_pct'] > params.get('kc_overbought', 1.0), 'sig_kc'] = -1
# --- 唐奇安通道 ---
out['sig_dc'] = 0
out.loc[out['dc_pct'] < params.get('dc_oversold', 0.2), 'sig_dc'] = 1
out.loc[out['dc_pct'] > params.get('dc_overbought', 0.8), 'sig_dc'] = -1
# --- EMA 交叉 ---
out['sig_ema'] = 0
out.loc[out['ema_diff'] > 0, 'sig_ema'] = 1
out.loc[out['ema_diff'] < 0, 'sig_ema'] = -1
# --- MACD ---
out['sig_macd'] = 0
out.loc[out['macd_hist'] > 0, 'sig_macd'] = 1
out.loc[out['macd_hist'] < 0, 'sig_macd'] = -1
# --- ADX + DI ---
adx_thresh = params.get('adx_threshold', 25)
out['sig_adx'] = 0
out.loc[(out['adx'] > adx_thresh) & (out['di_diff'] > 0), 'sig_adx'] = 1
out.loc[(out['adx'] > adx_thresh) & (out['di_diff'] < 0), 'sig_adx'] = -1
# --- Supertrend ---
out['sig_st'] = out['st_dir']
# --- RSI ---
rsi_ob = params.get('rsi_overbought', 70)
rsi_os = params.get('rsi_oversold', 30)
out['sig_rsi'] = 0
out.loc[out['rsi'] < rsi_os, 'sig_rsi'] = 1
out.loc[out['rsi'] > rsi_ob, 'sig_rsi'] = -1
# --- Stochastic ---
stoch_ob = params.get('stoch_overbought', 80)
stoch_os = params.get('stoch_oversold', 20)
out['sig_stoch'] = 0
out.loc[(out['stoch_k'] < stoch_os) & (out['stoch_k'] > out['stoch_d']), 'sig_stoch'] = 1
out.loc[(out['stoch_k'] > stoch_ob) & (out['stoch_k'] < out['stoch_d']), 'sig_stoch'] = -1
# --- CCI ---
cci_ob = params.get('cci_overbought', 100)
cci_os = params.get('cci_oversold', -100)
out['sig_cci'] = 0
out.loc[out['cci'] < cci_os, 'sig_cci'] = 1
out.loc[out['cci'] > cci_ob, 'sig_cci'] = -1
# --- Williams %R ---
wr_ob = params.get('wr_overbought', -20)
wr_os = params.get('wr_oversold', -80)
out['sig_wr'] = 0
out.loc[out['wr'] < wr_os, 'sig_wr'] = 1
out.loc[out['wr'] > wr_ob, 'sig_wr'] = -1
# --- WMA ---
out['sig_wma'] = 0
out.loc[out['wma_diff'] > 0, 'sig_wma'] = 1
out.loc[out['wma_diff'] < 0, 'sig_wma'] = -1
return out
SIGNAL_COLS = [
'sig_bb', 'sig_kc', 'sig_dc', 'sig_ema', 'sig_macd',
'sig_adx', 'sig_st', 'sig_rsi', 'sig_stoch', 'sig_cci',
'sig_wr', 'sig_wma',
]
WEIGHT_KEYS = [
'w_bb', 'w_kc', 'w_dc', 'w_ema', 'w_macd',
'w_adx', 'w_st', 'w_rsi', 'w_stoch', 'w_cci',
'w_wr', 'w_wma',
]
def compute_composite_score(df: pd.DataFrame, params: dict) -> pd.Series:
"""
加权投票计算综合得分 (-1 ~ +1)
"""
weights = np.array([params.get(k, 1.0) for k in WEIGHT_KEYS])
total_w = weights.sum()
if total_w == 0:
total_w = 1.0
signals = df[SIGNAL_COLS].values # (N, 12)
score = (signals * weights).sum(axis=1) / total_w
return pd.Series(score, index=df.index, name='score')
def apply_htf_filter(score: pd.Series, htf_df: pd.DataFrame, params: dict) -> pd.Series:
"""
用高时间框架如1h的趋势方向过滤信号
htf_df 需要包含 'ema_diff'
只允许与大趋势同向的信号通过
"""
# 将 htf 的 ema_diff reindex 到主时间框架
htf_trend = htf_df['ema_diff'].reindex(score.index, method='ffill')
filtered = score.copy()
# 大趋势向上时,屏蔽做空信号
filtered.loc[(htf_trend > 0) & (filtered < 0)] = 0
# 大趋势向下时,屏蔽做多信号
filtered.loc[(htf_trend < 0) & (filtered > 0)] = 0
return filtered

226
strategy/train.py Normal file
View File

@@ -0,0 +1,226 @@
"""
Optuna 训练入口 - 在 2020-2022 数据上搜索最优参数
"""
import json
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import optuna
from optuna.samplers import TPESampler
import numpy as np
from strategy.data_loader import load_klines
from strategy.indicators import compute_all_indicators
from strategy.strategy_signal import (
generate_indicator_signals, compute_composite_score,
apply_htf_filter, WEIGHT_KEYS,
)
from strategy.backtest_engine import BacktestEngine
# ============================================================
# 全局加载数据 (只加载一次)
# ============================================================
print("正在加载 2020-2022 训练数据...")
DF_5M = load_klines('5m', '2020-01-01', '2023-01-01')
DF_1H = load_klines('1h', '2020-01-01', '2023-01-01')
print(f" 5m: {len(DF_5M)} 条, 1h: {len(DF_1H)}")
print("数据加载完成。\n")
def build_params(trial: optuna.Trial) -> dict:
"""从 Optuna trial 构建完整参数字典"""
p = {}
# --- 指标参数 ---
p['bb_period'] = trial.suggest_int('bb_period', 10, 50)
p['bb_std'] = trial.suggest_float('bb_std', 1.0, 3.5, step=0.1)
p['kc_period'] = trial.suggest_int('kc_period', 10, 50)
p['kc_mult'] = trial.suggest_float('kc_mult', 0.5, 3.0, step=0.1)
p['dc_period'] = trial.suggest_int('dc_period', 10, 50)
p['ema_fast'] = trial.suggest_int('ema_fast', 3, 20)
p['ema_slow'] = trial.suggest_int('ema_slow', 15, 60)
p['macd_fast'] = trial.suggest_int('macd_fast', 6, 20)
p['macd_slow'] = trial.suggest_int('macd_slow', 18, 40)
p['macd_signal'] = trial.suggest_int('macd_signal', 5, 15)
p['adx_period'] = trial.suggest_int('adx_period', 7, 30)
p['st_period'] = trial.suggest_int('st_period', 5, 20)
p['st_mult'] = trial.suggest_float('st_mult', 1.0, 5.0, step=0.1)
p['rsi_period'] = trial.suggest_int('rsi_period', 7, 28)
p['stoch_k'] = trial.suggest_int('stoch_k', 5, 21)
p['stoch_d'] = trial.suggest_int('stoch_d', 2, 7)
p['stoch_smooth'] = trial.suggest_int('stoch_smooth', 2, 7)
p['cci_period'] = trial.suggest_int('cci_period', 10, 40)
p['wr_period'] = trial.suggest_int('wr_period', 7, 28)
p['wma_period'] = trial.suggest_int('wma_period', 10, 50)
# --- 信号阈值参数 ---
p['bb_oversold'] = trial.suggest_float('bb_oversold', -0.3, 0.3, step=0.05)
p['bb_overbought'] = trial.suggest_float('bb_overbought', 0.7, 1.3, step=0.05)
p['kc_oversold'] = trial.suggest_float('kc_oversold', -0.3, 0.3, step=0.05)
p['kc_overbought'] = trial.suggest_float('kc_overbought', 0.7, 1.3, step=0.05)
p['dc_oversold'] = trial.suggest_float('dc_oversold', 0.0, 0.3, step=0.05)
p['dc_overbought'] = trial.suggest_float('dc_overbought', 0.7, 1.0, step=0.05)
p['adx_threshold'] = trial.suggest_float('adx_threshold', 15, 35, step=1)
p['rsi_overbought'] = trial.suggest_float('rsi_overbought', 60, 85, step=1)
p['rsi_oversold'] = trial.suggest_float('rsi_oversold', 15, 40, step=1)
p['stoch_overbought'] = trial.suggest_float('stoch_overbought', 70, 90, step=1)
p['stoch_oversold'] = trial.suggest_float('stoch_oversold', 10, 30, step=1)
p['cci_overbought'] = trial.suggest_float('cci_overbought', 80, 200, step=5)
p['cci_oversold'] = trial.suggest_float('cci_oversold', -200, -80, step=5)
p['wr_overbought'] = trial.suggest_float('wr_overbought', -30, -10, step=1)
p['wr_oversold'] = trial.suggest_float('wr_oversold', -90, -70, step=1)
# --- 权重 ---
for wk in WEIGHT_KEYS:
p[wk] = trial.suggest_float(wk, 0.0, 1.0, step=0.05)
# --- 回测参数 ---
p['open_threshold'] = trial.suggest_float('open_threshold', 0.1, 0.6, step=0.02)
p['max_positions'] = trial.suggest_int('max_positions', 1, 3)
p['take_profit_pct'] = trial.suggest_float('take_profit_pct', 0.003, 0.025, step=0.001)
# 止损约束: N单同时止损 + 手续费 <= 50U
# N * 1250 * sl_pct + N * 1.25 <= 50
# sl_pct <= (50 - N*1.25) / (N*1250)
n = p['max_positions']
max_sl = (50.0 - n * 1.25) / (n * 1250.0)
max_sl = round(max(max_sl, 0.002), 3) # 至少 0.2%
p['stop_loss_pct'] = trial.suggest_float('stop_loss_pct', 0.002, max_sl, step=0.001)
return p
def objective(trial: optuna.Trial) -> float:
params = build_params(trial)
# 确保 ema_slow > ema_fast, macd_slow > macd_fast
if params['ema_slow'] <= params['ema_fast']:
return -1e6
if params['macd_slow'] <= params['macd_fast']:
return -1e6
try:
# 计算指标
df_5m = compute_all_indicators(DF_5M, params)
df_1h = compute_all_indicators(DF_1H, params)
# 生成信号
df_5m = generate_indicator_signals(df_5m, params)
df_1h = generate_indicator_signals(df_1h, params)
# 综合得分
score = compute_composite_score(df_5m, params)
# 高时间框架过滤
score = apply_htf_filter(score, df_1h, params)
# 回测
engine = BacktestEngine(
initial_capital=1000.0,
margin_per_trade=25.0,
leverage=50,
fee_rate=0.0005,
rebate_ratio=0.70,
max_daily_drawdown=50.0,
min_hold_bars=1,
stop_loss_pct=params['stop_loss_pct'],
take_profit_pct=params['take_profit_pct'],
max_positions=params['max_positions'],
)
result = engine.run(df_5m, score, open_threshold=params['open_threshold'])
num_trades = result['num_trades']
if num_trades < 50:
return -1e6 # 交易次数太少,不可靠
total_pnl = result['total_pnl']
max_dd = result['max_daily_dd'] # 负数 (引擎已保证 >= -50)
avg_daily = result['avg_daily_pnl']
# 引擎内部已经有每日 50U 回撤熔断,这里不再硬约束
# 目标: 最大化总收益
score_val = total_pnl
# 奖励日均收益高的方案
if avg_daily >= 50:
score_val *= 1.3
elif avg_daily >= 30:
score_val *= 1.15
trial.set_user_attr('total_pnl', total_pnl)
trial.set_user_attr('num_trades', num_trades)
trial.set_user_attr('win_rate', result['win_rate'])
trial.set_user_attr('max_daily_dd', max_dd)
trial.set_user_attr('avg_daily_pnl', avg_daily)
trial.set_user_attr('profit_factor', result['profit_factor'])
return score_val
except Exception as e:
print(f"Trial {trial.number} 异常: {e}")
return -1e6
def main():
study = optuna.create_study(
direction='maximize',
sampler=TPESampler(seed=42, n_startup_trials=30),
study_name='eth_strategy_v1',
)
# 设置日志级别
optuna.logging.set_verbosity(optuna.logging.WARNING)
n_trials = 1000
print(f"开始 Optuna 优化, 共 {n_trials} 次试验 (多单并发版)...")
print("=" * 60)
def callback(study, trial):
if trial.number % 10 == 0:
best = study.best_trial
print(f"[Trial {trial.number:>4d}] "
f"当前值={trial.value:.2f} | "
f"最佳值={best.value:.2f} | "
f"PnL={best.user_attrs.get('total_pnl', 0):.1f}U | "
f"胜率={best.user_attrs.get('win_rate', 0):.1%} | "
f"日均={best.user_attrs.get('avg_daily_pnl', 0):.1f}U | "
f"最大日回撤={best.user_attrs.get('max_daily_dd', 0):.1f}U")
study.optimize(objective, n_trials=n_trials, callbacks=[callback], show_progress_bar=True)
# 输出最佳结果
best = study.best_trial
print("\n" + "=" * 60)
print("训练完成!最佳参数:")
print("=" * 60)
print(f" 目标值: {best.value:.4f}")
print(f" 总收益: {best.user_attrs.get('total_pnl', 0):.2f}U")
print(f" 交易次数: {best.user_attrs.get('num_trades', 0)}")
print(f" 胜率: {best.user_attrs.get('win_rate', 0):.2%}")
print(f" 日均收益: {best.user_attrs.get('avg_daily_pnl', 0):.2f}U")
print(f" 最大日回撤: {best.user_attrs.get('max_daily_dd', 0):.2f}U")
print(f" 盈亏比: {best.user_attrs.get('profit_factor', 0):.2f}")
# 保存最佳参数
output_path = os.path.join(os.path.dirname(__file__), 'best_params_2020_2022.json')
with open(output_path, 'w') as f:
json.dump(best.params, f, indent=2, ensure_ascii=False)
print(f"\n最佳参数已保存到: {output_path}")
if __name__ == '__main__':
main()