back-trader-python 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. back_trader_python-1.4.0.dist-info/METADATA +1491 -0
  2. back_trader_python-1.4.0.dist-info/RECORD +465 -0
  3. back_trader_python-1.4.0.dist-info/WHEEL +5 -0
  4. back_trader_python-1.4.0.dist-info/licenses/LICENSE +674 -0
  5. back_trader_python-1.4.0.dist-info/top_level.txt +1 -0
  6. backtrader/__init__.py +148 -0
  7. backtrader/_cerebro/__init__.py +5 -0
  8. backtrader/_cerebro/channel.py +382 -0
  9. backtrader/_cerebro/execution.py +377 -0
  10. backtrader/_cerebro/lifecycle.py +143 -0
  11. backtrader/_cerebro/notifications.py +150 -0
  12. backtrader/_cerebro/presentation.py +230 -0
  13. backtrader/_cerebro/registry.py +593 -0
  14. backtrader/_cerebro/runnext.py +551 -0
  15. backtrader/_cerebro/runonce.py +142 -0
  16. backtrader/analyzer.py +594 -0
  17. backtrader/analyzers/__init__.py +50 -0
  18. backtrader/analyzers/annualreturn.py +226 -0
  19. backtrader/analyzers/calmar.py +165 -0
  20. backtrader/analyzers/drawdown.py +287 -0
  21. backtrader/analyzers/leverage.py +112 -0
  22. backtrader/analyzers/logreturnsrolling.py +190 -0
  23. backtrader/analyzers/periodstats.py +153 -0
  24. backtrader/analyzers/positions.py +119 -0
  25. backtrader/analyzers/pyfolio.py +470 -0
  26. backtrader/analyzers/returns.py +192 -0
  27. backtrader/analyzers/sharpe.py +307 -0
  28. backtrader/analyzers/sharpe_ratio_stats.py +534 -0
  29. backtrader/analyzers/sqn.py +112 -0
  30. backtrader/analyzers/timereturn.py +192 -0
  31. backtrader/analyzers/total_value.py +75 -0
  32. backtrader/analyzers/tradeanalyzer.py +278 -0
  33. backtrader/analyzers/transactions.py +141 -0
  34. backtrader/analyzers/vwr.py +245 -0
  35. backtrader/bokeh/__init__.py +155 -0
  36. backtrader/bokeh/analyzers/__init__.py +13 -0
  37. backtrader/bokeh/analyzers/plot.py +192 -0
  38. backtrader/bokeh/analyzers/recorder.py +181 -0
  39. backtrader/bokeh/app.py +1094 -0
  40. backtrader/bokeh/live/__init__.py +11 -0
  41. backtrader/bokeh/live/client.py +352 -0
  42. backtrader/bokeh/live/datahandler.py +346 -0
  43. backtrader/bokeh/plot_adapter.py +200 -0
  44. backtrader/bokeh/schemes/__init__.py +14 -0
  45. backtrader/bokeh/schemes/blackly.py +76 -0
  46. backtrader/bokeh/schemes/scheme.py +150 -0
  47. backtrader/bokeh/schemes/tradimo.py +82 -0
  48. backtrader/bokeh/tab.py +125 -0
  49. backtrader/bokeh/tabs/__init__.py +30 -0
  50. backtrader/bokeh/tabs/analyzer.py +120 -0
  51. backtrader/bokeh/tabs/config.py +154 -0
  52. backtrader/bokeh/tabs/live.py +109 -0
  53. backtrader/bokeh/tabs/log.py +185 -0
  54. backtrader/bokeh/tabs/metadata.py +182 -0
  55. backtrader/bokeh/tabs/performance.py +359 -0
  56. backtrader/bokeh/tabs/source.py +70 -0
  57. backtrader/bokeh/utils/__init__.py +8 -0
  58. backtrader/bokeh/utils/helpers.py +167 -0
  59. backtrader/bokeh/webapp.py +164 -0
  60. backtrader/broker.py +478 -0
  61. backtrader/brokers/__init__.py +36 -0
  62. backtrader/brokers/bbroker.py +2576 -0
  63. backtrader/brokers/btapibroker.py +8227 -0
  64. backtrader/brokers/hft/__init__.py +89 -0
  65. backtrader/brokers/hft/binance_bbo.py +625 -0
  66. backtrader/brokers/hft/binance_bbo_compare.py +1398 -0
  67. backtrader/brokers/hft/examples.py +1228 -0
  68. backtrader/brokers/hft/exchange.py +380 -0
  69. backtrader/brokers/hft/latency.py +309 -0
  70. backtrader/brokers/hft/matching_core.py +572 -0
  71. backtrader/brokers/hft/queue.py +238 -0
  72. backtrader/brokers/hft/recorder.py +88 -0
  73. backtrader/brokers/hft/state.py +138 -0
  74. backtrader/brokers/impact_models.py +118 -0
  75. backtrader/brokers/mixbroker.py +895 -0
  76. backtrader/brokers/tickbroker.py +1991 -0
  77. backtrader/btrun/__init__.py +12 -0
  78. backtrader/btrun/btrun.py +1218 -0
  79. backtrader/cerebro.py +828 -0
  80. backtrader/channel.py +682 -0
  81. backtrader/channels/__init__.py +23 -0
  82. backtrader/channels/bridge.py +186 -0
  83. backtrader/channels/funding.py +248 -0
  84. backtrader/channels/live_queue.py +216 -0
  85. backtrader/channels/live_validator.py +294 -0
  86. backtrader/channels/orderbook.py +257 -0
  87. backtrader/channels/tick.py +202 -0
  88. backtrader/comminfo.py +665 -0
  89. backtrader/commissions/__init__.py +106 -0
  90. backtrader/commissions/ctpoption.py +993 -0
  91. backtrader/configs/account_config_example.yaml +8 -0
  92. backtrader/dataseries.py +379 -0
  93. backtrader/errors.py +106 -0
  94. backtrader/events.py +980 -0
  95. backtrader/feed.py +1523 -0
  96. backtrader/feeds/__init__.py +75 -0
  97. backtrader/feeds/barrier.py +2006 -0
  98. backtrader/feeds/blaze.py +118 -0
  99. backtrader/feeds/btapifeed.py +1538 -0
  100. backtrader/feeds/btcsv.py +203 -0
  101. backtrader/feeds/chainer.py +114 -0
  102. backtrader/feeds/cryptohftdata.py +164 -0
  103. backtrader/feeds/csvgeneric.py +1205 -0
  104. backtrader/feeds/ctpcohort.py +1051 -0
  105. backtrader/feeds/influxfeed.py +158 -0
  106. backtrader/feeds/livefeed.py +71 -0
  107. backtrader/feeds/mixed_channel.py +108 -0
  108. backtrader/feeds/mt4csv.py +42 -0
  109. backtrader/feeds/pandafeed.py +381 -0
  110. backtrader/feeds/quandl.py +256 -0
  111. backtrader/feeds/rollover.py +229 -0
  112. backtrader/feeds/sierrachart.py +30 -0
  113. backtrader/feeds/vchart.py +162 -0
  114. backtrader/feeds/vchartcsv.py +84 -0
  115. backtrader/feeds/vchartfile.py +153 -0
  116. backtrader/feeds/yahoo.py +399 -0
  117. backtrader/fillers.py +148 -0
  118. backtrader/filters/__init__.py +34 -0
  119. backtrader/filters/bsplitter.py +127 -0
  120. backtrader/filters/calendardays.py +121 -0
  121. backtrader/filters/datafiller.py +192 -0
  122. backtrader/filters/datafilter.py +74 -0
  123. backtrader/filters/daysteps.py +96 -0
  124. backtrader/filters/heikinashi.py +63 -0
  125. backtrader/filters/renko.py +164 -0
  126. backtrader/filters/session.py +289 -0
  127. backtrader/flt.py +80 -0
  128. backtrader/functions.py +960 -0
  129. backtrader/indicator.py +449 -0
  130. backtrader/indicators/__init__.py +148 -0
  131. backtrader/indicators/accdecoscillator.py +110 -0
  132. backtrader/indicators/aroon.py +300 -0
  133. backtrader/indicators/atr.py +315 -0
  134. backtrader/indicators/awesomeoscillator.py +122 -0
  135. backtrader/indicators/basicops.py +834 -0
  136. backtrader/indicators/bollinger.py +223 -0
  137. backtrader/indicators/cci.py +89 -0
  138. backtrader/indicators/channels_ext.py +83 -0
  139. backtrader/indicators/contrib/__init__.py +228 -0
  140. backtrader/indicators/contrib/absolutely_no_lag_lwma.py +28 -0
  141. backtrader/indicators/contrib/absolutely_no_lag_lwma_color.py +44 -0
  142. backtrader/indicators/contrib/accumulation_distribution_line.py +92 -0
  143. backtrader/indicators/contrib/adx_cross_hull_style_indicator.py +249 -0
  144. backtrader/indicators/contrib/adxdmi.py +34 -0
  145. backtrader/indicators/contrib/ai_acceleration_deceleration_oscillator.py +34 -0
  146. backtrader/indicators/contrib/altr_trend_signal_v22.py +85 -0
  147. backtrader/indicators/contrib/anchored_momentum_line.py +115 -0
  148. backtrader/indicators/contrib/any_range_cld_tail_indicator.py +82 -0
  149. backtrader/indicators/contrib/aroon_horn_sign_indicator.py +96 -0
  150. backtrader/indicators/contrib/aroon_oscillator_sign_alert.py +50 -0
  151. backtrader/indicators/contrib/arrows_curves_indicator.py +112 -0
  152. backtrader/indicators/contrib/as_ctrend_indicator.py +143 -0
  153. backtrader/indicators/contrib/asimmetric_stoch_nr_indicator.py +187 -0
  154. backtrader/indicators/contrib/atr_normalize_histogram.py +118 -0
  155. backtrader/indicators/contrib/average_change_candle.py +165 -0
  156. backtrader/indicators/contrib/bb_squeeze_indicator.py +60 -0
  157. backtrader/indicators/contrib/bezier_st_dev_indicator.py +135 -0
  158. backtrader/indicators/contrib/binary_wave_indicator.py +233 -0
  159. backtrader/indicators/contrib/blau_c_momentum_indicator.py +123 -0
  160. backtrader/indicators/contrib/blau_cmi_indicator.py +141 -0
  161. backtrader/indicators/contrib/blau_csi.py +76 -0
  162. backtrader/indicators/contrib/blau_ergodic.py +53 -0
  163. backtrader/indicators/contrib/blau_t_stoch_i.py +72 -0
  164. backtrader/indicators/contrib/blau_ts_stochastic.py +85 -0
  165. backtrader/indicators/contrib/blau_tvi.py +55 -0
  166. backtrader/indicators/contrib/brain_trend2_indicator.py +128 -0
  167. backtrader/indicators/contrib/brain_trend_signal_proxy.py +47 -0
  168. backtrader/indicators/contrib/brake_parb_indicator.py +85 -0
  169. backtrader/indicators/contrib/breakout_bars_trend_v2.py +121 -0
  170. backtrader/indicators/contrib/bsi_indicator.py +87 -0
  171. backtrader/indicators/contrib/bulls_bears_eyes.py +67 -0
  172. backtrader/indicators/contrib/bulls_power.py +56 -0
  173. backtrader/indicators/contrib/bw_wise_man1_signal.py +102 -0
  174. backtrader/indicators/contrib/bykov_trend_indicator.py +85 -0
  175. backtrader/indicators/contrib/candle_stop_color.py +46 -0
  176. backtrader/indicators/contrib/candles_x_smoothed_indicator.py +69 -0
  177. backtrader/indicators/contrib/candlesticks_bw.py +45 -0
  178. backtrader/indicators/contrib/caudate_x_period_candle_color.py +56 -0
  179. backtrader/indicators/contrib/cci_histogram_indicator.py +53 -0
  180. backtrader/indicators/contrib/cci_woodies_indicator.py +80 -0
  181. backtrader/indicators/contrib/center_of_gravity_candle_indicator.py +83 -0
  182. backtrader/indicators/contrib/center_of_gravity_indicator.py +70 -0
  183. backtrader/indicators/contrib/cg_oscillator.py +40 -0
  184. backtrader/indicators/contrib/close_line_cci.py +38 -0
  185. backtrader/indicators/contrib/close_price_fractals.py +47 -0
  186. backtrader/indicators/contrib/color3rd_gen_xma_indicator.py +122 -0
  187. backtrader/indicators/contrib/color_bb_candles_indicator.py +108 -0
  188. backtrader/indicators/contrib/color_coppock_indicator.py +157 -0
  189. backtrader/indicators/contrib/color_hma.py +71 -0
  190. backtrader/indicators/contrib/color_j_variation_indicator.py +53 -0
  191. backtrader/indicators/contrib/color_metro_de_marker_indicator.py +78 -0
  192. backtrader/indicators/contrib/color_metro_stochastic_indicator.py +93 -0
  193. backtrader/indicators/contrib/color_metro_wpr_indicator.py +85 -0
  194. backtrader/indicators/contrib/color_schaff_de_marker_trend_cycle.py +92 -0
  195. backtrader/indicators/contrib/color_schaff_trend_cycle_indicator.py +203 -0
  196. backtrader/indicators/contrib/color_step_xccx_indicator.py +193 -0
  197. backtrader/indicators/contrib/color_x2_ma.py +49 -0
  198. backtrader/indicators/contrib/color_x_derivative.py +63 -0
  199. backtrader/indicators/contrib/color_zerolag_de_marker.py +84 -0
  200. backtrader/indicators/contrib/corrected_average_indicator.py +127 -0
  201. backtrader/indicators/contrib/darvas_boxes_system.py +73 -0
  202. backtrader/indicators/contrib/dema_range_channel_color.py +42 -0
  203. backtrader/indicators/contrib/derivative_indicator.py +95 -0
  204. backtrader/indicators/contrib/digital_ft01_indicator.py +112 -0
  205. backtrader/indicators/contrib/digital_macd.py +200 -0
  206. backtrader/indicators/contrib/donchian_channels_system.py +45 -0
  207. backtrader/indicators/contrib/dots_indicator.py +93 -0
  208. backtrader/indicators/contrib/ef_distance_indicator.py +82 -0
  209. backtrader/indicators/contrib/ema_rsi_va.py +80 -0
  210. backtrader/indicators/contrib/envelopes_jp_alonso.py +32 -0
  211. backtrader/indicators/contrib/f2a_ao_indicator.py +120 -0
  212. backtrader/indicators/contrib/fatl_filter.py +179 -0
  213. backtrader/indicators/contrib/fibo_candles_indicator.py +78 -0
  214. backtrader/indicators/contrib/fine_tuning_ma.py +100 -0
  215. backtrader/indicators/contrib/fisher_org_v1.py +102 -0
  216. backtrader/indicators/contrib/fisher_org_v1_sign.py +118 -0
  217. backtrader/indicators/contrib/force_index_ema.py +96 -0
  218. backtrader/indicators/contrib/force_index_ema_2.py +27 -0
  219. backtrader/indicators/contrib/forecast_oscilator.py +145 -0
  220. backtrader/indicators/contrib/fractal_amambk.py +81 -0
  221. backtrader/indicators/contrib/frama_series.py +84 -0
  222. backtrader/indicators/contrib/frasm_av2_indicator.py +104 -0
  223. backtrader/indicators/contrib/go_indicator.py +93 -0
  224. backtrader/indicators/contrib/hlr_indicator.py +95 -0
  225. backtrader/indicators/contrib/hma.py +50 -0
  226. backtrader/indicators/contrib/i4_drfv2.py +34 -0
  227. backtrader/indicators/contrib/i4_drfv3.py +38 -0
  228. backtrader/indicators/contrib/i_anch_mom_indicator.py +72 -0
  229. backtrader/indicators/contrib/i_de_marker_sign_indicator.py +64 -0
  230. backtrader/indicators/contrib/i_gap_indicator.py +45 -0
  231. backtrader/indicators/contrib/i_stoch_komposter_indicator.py +77 -0
  232. backtrader/indicators/contrib/i_trend_indicator.py +125 -0
  233. backtrader/indicators/contrib/iamma_indicator.py +39 -0
  234. backtrader/indicators/contrib/indexed_moving_average.py +33 -0
  235. backtrader/indicators/contrib/instantaneous_trend_filter_indicator.py +51 -0
  236. backtrader/indicators/contrib/inverse_reaction_indicator.py +41 -0
  237. backtrader/indicators/contrib/irsi_sign_indicator.py +95 -0
  238. backtrader/indicators/contrib/iwpr_sign_indicator.py +59 -0
  239. backtrader/indicators/contrib/j_brain_trend1_sig_indicator.py +233 -0
  240. backtrader/indicators/contrib/j_tpo_proxy.py +32 -0
  241. backtrader/indicators/contrib/jma_slope_indicator.py +73 -0
  242. backtrader/indicators/contrib/kalman_filter_indicator.py +119 -0
  243. backtrader/indicators/contrib/kalman_filter_line.py +127 -0
  244. backtrader/indicators/contrib/kama_indicator.py +150 -0
  245. backtrader/indicators/contrib/karacatica_indicator.py +99 -0
  246. backtrader/indicators/contrib/kdj_indicator.py +59 -0
  247. backtrader/indicators/contrib/kwan_ccc_indicator.py +195 -0
  248. backtrader/indicators/contrib/kwan_nrp_indicator.py +113 -0
  249. backtrader/indicators/contrib/kwan_rdp_indicator.py +192 -0
  250. backtrader/indicators/contrib/laguerre_adx_indicator.py +85 -0
  251. backtrader/indicators/contrib/laguerre_filter_indicator.py +66 -0
  252. backtrader/indicators/contrib/laguerre_plus_di_proxy.py +57 -0
  253. backtrader/indicators/contrib/laguerre_roc_indicator.py +81 -0
  254. backtrader/indicators/contrib/le_man_signal_indicator.py +63 -0
  255. backtrader/indicators/contrib/linear_reg_slope_v2_indicator.py +136 -0
  256. backtrader/indicators/contrib/loco_indicator.py +88 -0
  257. backtrader/indicators/contrib/lrma_indicator.py +185 -0
  258. backtrader/indicators/contrib/lsma_angle_indicator.py +106 -0
  259. backtrader/indicators/contrib/ma_rounding_channel_indicator.py +149 -0
  260. backtrader/indicators/contrib/macd2_indicator.py +61 -0
  261. backtrader/indicators/contrib/macd_candle_indicator.py +80 -0
  262. backtrader/indicators/contrib/malr_indicator.py +77 -0
  263. backtrader/indicators/contrib/momentum_candle_sign_indicator.py +51 -0
  264. backtrader/indicators/contrib/moving_average_fn_indicator.py +139 -0
  265. backtrader/indicators/contrib/mt5_stochastic_close_close.py +57 -0
  266. backtrader/indicators/contrib/muv_nor_diff_cloud_indicator.py +107 -0
  267. backtrader/indicators/contrib/non_lag_dot_indicator.py +124 -0
  268. backtrader/indicators/contrib/nrtr_extr_indicator.py +95 -0
  269. backtrader/indicators/contrib/nrtr_indicator.py +95 -0
  270. backtrader/indicators/contrib/p_channel_system.py +40 -0
  271. backtrader/indicators/contrib/percent_envelope.py +37 -0
  272. backtrader/indicators/contrib/percentage_crossover_channel.py +47 -0
  273. backtrader/indicators/contrib/pivot_zig_zag_proxy.py +47 -0
  274. backtrader/indicators/contrib/price_channel_stop_indicator.py +104 -0
  275. backtrader/indicators/contrib/price_extreme_channel.py +35 -0
  276. backtrader/indicators/contrib/qqe_cloud_indicator.py +129 -0
  277. backtrader/indicators/contrib/ravi_indicator.py +40 -0
  278. backtrader/indicators/contrib/raw_close_close_stochastic.py +74 -0
  279. backtrader/indicators/contrib/rd_trend_trigger_indicator.py +51 -0
  280. backtrader/indicators/contrib/renko_level.py +85 -0
  281. backtrader/indicators/contrib/renko_line_break.py +91 -0
  282. backtrader/indicators/contrib/rftl_indicator.py +41 -0
  283. backtrader/indicators/contrib/rkd_indicator.py +53 -0
  284. backtrader/indicators/contrib/roc2_vg_indicator.py +68 -0
  285. backtrader/indicators/contrib/rsi_histogram_indicator.py +43 -0
  286. backtrader/indicators/contrib/rsi_slowdown.py +57 -0
  287. backtrader/indicators/contrib/rsioma_v2.py +41 -0
  288. backtrader/indicators/contrib/rvi_histogram_indicator.py +107 -0
  289. backtrader/indicators/contrib/safe_adx.py +89 -0
  290. backtrader/indicators/contrib/shared_strategy_indicators.py +1651 -0
  291. backtrader/indicators/contrib/sidus_indicator.py +105 -0
  292. backtrader/indicators/contrib/silver_trend_indicator.py +79 -0
  293. backtrader/indicators/contrib/sliding_range_color.py +56 -0
  294. backtrader/indicators/contrib/slow_stoch.py +42 -0
  295. backtrader/indicators/contrib/smoothed_adx_indicator.py +86 -0
  296. backtrader/indicators/contrib/smoothed_rsi.py +31 -0
  297. backtrader/indicators/contrib/spearman_rank_correlation_histogram.py +60 -0
  298. backtrader/indicators/contrib/stalin_indicator.py +152 -0
  299. backtrader/indicators/contrib/starter_laguerre_filter.py +62 -0
  300. backtrader/indicators/contrib/step_manrtr_indicator.py +137 -0
  301. backtrader/indicators/contrib/stochastic_histogram_indicator.py +143 -0
  302. backtrader/indicators/contrib/t3_alarm_indicator.py +125 -0
  303. backtrader/indicators/contrib/t3_average.py +76 -0
  304. backtrader/indicators/contrib/t3_indicator.py +40 -0
  305. backtrader/indicators/contrib/the20s_v020_signal.py +93 -0
  306. backtrader/indicators/contrib/three_candles_indicator.py +70 -0
  307. backtrader/indicators/contrib/three_line_break_indicator.py +64 -0
  308. backtrader/indicators/contrib/time_line.py +57 -0
  309. backtrader/indicators/contrib/trading_channel_index_proxy.py +48 -0
  310. backtrader/indicators/contrib/trend_arrows_indicator.py +109 -0
  311. backtrader/indicators/contrib/trend_continuation_indicator.py +127 -0
  312. backtrader/indicators/contrib/trend_intensity_index_proxy.py +51 -0
  313. backtrader/indicators/contrib/trend_manager_indicator.py +39 -0
  314. backtrader/indicators/contrib/tri_x_candle_indicator.py +51 -0
  315. backtrader/indicators/contrib/trigger_line.py +66 -0
  316. backtrader/indicators/contrib/triple_ema_rate.py +34 -0
  317. backtrader/indicators/contrib/trvi_indicator.py +194 -0
  318. backtrader/indicators/contrib/two_pb_ideal_xosma_indicator.py +127 -0
  319. backtrader/indicators/contrib/ultra_absolutely_no_lag_lwma_color.py +92 -0
  320. backtrader/indicators/contrib/ultra_wpr_indicator.py +173 -0
  321. backtrader/indicators/contrib/up_down_candle_strength.py +68 -0
  322. backtrader/indicators/contrib/vinin_i_trend_indicator.py +139 -0
  323. backtrader/indicators/contrib/volume_weighted_ma_indicator.py +78 -0
  324. backtrader/indicators/contrib/volume_weighted_ma_st_dev_indicator.py +111 -0
  325. backtrader/indicators/contrib/vwap_close_indicator.py +65 -0
  326. backtrader/indicators/contrib/vwma_candle.py +57 -0
  327. backtrader/indicators/contrib/vwma_digit_system.py +70 -0
  328. backtrader/indicators/contrib/wami.py +43 -0
  329. backtrader/indicators/contrib/wprsi_signal_indicator.py +105 -0
  330. backtrader/indicators/contrib/x_de_marker_histogram_vol_direct_indicator.py +145 -0
  331. backtrader/indicators/contrib/x_fisher_indicator.py +64 -0
  332. backtrader/indicators/contrib/xcci_histogram_vol_direct_indicator.py +56 -0
  333. backtrader/indicators/contrib/xcci_histogram_vol_indicator.py +85 -0
  334. backtrader/indicators/contrib/xma_ichimoku.py +163 -0
  335. backtrader/indicators/contrib/xma_ishimoku_channel_indicator.py +65 -0
  336. backtrader/indicators/contrib/xma_ishimoku_line.py +68 -0
  337. backtrader/indicators/contrib/xma_range_bands_indicator.py +107 -0
  338. backtrader/indicators/contrib/xmacd_indicator.py +70 -0
  339. backtrader/indicators/contrib/xrsi_de_marker_histogram.py +67 -0
  340. backtrader/indicators/contrib/xrsi_histogram_vol_direct_indicator.py +52 -0
  341. backtrader/indicators/contrib/xrsi_histogram_vol_indicator.py +81 -0
  342. backtrader/indicators/contrib/xrvi_indicator.py +130 -0
  343. backtrader/indicators/contrib/zero_lag_macd.py +36 -0
  344. backtrader/indicators/contrib/zig_zag_recent_pivot_signal.py +90 -0
  345. backtrader/indicators/contrib/zpf_indicator.py +115 -0
  346. backtrader/indicators/crossover.py +337 -0
  347. backtrader/indicators/dema.py +175 -0
  348. backtrader/indicators/demarker.py +270 -0
  349. backtrader/indicators/deviation.py +284 -0
  350. backtrader/indicators/directionalmove.py +1071 -0
  351. backtrader/indicators/dma.py +112 -0
  352. backtrader/indicators/dpo.py +96 -0
  353. backtrader/indicators/dv2.py +56 -0
  354. backtrader/indicators/ema.py +145 -0
  355. backtrader/indicators/envelope.py +475 -0
  356. backtrader/indicators/hadelta.py +198 -0
  357. backtrader/indicators/heikinashi.py +153 -0
  358. backtrader/indicators/hma.py +153 -0
  359. backtrader/indicators/hurst.py +151 -0
  360. backtrader/indicators/ichimoku.py +267 -0
  361. backtrader/indicators/kama.py +181 -0
  362. backtrader/indicators/kst.py +159 -0
  363. backtrader/indicators/lrsi.py +125 -0
  364. backtrader/indicators/mabase.py +147 -0
  365. backtrader/indicators/macd.py +322 -0
  366. backtrader/indicators/momentum.py +267 -0
  367. backtrader/indicators/moneyflow.py +237 -0
  368. backtrader/indicators/mt5atr.py +124 -0
  369. backtrader/indicators/myind.py +179 -0
  370. backtrader/indicators/obv.py +94 -0
  371. backtrader/indicators/ols.py +265 -0
  372. backtrader/indicators/oscillator.py +161 -0
  373. backtrader/indicators/percentchange.py +83 -0
  374. backtrader/indicators/percentrank.py +46 -0
  375. backtrader/indicators/pivotpoint.py +469 -0
  376. backtrader/indicators/prettygoodoscillator.py +113 -0
  377. backtrader/indicators/priceops_ext.py +123 -0
  378. backtrader/indicators/priceoscillator.py +262 -0
  379. backtrader/indicators/psar.py +212 -0
  380. backtrader/indicators/rmi.py +69 -0
  381. backtrader/indicators/rsi.py +440 -0
  382. backtrader/indicators/sma.py +141 -0
  383. backtrader/indicators/smma.py +116 -0
  384. backtrader/indicators/spread.py +54 -0
  385. backtrader/indicators/stochastic.py +263 -0
  386. backtrader/indicators/supertrend.py +436 -0
  387. backtrader/indicators/trend_ext.py +105 -0
  388. backtrader/indicators/trix.py +202 -0
  389. backtrader/indicators/tsi.py +155 -0
  390. backtrader/indicators/ultimateoscillator.py +158 -0
  391. backtrader/indicators/vortex.py +62 -0
  392. backtrader/indicators/williams.py +194 -0
  393. backtrader/indicators/wma.py +103 -0
  394. backtrader/indicators/zlema.py +135 -0
  395. backtrader/indicators/zlind.py +104 -0
  396. backtrader/linebuffer.py +3155 -0
  397. backtrader/lineiterator.py +2911 -0
  398. backtrader/lineroot.py +1106 -0
  399. backtrader/lineseries.py +2559 -0
  400. backtrader/live_trading/__init__.py +31 -0
  401. backtrader/live_trading/interface.py +404 -0
  402. backtrader/mathsupport.py +94 -0
  403. backtrader/metabase.py +1804 -0
  404. backtrader/mixins/__init__.py +21 -0
  405. backtrader/mixins/singleton.py +118 -0
  406. backtrader/observer.py +106 -0
  407. backtrader/observers/__init__.py +45 -0
  408. backtrader/observers/benchmark.py +126 -0
  409. backtrader/observers/broker.py +184 -0
  410. backtrader/observers/buysell.py +144 -0
  411. backtrader/observers/drawdown.py +161 -0
  412. backtrader/observers/logreturns.py +113 -0
  413. backtrader/observers/timereturn.py +86 -0
  414. backtrader/observers/trade_logger.py +2972 -0
  415. backtrader/observers/tradelogger.py +6 -0
  416. backtrader/observers/trades.py +258 -0
  417. backtrader/order.py +1114 -0
  418. backtrader/parameters.py +2345 -0
  419. backtrader/plot/__init__.py +54 -0
  420. backtrader/plot/finance.py +1022 -0
  421. backtrader/plot/formatters.py +200 -0
  422. backtrader/plot/locator.py +353 -0
  423. backtrader/plot/multicursor.py +495 -0
  424. backtrader/plot/plot.py +2500 -0
  425. backtrader/plot/plot_plotly.py +1351 -0
  426. backtrader/plot/scheme.py +253 -0
  427. backtrader/plot/utils.py +104 -0
  428. backtrader/position.py +290 -0
  429. backtrader/position_modes.py +132 -0
  430. backtrader/profiles.py +254 -0
  431. backtrader/reports/__init__.py +39 -0
  432. backtrader/reports/charts.py +371 -0
  433. backtrader/reports/performance.py +620 -0
  434. backtrader/reports/reporter.py +660 -0
  435. backtrader/resamplerfilter.py +1001 -0
  436. backtrader/signal.py +118 -0
  437. backtrader/signals/__init__.py +17 -0
  438. backtrader/sizer.py +114 -0
  439. backtrader/sizers/__init__.py +26 -0
  440. backtrader/sizers/fixedsize.py +161 -0
  441. backtrader/sizers/percents_sizer.py +119 -0
  442. backtrader/store.py +221 -0
  443. backtrader/stores/__init__.py +33 -0
  444. backtrader/stores/btapistore.py +15506 -0
  445. backtrader/stores/livestore.py +137 -0
  446. backtrader/stores/vchartfile.py +96 -0
  447. backtrader/strategy.py +3655 -0
  448. backtrader/talib.py +280 -0
  449. backtrader/test_helpers.py +96 -0
  450. backtrader/timer.py +358 -0
  451. backtrader/trade.py +442 -0
  452. backtrader/tradingcal.py +361 -0
  453. backtrader/utils/__init__.py +68 -0
  454. backtrader/utils/autodict.py +251 -0
  455. backtrader/utils/date.py +71 -0
  456. backtrader/utils/dateintern.py +509 -0
  457. backtrader/utils/flushfile.py +94 -0
  458. backtrader/utils/fractal.py +101 -0
  459. backtrader/utils/get_metrics.py +101 -0
  460. backtrader/utils/load_data.py +209 -0
  461. backtrader/utils/log_message.py +998 -0
  462. backtrader/utils/ordereddefaultdict.py +75 -0
  463. backtrader/utils/py3.py +296 -0
  464. backtrader/version.py +21 -0
  465. backtrader/writer.py +372 -0
@@ -0,0 +1,620 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Performance metrics calculator.
4
+
5
+ Extracts and calculates all performance metrics from strategies and analyzers.
6
+ """
7
+
8
+ import math
9
+
10
+ from ..utils.log_message import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class PerformanceCalculator:
16
+ """Unified performance metrics calculator.
17
+
18
+ Extracts and calculates all performance metrics from strategies and analyzers, including:
19
+ - PnL metrics: total return, annual return, cumulative return
20
+ - Risk metrics: max drawdown, Sharpe ratio, SQN, Calmar ratio
21
+ - Trade statistics: win rate, profit/loss ratio, average profit/loss
22
+
23
+ Attributes:
24
+ strategy: Strategy instance
25
+
26
+ Usage example:
27
+ calc = PerformanceCalculator(strategy)
28
+ metrics = calc.get_all_metrics()
29
+ print(f"Sharpe ratio: {metrics['sharpe_ratio']}")
30
+ print(f"SQN rating: {metrics['sqn_human']}")
31
+ """
32
+
33
+ def __init__(self, strategy):
34
+ """Initialize the performance calculator.
35
+
36
+ Args:
37
+ strategy: backtrader strategy instance (result from run())
38
+ """
39
+ self.strategy = strategy
40
+ self._analyzers = getattr(strategy, "analyzers", None)
41
+ self._broker = getattr(strategy, "broker", None)
42
+
43
+ def get_all_metrics(self):
44
+ """Return dictionary of all performance metrics.
45
+
46
+ Returns:
47
+ dict: Dictionary containing all performance metrics
48
+ """
49
+ metrics = {}
50
+ pnl_metrics = self.get_pnl_metrics()
51
+ metrics.update(pnl_metrics)
52
+ metrics.update(self.get_risk_metrics(pnl_metrics=pnl_metrics))
53
+ metrics.update(self.get_trade_metrics())
54
+ metrics.update(self.get_kpi_metrics())
55
+ return metrics
56
+
57
+ def get_pnl_metrics(self):
58
+ """Get profit and loss related metrics.
59
+
60
+ Returns:
61
+ dict: PnL metrics dictionary
62
+ """
63
+ metrics = {
64
+ "start_cash": self._get_start_cash(),
65
+ "end_value": self._get_end_value(),
66
+ "rpl": None, # Realized profit/loss
67
+ "total_return": None, # Total return %
68
+ "annual_return": None, # Annual return %
69
+ "result_won_trades": None,
70
+ "result_lost_trades": None,
71
+ "profit_factor": None,
72
+ "rpl_per_trade": None,
73
+ }
74
+
75
+ # Calculate basic returns
76
+ start_cash = metrics["start_cash"]
77
+ end_value = metrics["end_value"]
78
+
79
+ if start_cash is not None and end_value is not None:
80
+ if math.isfinite(start_cash) and math.isfinite(end_value):
81
+ metrics["rpl"] = end_value - start_cash
82
+ if start_cash != 0:
83
+ metrics["total_return"] = 100 * (end_value / start_cash - 1)
84
+ else:
85
+ logger.debug(
86
+ "Skipping basic PnL metric calculation for invalid broker values: start_cash=%s, end_value=%s",
87
+ start_cash,
88
+ end_value,
89
+ )
90
+
91
+ # Get trade statistics from TradeAnalyzer
92
+ trade_analysis = self._get_analyzer_result("tradeanalyzer")
93
+ if trade_analysis:
94
+ self._apply_trade_metrics(metrics, trade_analysis)
95
+
96
+ # Calculate annual return
97
+ bt_period_days = self._get_backtest_days()
98
+ if bt_period_days and bt_period_days > 0 and metrics["total_return"] is not None:
99
+ total_return_decimal = metrics["total_return"] / 100
100
+ compound_ratio = 1 + total_return_decimal
101
+ if math.isfinite(compound_ratio) and compound_ratio > 0:
102
+ metrics["annual_return"] = 100 * (compound_ratio ** (365.25 / bt_period_days) - 1)
103
+ else:
104
+ logger.debug(
105
+ "Skipping annual_return calculation for invalid compound ratio: %s",
106
+ compound_ratio,
107
+ )
108
+
109
+ return metrics
110
+
111
+ def _apply_trade_metrics(self, metrics, trade_analysis):
112
+ """Fill rpl / won-lost / profit_factor / rpl_per_trade into ``metrics``
113
+ from a TradeAnalyzer result dict. Extracted from get_pnl_metrics."""
114
+ pnl = trade_analysis.get("pnl", {})
115
+ net = pnl.get("net", {})
116
+
117
+ if "total" in net:
118
+ metrics["rpl"] = net["total"]
119
+
120
+ won = trade_analysis.get("won", {})
121
+ lost = trade_analysis.get("lost", {})
122
+
123
+ won_pnl = won.get("pnl", {})
124
+ lost_pnl = lost.get("pnl", {})
125
+
126
+ metrics["result_won_trades"] = won_pnl.get("total")
127
+ metrics["result_lost_trades"] = lost_pnl.get("total")
128
+
129
+ # Calculate profit factor
130
+ result_won_trades = metrics["result_won_trades"]
131
+ result_lost_trades = metrics["result_lost_trades"]
132
+ if all(
133
+ isinstance(value, (int, float)) and math.isfinite(value)
134
+ for value in (result_won_trades, result_lost_trades)
135
+ ):
136
+ if result_lost_trades != 0:
137
+ metrics["profit_factor"] = abs(result_won_trades / result_lost_trades)
138
+ elif result_won_trades is not None or result_lost_trades is not None:
139
+ logger.debug(
140
+ "Skipping profit_factor calculation for invalid trade PnL totals: won=%s, lost=%s",
141
+ result_won_trades,
142
+ result_lost_trades,
143
+ )
144
+
145
+ # Average profit/loss per trade
146
+ total = trade_analysis.get("total", {})
147
+ closed = total.get("closed", 0)
148
+ if (
149
+ isinstance(closed, (int, float))
150
+ and math.isfinite(closed)
151
+ and closed > 0
152
+ and isinstance(metrics["rpl"], (int, float))
153
+ and math.isfinite(metrics["rpl"])
154
+ ):
155
+ metrics["rpl_per_trade"] = metrics["rpl"] / closed
156
+ elif closed not in (0, None) or metrics["rpl"] is not None:
157
+ logger.debug(
158
+ "Skipping rpl_per_trade calculation for invalid inputs: closed=%s, rpl=%s",
159
+ closed,
160
+ metrics["rpl"],
161
+ )
162
+
163
+ def get_risk_metrics(self, pnl_metrics=None):
164
+ """Get risk-related metrics.
165
+
166
+ Args:
167
+ pnl_metrics: Pre-computed PnL metrics dict (avoids recomputation)
168
+
169
+ Returns:
170
+ dict: Risk metrics dictionary
171
+ """
172
+ metrics = {
173
+ "max_money_drawdown": None,
174
+ "max_pct_drawdown": None,
175
+ "calmar_ratio": None,
176
+ }
177
+
178
+ # Get from DrawDown analyzer
179
+ drawdown = self._get_analyzer_result("drawdown")
180
+ if drawdown:
181
+ max_dd = drawdown.get("max", {})
182
+ metrics["max_money_drawdown"] = max_dd.get("moneydown")
183
+ metrics["max_pct_drawdown"] = max_dd.get("drawdown")
184
+
185
+ # Calculate Calmar ratio
186
+ if pnl_metrics is None:
187
+ pnl_metrics = self.get_pnl_metrics()
188
+ annual_return = pnl_metrics.get("annual_return")
189
+ max_pct_drawdown = metrics.get("max_pct_drawdown")
190
+ if annual_return is not None and max_pct_drawdown is not None:
191
+ if (
192
+ math.isfinite(annual_return)
193
+ and math.isfinite(max_pct_drawdown)
194
+ and max_pct_drawdown > 0
195
+ ):
196
+ metrics["calmar_ratio"] = abs(annual_return / max_pct_drawdown)
197
+ else:
198
+ logger.debug(
199
+ "Skipping calmar_ratio calculation for invalid inputs: annual_return=%s, max_pct_drawdown=%s",
200
+ annual_return,
201
+ max_pct_drawdown,
202
+ )
203
+
204
+ return metrics
205
+
206
+ def get_trade_metrics(self):
207
+ """Get trade statistics metrics.
208
+
209
+ Returns:
210
+ dict: Trade statistics dictionary
211
+ """
212
+ metrics = {
213
+ "total_number_trades": 0,
214
+ "trades_closed": 0,
215
+ "trades_won": 0,
216
+ "trades_lost": 0,
217
+ "pct_winning": None,
218
+ "pct_losing": None,
219
+ "avg_money_winning": None,
220
+ "avg_money_losing": None,
221
+ "best_winning_trade": None,
222
+ "worst_losing_trade": None,
223
+ "avg_trade_duration": None,
224
+ }
225
+
226
+ trade_analysis = self._get_analyzer_result("tradeanalyzer")
227
+ if trade_analysis:
228
+ total = trade_analysis.get("total", {})
229
+ metrics["total_number_trades"] = total.get("total", 0)
230
+ metrics["trades_closed"] = total.get("closed", 0)
231
+
232
+ won = trade_analysis.get("won", {})
233
+ lost = trade_analysis.get("lost", {})
234
+
235
+ metrics["trades_won"] = won.get("total", 0)
236
+ metrics["trades_lost"] = lost.get("total", 0)
237
+
238
+ # Win rate
239
+ trades_closed = metrics["trades_closed"]
240
+ trades_won = metrics["trades_won"]
241
+ trades_lost = metrics["trades_lost"]
242
+ if all(
243
+ isinstance(value, (int, float)) and math.isfinite(value)
244
+ for value in (trades_closed, trades_won, trades_lost)
245
+ ):
246
+ if trades_closed > 0:
247
+ metrics["pct_winning"] = 100 * trades_won / trades_closed
248
+ metrics["pct_losing"] = 100 * trades_lost / trades_closed
249
+ else:
250
+ logger.debug(
251
+ "Skipping trade win/loss percentage calculation for invalid counts: closed=%s, won=%s, lost=%s",
252
+ trades_closed,
253
+ trades_won,
254
+ trades_lost,
255
+ )
256
+
257
+ # Average profit/loss
258
+ won_pnl = won.get("pnl", {})
259
+ lost_pnl = lost.get("pnl", {})
260
+
261
+ metrics["avg_money_winning"] = won_pnl.get("average")
262
+ metrics["avg_money_losing"] = lost_pnl.get("average")
263
+ metrics["best_winning_trade"] = won_pnl.get("max")
264
+ metrics["worst_losing_trade"] = lost_pnl.get("max")
265
+
266
+ # Average trade duration
267
+ len_info = trade_analysis.get("len", {})
268
+ if isinstance(len_info, dict):
269
+ total_len = len_info.get("total", {})
270
+ if isinstance(total_len, dict):
271
+ metrics["avg_trade_duration"] = total_len.get("average")
272
+
273
+ return metrics
274
+
275
+ def get_kpi_metrics(self):
276
+ """Get key performance indicators.
277
+
278
+ Returns:
279
+ dict: KPI metrics dictionary
280
+ """
281
+ metrics = {
282
+ "sharpe_ratio": None,
283
+ "sqn_score": None,
284
+ "sqn_human": None,
285
+ "sortino_ratio": None,
286
+ }
287
+
288
+ # Sharpe ratio
289
+ sharpe = self._get_analyzer_result("sharperatio")
290
+ if sharpe:
291
+ metrics["sharpe_ratio"] = sharpe.get("sharperatio")
292
+
293
+ # SQN
294
+ sqn = self._get_analyzer_result("sqn")
295
+ if sqn:
296
+ sqn_score = sqn.get("sqn")
297
+ metrics["sqn_score"] = sqn_score
298
+ metrics["sqn_human"] = self.sqn_to_rating(sqn_score)
299
+
300
+ # Sortino ratio
301
+ sortino = self._get_analyzer_result("sortinoratio")
302
+ if sortino:
303
+ metrics["sortino_ratio"] = sortino.get("sortinoratio")
304
+
305
+ return metrics
306
+
307
+ def get_equity_curve(self):
308
+ """Get equity curve data.
309
+
310
+ Returns:
311
+ tuple: (dates, values) Lists of dates and equity values
312
+ """
313
+ dates = []
314
+ values = []
315
+
316
+ # Try to get from Broker observer
317
+ if hasattr(self.strategy, "observers"):
318
+ for obs in self.strategy.observers:
319
+ if obs.__class__.__name__ == "Broker":
320
+ if hasattr(obs.lines, "value"):
321
+ value_line = obs.lines.value
322
+ length = len(value_line)
323
+
324
+ # Get dates
325
+ if hasattr(self.strategy, "data"):
326
+ data = self.strategy.data
327
+ from ..utils.date import num2date
328
+
329
+ # Correct indexing: from 1-length to 0
330
+ for i in range(length):
331
+ idx = 1 - length + i
332
+ try:
333
+ dt_num = data.datetime[idx]
334
+ dates.append(num2date(dt_num))
335
+ values.append(value_line[idx])
336
+ except (AttributeError, IndexError, TypeError, ValueError) as e:
337
+ logger.warning(
338
+ "Failed to get equity data at idx %d: %s", idx, e
339
+ )
340
+ break
341
+
342
+ if not values:
343
+ # Try to get from TimeReturn analyzer and calculate cumulative equity
344
+ time_return = self._get_analyzer_result("timereturn")
345
+ if time_return:
346
+ start_cash = self._resolve_start_cash(self._get_start_cash())
347
+ cumulative_value = start_cash
348
+ for dt, ret in sorted(time_return.items()):
349
+ if not isinstance(ret, (int, float)) or not math.isfinite(ret):
350
+ logger.debug(
351
+ "Skipping invalid timereturn value in equity curve: %s at %s",
352
+ ret,
353
+ dt,
354
+ )
355
+ dates.append(dt)
356
+ values.append(cumulative_value)
357
+ continue
358
+
359
+ cumulative_value = cumulative_value * (1 + ret)
360
+ dates.append(dt)
361
+ values.append(cumulative_value)
362
+
363
+ if not values:
364
+ # If still no data, calculate buy-and-hold equity curve from data source as fallback
365
+ benchmark_dates, benchmark_values = self.get_buynhold_curve()
366
+ if benchmark_dates and benchmark_values:
367
+ start_cash = self._resolve_start_cash(self._get_start_cash())
368
+ dates = benchmark_dates
369
+ # Convert normalized values to actual equity values
370
+ values = [start_cash * v / 100 for v in benchmark_values]
371
+
372
+ return dates, values
373
+
374
+ def get_buynhold_curve(self):
375
+ """Get buy-and-hold comparison curve.
376
+
377
+ Returns:
378
+ tuple: (dates, values) Lists of dates and buy-and-hold values
379
+ """
380
+ if not hasattr(self.strategy, "data"):
381
+ return None, None
382
+
383
+ data = self.strategy.data
384
+ dates = []
385
+ values: list = []
386
+
387
+ try:
388
+ length = len(data)
389
+ if length == 0:
390
+ return None, None
391
+
392
+ # Get open price as buy-and-hold benchmark
393
+ first_price = None
394
+
395
+ from ..utils.date import num2date
396
+
397
+ # Correct indexing: from 1-length to 0
398
+ for i in range(length):
399
+ idx = 1 - length + i
400
+ try:
401
+ dt_num = data.datetime[idx]
402
+ dates.append(num2date(dt_num))
403
+
404
+ price = data.open[idx]
405
+ if not isinstance(price, (int, float)) or not math.isfinite(price):
406
+ logger.debug(
407
+ "Skipping invalid buy-and-hold price at idx %d: %s", idx, price
408
+ )
409
+ values.append(values[-1] if values else 100)
410
+ continue
411
+
412
+ if first_price is None and price > 0:
413
+ first_price = price
414
+
415
+ # Normalize to 100
416
+ values.append(100 * price / first_price if first_price else 100)
417
+ except (AttributeError, IndexError, TypeError, ValueError, ZeroDivisionError) as e:
418
+ logger.warning("Failed to get benchmark data at idx %d: %s", idx, e)
419
+ except (AttributeError, IndexError, TypeError, ValueError) as e:
420
+ logger.warning("Failed to calculate benchmark curve: %s", e)
421
+
422
+ return dates, values
423
+
424
+ @staticmethod
425
+ def _resolve_start_cash(value, default=100000):
426
+ if not isinstance(value, (int, float)) or not math.isfinite(value):
427
+ return default
428
+ return value
429
+
430
+ @staticmethod
431
+ def _normalize_analyzer_name(value):
432
+ return value.lower() if isinstance(value, str) else ""
433
+
434
+ @staticmethod
435
+ def sqn_to_rating(sqn_score):
436
+ """Convert SQN score to human-readable rating.
437
+
438
+ Reference: http://www.vantharp.com/tharp-concepts/sqn.asp
439
+
440
+ Args:
441
+ sqn_score: SQN score
442
+
443
+ Returns:
444
+ str: Human-readable rating
445
+ """
446
+ if sqn_score is None or not isinstance(sqn_score, (int, float)):
447
+ return "N/A"
448
+
449
+ if math.isnan(sqn_score):
450
+ return "N/A"
451
+
452
+ if sqn_score < 1.6:
453
+ return "Poor"
454
+ if sqn_score < 1.9:
455
+ return "Below Average"
456
+ if sqn_score < 2.4:
457
+ return "Average"
458
+ if sqn_score < 2.9:
459
+ return "Good"
460
+ if sqn_score < 5.0:
461
+ return "Excellent"
462
+ if sqn_score < 6.9:
463
+ return "Superb"
464
+ return "Holy Grail"
465
+
466
+ def _get_start_cash(self):
467
+ """Get starting cash."""
468
+ if self._broker is None:
469
+ return None
470
+ try:
471
+ return getattr(self._broker, "startingcash", None)
472
+ except Exception as e:
473
+ # Broker is a pluggable object that may raise any error type;
474
+ # report generation must degrade gracefully (see edge-case tests).
475
+ logger.warning("Failed to get starting cash: %s", e)
476
+ return None
477
+
478
+ def _get_end_value(self):
479
+ """Get final portfolio value."""
480
+ if self._broker is None:
481
+ return None
482
+ try:
483
+ return self._broker.getvalue()
484
+ except Exception as e:
485
+ # Broker is a pluggable object that may raise any error type;
486
+ # report generation must degrade gracefully (see edge-case tests).
487
+ logger.warning("Failed to get end value: %s", e)
488
+ return None
489
+
490
+ def _get_backtest_days(self):
491
+ """Get number of backtest days."""
492
+ if not hasattr(self.strategy, "data"):
493
+ return None
494
+
495
+ data = self.strategy.data
496
+ try:
497
+ from ..utils.date import num2date
498
+
499
+ length = len(data)
500
+ if length < 2:
501
+ return None
502
+
503
+ # Correct indexing: 0 is current (last) bar, 1-length is first bar
504
+ start_dt = num2date(data.datetime[1 - length])
505
+ end_dt = num2date(data.datetime[0])
506
+
507
+ delta = end_dt - start_dt
508
+ return delta.days
509
+ except (AttributeError, IndexError, TypeError, ValueError) as e:
510
+ logger.warning("Failed to calculate backtest days: %s", e)
511
+ return None
512
+
513
+ def _get_analyzer_result(self, name):
514
+ """Get analyzer result.
515
+
516
+ Args:
517
+ name: Analyzer name (case-insensitive)
518
+
519
+ Returns:
520
+ dict: Analyzer result, or None if not found
521
+ """
522
+ if self._analyzers is None:
523
+ return None
524
+
525
+ # Try to get directly by name
526
+ name_lower = name.lower()
527
+
528
+ # First pass: exact match on class name or _name attribute
529
+ for analyzer in self._analyzers:
530
+ analyzer_name = analyzer.__class__.__name__.lower()
531
+ custom_name = self._normalize_analyzer_name(getattr(analyzer, "_name", ""))
532
+
533
+ if analyzer_name == name_lower or custom_name == name_lower:
534
+ try:
535
+ return analyzer.get_analysis()
536
+ except (AttributeError, KeyError, TypeError, ValueError, IndexError) as e:
537
+ logger.warning("Failed to get analysis from %s: %s", analyzer_name, e)
538
+
539
+ # Second pass: substring match (less precise, used as fallback)
540
+ for analyzer in self._analyzers:
541
+ analyzer_name = analyzer.__class__.__name__.lower()
542
+ custom_name = self._normalize_analyzer_name(getattr(analyzer, "_name", ""))
543
+
544
+ if name_lower in analyzer_name or name_lower in custom_name:
545
+ try:
546
+ return analyzer.get_analysis()
547
+ except (AttributeError, KeyError, TypeError, ValueError, IndexError) as e:
548
+ logger.warning("Failed to get analysis from %s: %s", analyzer_name, e)
549
+
550
+ return None
551
+
552
+ def get_strategy_info(self):
553
+ """Get strategy information.
554
+
555
+ Returns:
556
+ dict: Strategy information dictionary
557
+ """
558
+ info = {
559
+ "strategy_name": self.strategy.__class__.__name__,
560
+ "params": {},
561
+ }
562
+
563
+ # Get strategy parameters
564
+ if hasattr(self.strategy, "params"):
565
+ params = self.strategy.params
566
+ try:
567
+ param_items = params.items()
568
+ except (AttributeError, TypeError) as e:
569
+ logger.warning("Failed to enumerate strategy parameters: %s", e)
570
+ param_items = None
571
+
572
+ if param_items is not None:
573
+ info["params"] = {name: value for name, value in param_items if not callable(value)}
574
+ else:
575
+ for name in dir(params):
576
+ if not name.startswith("_"):
577
+ try:
578
+ value = getattr(params, name)
579
+ if not callable(value):
580
+ info["params"][name] = value
581
+ except (AttributeError, TypeError) as e:
582
+ logger.warning("Failed to get param '%s': %s", name, e)
583
+
584
+ return info
585
+
586
+ def get_data_info(self):
587
+ """Get data information.
588
+
589
+ Returns:
590
+ dict: Data information dictionary
591
+ """
592
+ info = {
593
+ "data_name": None,
594
+ "start_date": None,
595
+ "end_date": None,
596
+ "bars": 0,
597
+ }
598
+
599
+ if not hasattr(self.strategy, "data"):
600
+ return info
601
+
602
+ data = self.strategy.data
603
+
604
+ # Data name
605
+ info["data_name"] = getattr(data, "_name", None) or "Data"
606
+
607
+ try:
608
+ from ..utils.date import num2date
609
+
610
+ length = len(data)
611
+ info["bars"] = length
612
+
613
+ if length > 0:
614
+ # Correct indexing: 0 is current (last) bar, 1-length is first bar
615
+ info["start_date"] = num2date(data.datetime[1 - length])
616
+ info["end_date"] = num2date(data.datetime[0])
617
+ except (AttributeError, IndexError, TypeError, ValueError) as e:
618
+ logger.warning("Failed to get data info: %s", e)
619
+
620
+ return info