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,2500 @@
1
+ #!/usr/bin/env python
2
+ """Plotting module for Backtrader.
3
+
4
+ This module provides plotting functionality for backtrader strategies, including
5
+ matplotlib-based plotting, plotly integration, and pyecharts support for
6
+ creating interactive charts and visualizations of trading results.
7
+
8
+ Classes:
9
+ PInfo: Internal plotting information container
10
+ Plot_OldSync: Main plotting class for matplotlib-based chart generation
11
+
12
+ Functions:
13
+ split_data: Split dataframe into chart components
14
+ get_up_scatter: Get upward swing points for chart
15
+ get_dn_scatter: Get downward swing points for chart
16
+ get_valid_point: Get valid swing points
17
+ draw_chart: Draw comprehensive trading chart
18
+ get_rate_sharpe_drawdown: Calculate performance metrics
19
+ run_cerebro_and_plot: Run cerebro backtest and plot results
20
+ """
21
+
22
+ import bisect
23
+ import collections
24
+ import copy
25
+ import datetime
26
+ import math
27
+ import operator
28
+ import os
29
+ import sys
30
+ import time
31
+ import traceback
32
+ from collections import OrderedDict
33
+
34
+ import matplotlib
35
+ import matplotlib.font_manager as mfontmgr
36
+ import matplotlib.ticker as mticker
37
+ import numpy as np # guaranteed by matplotlib
38
+
39
+ from ..utils.log_message import get_logger
40
+
41
+ if not hasattr(np, "unicode_"):
42
+ # Runtime shim for numpy>=2.0 which removed np.unicode_ (used by deps).
43
+ np.unicode_ = np.str_ # noqa: E402
44
+
45
+ import pandas as pd
46
+ import plotly.figure_factory as ff
47
+ import plotly.graph_objs as go
48
+ import plotly.offline as py
49
+ from dash import html
50
+ from pyecharts import options as opts
51
+ from pyecharts.charts import Bar, EffectScatter, Grid, Kline, Line
52
+ from pyecharts.commons.utils import JsCode
53
+ from pyecharts.globals import SymbolType
54
+
55
+ from .. import AutoInfoClass, analyzers, date2num
56
+ from ..dataseries import TimeFrame
57
+ from ..parameters import ParameterDescriptor, ParameterizedBase
58
+ from ..utils.py3 import integer_types, range
59
+ from . import locator as loc
60
+ from .finance import plot_candlestick, plot_lineonclose, plot_ohlc, plot_volume
61
+ from .formatters import MyDateFormatter, MyVolFormatter
62
+ from .multicursor import MultiCursor
63
+ from .scheme import PlotScheme
64
+ from .utils import tag_box_style
65
+
66
+ logger = get_logger(__name__)
67
+
68
+ # from jupyter_plotly_dash import JupyterDash
69
+
70
+
71
+ def cal_macd_system(data, short_=26, long_=12, m=9):
72
+ """
73
+ data is a standard dataframe containing high, open, low, close, volume
74
+ short_, long_, m are the three parameters of macd
75
+ Return value is a dataframe containing original data and diff, dea, macd columns
76
+ """
77
+ data["diff"] = (
78
+ data["close"].ewm(adjust=False, alpha=2 / (short_ + 1), ignore_na=True).mean()
79
+ - data["close"].ewm(adjust=False, alpha=2 / (long_ + 1), ignore_na=True).mean()
80
+ )
81
+ data["dea"] = data["diff"].ewm(adjust=False, alpha=2 / (m + 1), ignore_na=True).mean()
82
+ data["macd"] = 2 * (data["diff"] - data["dea"])
83
+ return data
84
+
85
+
86
+ def split_data(df) -> dict:
87
+ """Split dataframe into components for pyecharts plotting.
88
+
89
+ Args:
90
+ df: DataFrame containing OHLCV data and MACD indicators
91
+
92
+ Returns:
93
+ Dictionary with keys: datas, times, vols, macds, difs, deas
94
+ """
95
+ datas = list(zip(df["open"], df["close"], df["low"], df["high"], df["volume"], df["up_bar"]))
96
+ times = list(df.index)
97
+ vols = list(df["volume"])
98
+ macds = list(df["macd"])
99
+ difs = list(df["diff"])
100
+ deas = list(df["dea"])
101
+
102
+ return {
103
+ "datas": datas,
104
+ "times": times,
105
+ "vols": vols,
106
+ "macds": macds,
107
+ "difs": difs,
108
+ "deas": deas,
109
+ }
110
+
111
+
112
+ def get_up_scatter(df):
113
+ """Get upward swing points from dataframe.
114
+
115
+ Identifies low points in the price swing for marking on charts.
116
+ Returns a list of tuples containing (time, lowest price).
117
+
118
+ Args:
119
+ df: DataFrame with up_bar and dn_bar columns
120
+
121
+ Returns:
122
+ List of [time, low] tuples marking upward swing points
123
+ """
124
+ # Mark up points, format is a list containing tuples of (time, lowest price)
125
+ mark_line_data = []
126
+ first_swing = None
127
+ pre_index = None
128
+ pre_low = None
129
+ for index, row in df.iterrows():
130
+ up_bar = row["up_bar"]
131
+ dn_bar = row["dn_bar"]
132
+ low = row["low"]
133
+ if first_swing is None:
134
+ if up_bar == 1:
135
+ first_swing = "up"
136
+ if dn_bar == 1:
137
+ first_swing = "dn"
138
+ if first_swing == "up" and dn_bar == 1:
139
+ # mark_line_data.append([index, high])
140
+ first_swing = "dn"
141
+ if first_swing == "dn" and up_bar == 1:
142
+ mark_line_data.append([pre_index, pre_low])
143
+ first_swing = "up"
144
+ pre_index = index
145
+ pre_low = low
146
+ return mark_line_data
147
+
148
+
149
+ def get_dn_scatter(df):
150
+ """Get downward swing points from dataframe.
151
+
152
+ Identifies high points in the price swing for marking on charts.
153
+ Returns a list of tuples containing (time, highest price).
154
+
155
+ Args:
156
+ df: DataFrame with up_bar and dn_bar columns
157
+
158
+ Returns:
159
+ List of [time, high] tuples marking downward swing points
160
+ """
161
+ # Mark down points, format is a list containing tuples of (time, highest price)
162
+ mark_line_data = []
163
+ first_swing = None
164
+ pre_index = None
165
+ pre_high = None
166
+ for index, row in df.iterrows():
167
+ up_bar = row["up_bar"]
168
+ dn_bar = row["dn_bar"]
169
+ high = row["high"]
170
+ if first_swing is None:
171
+ if up_bar == 1:
172
+ first_swing = "up"
173
+ if dn_bar == 1:
174
+ first_swing = "dn"
175
+ if first_swing == "up" and dn_bar == 1:
176
+ mark_line_data.append([pre_index, pre_high])
177
+ first_swing = "dn"
178
+ if first_swing == "dn" and up_bar == 1:
179
+ # mark_line_data.append([index, low])
180
+ first_swing = "up"
181
+ pre_index = index
182
+ pre_high = high
183
+ return mark_line_data
184
+
185
+
186
+ def get_valid_point(df):
187
+ """Get valid swing points for support/resistance lines.
188
+
189
+ Identifies valid swing high and low points that can be used to draw
190
+ support and resistance lines on price charts.
191
+
192
+ Args:
193
+ df: DataFrame with up_bar, dn_bar, high, and low columns
194
+
195
+ Returns:
196
+ Tuple of two lists:
197
+ - valid_dn_point_list: Valid downward (high) points
198
+ - valid_up_point_list: Valid upward (low) points
199
+ """
200
+ valid_dn_point_list = []
201
+ valid_up_point_list = []
202
+ dn_point_point_list = []
203
+ up_point_point_list: list = []
204
+ first_swing = None
205
+ pre_index = None
206
+ pre_low = None
207
+ pre_high = None
208
+ for index, row in df.iterrows():
209
+ up_bar = row["up_bar"]
210
+ dn_bar = row["dn_bar"]
211
+ high = row["high"]
212
+ low = row["low"]
213
+ if first_swing is None:
214
+ if up_bar == 1:
215
+ first_swing = "up"
216
+ if dn_bar == 1:
217
+ first_swing = "dn"
218
+ if first_swing == "up" and dn_bar == 1:
219
+ dn_point_point_list.append([pre_index, pre_high])
220
+ first_swing = "dn"
221
+ if len(dn_point_point_list) > 1 and len(up_point_point_list) > 1:
222
+ pre_pre_high = dn_point_point_list[-2][1]
223
+ # If current highest point is greater than previous highest point, then previous up swing point is at least a test point
224
+ if pre_high > pre_pre_high:
225
+ # Try to get previous dn_point's highest price and pre-previous dn_point's highest price
226
+ pre_1_index, pre_1_low = up_point_point_list[-1]
227
+ pre_2_index, pre_2_low = up_point_point_list[-2]
228
+ # If previous swing point is up
229
+ if pre_1_low < pre_2_low:
230
+ valid_up_point_list.append([pre_1_index, pre_1_low])
231
+
232
+ if first_swing == "dn" and up_bar == 1:
233
+ up_point_point_list.append([pre_index, pre_low])
234
+ first_swing = "up"
235
+ # Get previous up_point
236
+ if len(dn_point_point_list) > 1 and len(up_point_point_list) > 1:
237
+ pre_pre_low = up_point_point_list[-2][1]
238
+ # If current lowest point is less than previous lowest point, then previous highest price is a test point
239
+ if pre_low < pre_pre_low:
240
+ # Try to get previous dn_point's highest price and pre-previous dn_point's highest price
241
+ pre_1_index, pre_1_high = dn_point_point_list[-1]
242
+ pre_2_index, pre_2_high = dn_point_point_list[-2]
243
+ # If previous swing point is up
244
+ if pre_1_high > pre_2_high:
245
+ valid_dn_point_list.append([pre_1_index, pre_1_high])
246
+
247
+ pre_index = index
248
+ pre_low = low
249
+ pre_high = high
250
+ return valid_dn_point_list, valid_up_point_list
251
+
252
+
253
+ def draw_chart(data, df, bk_list, bp_list, sk_list, sp_list):
254
+ """Draw comprehensive trading chart using pyecharts.
255
+
256
+ Creates a detailed K-line chart with volume, MACD indicators,
257
+ trading signals, and support/resistance lines.
258
+
259
+ Args:
260
+ data: Dictionary containing times, volumes, MACD data
261
+ df: DataFrame with OHLCV data and swing markers
262
+ bk_list: List of buy signals (open long)
263
+ bp_list: List of sell signals (close long)
264
+ sk_list: List of short signals (open short)
265
+ sp_list: List of cover signals (close short)
266
+
267
+ Returns:
268
+ None (renders chart to HTML file)
269
+ """
270
+ kline = (
271
+ Kline()
272
+ .add_xaxis(xaxis_data=data["times"])
273
+ .add_yaxis(
274
+ series_name="",
275
+ y_axis=data["datas"],
276
+ itemstyle_opts=opts.ItemStyleOpts(
277
+ color="#ef232a",
278
+ color0="#14b143",
279
+ border_color="#ef232a",
280
+ border_color0="#14b143",
281
+ ),
282
+ markpoint_opts=opts.MarkPointOpts(
283
+ data=[
284
+ opts.MarkPointItem(type_="max", name="Maximum"),
285
+ opts.MarkPointItem(type_="min", name="Minimum"),
286
+ ]
287
+ ),
288
+ # markline_opts = opts.MarkLineOpts(
289
+ # label_opts=opts.LabelOpts(
290
+ # position="middle", color="blue", font_size=15
291
+ # ),
292
+ # data=split_data_part(),
293
+ # symbol=["circle", "none"],
294
+ # ),
295
+ )
296
+ .set_series_opts(
297
+ # To avoid affecting mark points, turn off labels here
298
+ label_opts=opts.LabelOpts(is_show=False),
299
+ markpoint_opts=opts.MarkPointOpts(
300
+ data=[
301
+ opts.MarkPointItem(type_="min", name="y-axis minimum", value_index=1),
302
+ opts.MarkPointItem(type_="max", name="y-axis maximum", value_index=1),
303
+ ]
304
+ ),
305
+ )
306
+ .set_global_opts(
307
+ title_opts=opts.TitleOpts(title="K-line cycle chart", pos_left="0"),
308
+ xaxis_opts=opts.AxisOpts(
309
+ type_="category",
310
+ is_scale=True,
311
+ boundary_gap=False,
312
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
313
+ splitline_opts=opts.SplitLineOpts(is_show=False),
314
+ split_number=20,
315
+ min_="dataMin",
316
+ max_="dataMax",
317
+ ),
318
+ yaxis_opts=opts.AxisOpts(
319
+ is_scale=True, splitline_opts=opts.SplitLineOpts(is_show=True)
320
+ ),
321
+ tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="line"),
322
+ datazoom_opts=[
323
+ opts.DataZoomOpts(is_show=False, type_="inside", xaxis_index=[0, 0], range_end=100),
324
+ opts.DataZoomOpts(is_show=True, xaxis_index=[0, 1], pos_top="97%", range_end=100),
325
+ opts.DataZoomOpts(is_show=False, xaxis_index=[0, 2], range_end=100),
326
+ ],
327
+ # Connect axes of three charts together
328
+ # axispointer_opts=opts.AxisPointerOpts(
329
+ # is_show=True,
330
+ # link=[{"xAxisIndex": "all"}],
331
+ # label=opts.LabelOpts(background_color="#777"),
332
+ # ),
333
+ )
334
+ )
335
+ esc = get_up_scatter(df)
336
+ esc_dn = get_dn_scatter(df)
337
+
338
+ all_up_dn = esc + esc_dn
339
+ all_up_dn_sorted = sorted(all_up_dn, key=lambda x: x[0])
340
+ line_index = [i[0] for i in all_up_dn_sorted]
341
+ line_value = [i[1] for i in all_up_dn_sorted]
342
+
343
+ kline_line = (
344
+ Line()
345
+ .add_xaxis(xaxis_data=line_index)
346
+ .add_yaxis(
347
+ series_name="Wave",
348
+ y_axis=line_value,
349
+ is_smooth=False,
350
+ # linestyle_opts=opts.LineStyleOpts(opacity=0.5),
351
+ linestyle_opts=opts.LineStyleOpts(color="black", width=4, type_="dashed"),
352
+ label_opts=opts.LabelOpts(is_show=False),
353
+ symbol="arrow",
354
+ )
355
+ .set_global_opts(
356
+ xaxis_opts=opts.AxisOpts(
357
+ type_="category",
358
+ grid_index=1,
359
+ axislabel_opts=opts.LabelOpts(is_show=False),
360
+ ),
361
+ yaxis_opts=opts.AxisOpts(
362
+ grid_index=1,
363
+ split_number=3,
364
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
365
+ axistick_opts=opts.AxisTickOpts(is_show=False),
366
+ splitline_opts=opts.SplitLineOpts(is_show=False),
367
+ axislabel_opts=opts.LabelOpts(is_show=True),
368
+ ),
369
+ )
370
+ )
371
+ # Overlap Kline + Line
372
+ overlap_kline_line = kline.overlap(kline_line)
373
+
374
+ # Try to draw support line
375
+ valid_dn_point_list, valid_up_point_list = get_valid_point(df)
376
+ es = (
377
+ EffectScatter()
378
+ .add_xaxis([i[0] for i in valid_up_point_list])
379
+ .add_yaxis("", [i[1] for i in valid_up_point_list], symbol=SymbolType.TRIANGLE)
380
+ )
381
+ # overlap_kline_line = kline
382
+ overlap_kline_line = overlap_kline_line.overlap(es)
383
+
384
+ es_dn = (
385
+ EffectScatter()
386
+ .add_xaxis([i[0] for i in valid_dn_point_list])
387
+ .add_yaxis("", [i[1] for i in valid_dn_point_list], symbol=SymbolType.DIAMOND)
388
+ )
389
+ # overlap_kline_line = kline
390
+ overlap_kline_line = overlap_kline_line.overlap(es_dn)
391
+
392
+ # Try to add some support lines to the support
393
+ for d1, d2 in zip(valid_dn_point_list[:-1], valid_dn_point_list[1:]):
394
+ dn_line = (
395
+ Line()
396
+ .add_xaxis(xaxis_data=[d1[0], d2[0]])
397
+ .add_yaxis(
398
+ series_name="Support",
399
+ y_axis=[d1[1], d2[1]],
400
+ is_smooth=False,
401
+ # linestyle_opts=opts.LineStyleOpts(opacity=0.5),
402
+ linestyle_opts=opts.LineStyleOpts(color="green", width=2, type_="dotted"),
403
+ label_opts=opts.LabelOpts(is_show=False),
404
+ symbol="arrow",
405
+ )
406
+ .set_global_opts(
407
+ xaxis_opts=opts.AxisOpts(
408
+ type_="category",
409
+ grid_index=1,
410
+ axislabel_opts=opts.LabelOpts(is_show=False),
411
+ ),
412
+ yaxis_opts=opts.AxisOpts(
413
+ grid_index=1,
414
+ split_number=3,
415
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
416
+ axistick_opts=opts.AxisTickOpts(is_show=False),
417
+ splitline_opts=opts.SplitLineOpts(is_show=False),
418
+ axislabel_opts=opts.LabelOpts(is_show=True),
419
+ ),
420
+ )
421
+ )
422
+ overlap_kline_line = kline.overlap(dn_line)
423
+
424
+ for d1, d2 in zip(valid_up_point_list[:-1], valid_up_point_list[1:]):
425
+ dn_line = (
426
+ Line()
427
+ .add_xaxis(xaxis_data=[d1[0], d2[0]])
428
+ .add_yaxis(
429
+ series_name="Support",
430
+ y_axis=[d1[1], d2[1]],
431
+ is_smooth=False,
432
+ # linestyle_opts=opts.LineStyleOpts(opacity=0.5),
433
+ linestyle_opts=opts.LineStyleOpts(color="red", width=2, type_="dotted"),
434
+ label_opts=opts.LabelOpts(is_show=False),
435
+ symbol="arrow",
436
+ )
437
+ .set_global_opts(
438
+ xaxis_opts=opts.AxisOpts(
439
+ type_="category",
440
+ grid_index=1,
441
+ axislabel_opts=opts.LabelOpts(is_show=False),
442
+ ),
443
+ yaxis_opts=opts.AxisOpts(
444
+ grid_index=1,
445
+ split_number=3,
446
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
447
+ axistick_opts=opts.AxisTickOpts(is_show=False),
448
+ splitline_opts=opts.SplitLineOpts(is_show=False),
449
+ axislabel_opts=opts.LabelOpts(is_show=True),
450
+ ),
451
+ )
452
+ )
453
+ overlap_kline_line = kline.overlap(dn_line)
454
+
455
+ # Add buy/sell points
456
+ # Open long
457
+ bk_df = df[df.index.isin([str(i[0]) for i in bk_list])]
458
+ bk_c = (
459
+ EffectScatter()
460
+ .add_xaxis(bk_df.index)
461
+ .add_yaxis(
462
+ "", bk_df.low, color="red", symbol="image://c:/result/img/open_long.png", symbol_size=10
463
+ )
464
+ .set_global_opts(title_opts=opts.TitleOpts(title="buy"))
465
+ )
466
+ overlap_kline_line = kline.overlap(bk_c)
467
+ # Close long
468
+ bp_df = df[df.index.isin([str(i[0]) for i in bp_list])]
469
+ bp_c = (
470
+ EffectScatter()
471
+ .add_xaxis(bp_df.index)
472
+ .add_yaxis(
473
+ "",
474
+ bp_df.high,
475
+ color="green",
476
+ symbol="image://c:/result/img/close_long.png",
477
+ symbol_size=10,
478
+ )
479
+ .set_global_opts(title_opts=opts.TitleOpts(title="=sell"))
480
+ )
481
+ overlap_kline_line = kline.overlap(bp_c)
482
+ # Long position line segment
483
+ for bk, bp in zip([str(i[0]) for i in bk_list], [str(i[0]) for i in bp_list]):
484
+ try:
485
+ bk_df = df[df.index >= bk]
486
+ bk_price = list(bk_df["open"])[1]
487
+ bp_df = df[df.index >= bp]
488
+ bp_price = list(bp_df["open"])[1]
489
+ long_line = (
490
+ Line()
491
+ .add_xaxis(xaxis_data=[bk, bp])
492
+ .add_yaxis(
493
+ series_name="long_signal",
494
+ y_axis=[bk_price, bp_price],
495
+ is_smooth=False,
496
+ # linestyle_opts=opts.LineStyleOpts(opacity=0.5),
497
+ linestyle_opts=opts.LineStyleOpts(color="red", width=5, type_="dotted"),
498
+ label_opts=opts.LabelOpts(is_show=False),
499
+ symbol="arrow",
500
+ )
501
+ .set_global_opts(
502
+ xaxis_opts=opts.AxisOpts(
503
+ type_="category",
504
+ grid_index=1,
505
+ axislabel_opts=opts.LabelOpts(is_show=False),
506
+ ),
507
+ yaxis_opts=opts.AxisOpts(
508
+ grid_index=1,
509
+ split_number=3,
510
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
511
+ axistick_opts=opts.AxisTickOpts(is_show=False),
512
+ splitline_opts=opts.SplitLineOpts(is_show=False),
513
+ axislabel_opts=opts.LabelOpts(is_show=True),
514
+ ),
515
+ )
516
+ )
517
+ overlap_kline_line = kline.overlap(long_line)
518
+ except Exception as e:
519
+ logger.warning("Failed to create long line overlay: %s", e)
520
+
521
+ sk_df = df[df.index.isin([str(i[0]) for i in sk_list])]
522
+ sk_c = (
523
+ EffectScatter()
524
+ .add_xaxis(sk_df.index)
525
+ .add_yaxis(
526
+ "",
527
+ sk_df.high,
528
+ color="green",
529
+ symbol="image://c:/result/img/open_short.png",
530
+ symbol_size=10,
531
+ )
532
+ .set_global_opts(title_opts=opts.TitleOpts(title="sellshort"))
533
+ )
534
+ overlap_kline_line = kline.overlap(sk_c)
535
+
536
+ sp_df = df[df.index.isin([str(i[0]) for i in sp_list])]
537
+ sp_c = (
538
+ EffectScatter()
539
+ .add_xaxis(sp_df.index)
540
+ .add_yaxis(
541
+ "",
542
+ sp_df.low,
543
+ color="red",
544
+ symbol="image://c:/result/img/close_short.png",
545
+ symbol_size=10,
546
+ )
547
+ .set_global_opts(title_opts=opts.TitleOpts(title="buytocover"))
548
+ )
549
+ overlap_kline_line = kline.overlap(sp_c)
550
+
551
+ # Short position line segment
552
+ for sk, sp in zip([str(i[0]) for i in sk_list], [str(i[0]) for i in sp_list]):
553
+ try:
554
+ sk_df = df[df.index >= sk]
555
+ sk = list(sk_df.index)[1]
556
+ sk_price = list(sk_df["open"])[1]
557
+ sp_df = df[df.index >= sp]
558
+ sp = list(sp_df.index)[1]
559
+ sp_price = list(sp_df["open"])[1]
560
+ short_line = (
561
+ Line()
562
+ .add_xaxis(xaxis_data=[sk, sp])
563
+ .add_yaxis(
564
+ series_name="short_signal",
565
+ y_axis=[sk_price, sp_price],
566
+ is_smooth=False,
567
+ # linestyle_opts=opts.LineStyleOpts(opacity=0.5),
568
+ linestyle_opts=opts.LineStyleOpts(color="green", width=5, type_="dotted"),
569
+ label_opts=opts.LabelOpts(is_show=False),
570
+ symbol="arrow",
571
+ )
572
+ .set_global_opts(
573
+ xaxis_opts=opts.AxisOpts(
574
+ type_="category",
575
+ grid_index=1,
576
+ axislabel_opts=opts.LabelOpts(is_show=False),
577
+ ),
578
+ yaxis_opts=opts.AxisOpts(
579
+ grid_index=1,
580
+ split_number=3,
581
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
582
+ axistick_opts=opts.AxisTickOpts(is_show=False),
583
+ splitline_opts=opts.SplitLineOpts(is_show=False),
584
+ axislabel_opts=opts.LabelOpts(is_show=True),
585
+ ),
586
+ )
587
+ )
588
+ overlap_kline_line = kline.overlap(short_line)
589
+ except Exception as e:
590
+ logger.warning("Failed to create short line overlay: %s", e)
591
+
592
+ # Bar-1
593
+ bar_1 = (
594
+ Bar()
595
+ .add_xaxis(xaxis_data=data["times"])
596
+ .add_yaxis(
597
+ series_name="Volumn",
598
+ y_axis=data["vols"],
599
+ xaxis_index=1,
600
+ yaxis_index=1,
601
+ label_opts=opts.LabelOpts(is_show=False),
602
+ # According to echarts demo original version, it's written like this
603
+ # itemstyle_opts=opts.ItemStyleOpts(
604
+ # color=JsCode("""
605
+ # function(params) {
606
+ # var colorList;
607
+ # if (data.datas[params.dataIndex][1]>data.datas[params.dataIndex][0]) {
608
+ # colorList = '#ef232a';
609
+ # } else {
610
+ # colorList = '#14b143';
611
+ # }
612
+ # return colorList;
613
+ # }
614
+ # """)
615
+ # )
616
+ # After improvement, after add_js_funcs in grid, it becomes as follows
617
+ itemstyle_opts=opts.ItemStyleOpts(color=JsCode("""
618
+ function(params) {
619
+ var colorList;
620
+ if (barData[params.dataIndex][1] > barData[params.dataIndex][0]) {
621
+ colorList = '#ef232a';
622
+ } else {
623
+ colorList = '#14b143';
624
+ }
625
+ return colorList;
626
+ }
627
+ """)),
628
+ )
629
+ .set_global_opts(
630
+ xaxis_opts=opts.AxisOpts(
631
+ type_="category",
632
+ grid_index=1,
633
+ axislabel_opts=opts.LabelOpts(is_show=False),
634
+ ),
635
+ legend_opts=opts.LegendOpts(is_show=False),
636
+ )
637
+ )
638
+
639
+ # Bar-2 (Overlap Bar + Line)
640
+ bar_2 = (
641
+ Bar()
642
+ .add_xaxis(xaxis_data=data["times"])
643
+ .add_yaxis(
644
+ series_name="MACD",
645
+ y_axis=data["macds"],
646
+ xaxis_index=2,
647
+ yaxis_index=2,
648
+ label_opts=opts.LabelOpts(is_show=False),
649
+ itemstyle_opts=opts.ItemStyleOpts(color=JsCode("""
650
+ function(params) {
651
+ var colorList;
652
+ if (params.data >= 0) {
653
+ colorList = '#ef232a';
654
+ } else {
655
+ colorList = '#14b143';
656
+ }
657
+ return colorList;
658
+ }
659
+ """)),
660
+ )
661
+ .set_global_opts(
662
+ xaxis_opts=opts.AxisOpts(
663
+ type_="category",
664
+ grid_index=2,
665
+ axislabel_opts=opts.LabelOpts(is_show=False),
666
+ ),
667
+ yaxis_opts=opts.AxisOpts(
668
+ grid_index=2,
669
+ split_number=4,
670
+ axisline_opts=opts.AxisLineOpts(is_on_zero=False),
671
+ axistick_opts=opts.AxisTickOpts(is_show=False),
672
+ splitline_opts=opts.SplitLineOpts(is_show=False),
673
+ axislabel_opts=opts.LabelOpts(is_show=True),
674
+ ),
675
+ legend_opts=opts.LegendOpts(is_show=False),
676
+ )
677
+ )
678
+
679
+ line_2 = (
680
+ Line()
681
+ .add_xaxis(xaxis_data=data["times"])
682
+ .add_yaxis(
683
+ series_name="DIF",
684
+ y_axis=data["difs"],
685
+ xaxis_index=2,
686
+ yaxis_index=2,
687
+ label_opts=opts.LabelOpts(is_show=False),
688
+ )
689
+ .add_yaxis(
690
+ series_name="DIF",
691
+ y_axis=data["deas"],
692
+ xaxis_index=2,
693
+ yaxis_index=2,
694
+ label_opts=opts.LabelOpts(is_show=False),
695
+ )
696
+ .set_global_opts(legend_opts=opts.LegendOpts(is_show=False))
697
+ )
698
+ # Bottom bar chart and line chart
699
+ overlap_bar_line = bar_2.overlap(line_2)
700
+
701
+ # Final Grid
702
+ grid_chart = Grid(init_opts=opts.InitOpts(width="1400px", height="800px"))
703
+
704
+ # This is to write data.datas into html, haven't figured out how to pass values across series
705
+ # Code in demo also uses global variables
706
+ grid_chart.add_js_funcs("var barData = {}".format(data["datas"]))
707
+
708
+ # K-line chart and MA5 line chart
709
+ grid_chart.add(
710
+ overlap_kline_line,
711
+ grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", height="60%"),
712
+ )
713
+ # Volume bar chart
714
+ grid_chart.add(
715
+ bar_1,
716
+ grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", pos_top="71%", height="10%"),
717
+ )
718
+ # MACD DIFS DEAS
719
+ grid_chart.add(
720
+ overlap_bar_line,
721
+ grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", pos_top="82%", height="14%"),
722
+ )
723
+ grid_chart.render("c:/result/test_price_action_kline_chart.html")
724
+
725
+
726
+ class PInfo:
727
+ """Container for plotting information and state.
728
+
729
+ This class maintains all the state information needed during
730
+ the plotting process, including figure references, axes,
731
+ color schemes, and layout information.
732
+
733
+ Attributes:
734
+ sch: PlotScheme instance with plotting configuration
735
+ nrows: Total number of rows in the plot
736
+ row: Current row index
737
+ clock: Strategy or data object providing time reference
738
+ x: X-axis data points
739
+ xlen: Length of x-axis data
740
+ sharex: Shared x-axis reference
741
+ figs: List of figure objects
742
+ cursors: List of MultiCursor objects
743
+ daxis: Ordered dictionary mapping objects to axes
744
+ vaxis: List of vertical (twinx) axes
745
+ zorder: Dictionary mapping axes to z-order values
746
+ coloridx: Dictionary tracking color index per axis
747
+ handles: Dictionary of legend handles per axis
748
+ labels: Dictionary of legend labels per axis
749
+ legpos: Dictionary tracking legend position per axis
750
+ prop: FontProperties for subplot text
751
+ """
752
+
753
+ def __init__(self, sch):
754
+ """Initialize PInfo with plotting scheme.
755
+
756
+ Args:
757
+ sch: PlotScheme instance with plotting configuration
758
+ """
759
+ self.sch = sch
760
+ self.nrows = 0
761
+ self.row = 0
762
+ self.clock = None
763
+ self.x = None
764
+ self.xlen = 0
765
+ self.sharex = None
766
+ self.figs = []
767
+ self.cursors = []
768
+ self.daxis = collections.OrderedDict()
769
+ self.vaxis = []
770
+ self.zorder = {}
771
+ self.coloridx = collections.defaultdict(lambda: -1)
772
+ self.handles = collections.defaultdict(list)
773
+ self.labels = collections.defaultdict(list)
774
+ self.legpos = collections.defaultdict(int)
775
+
776
+ self.prop = mfontmgr.FontProperties(size=self.sch.subtxtsize)
777
+
778
+ def newfig(self, figid, numfig, mpyplot):
779
+ """Create a new matplotlib figure.
780
+
781
+ Args:
782
+ figid: Base figure identifier
783
+ numfig: Figure number suffix
784
+ mpyplot: Matplotlib pyplot module
785
+
786
+ Returns:
787
+ Figure object
788
+ """
789
+ fig = mpyplot.figure(figid + numfig)
790
+ self.figs.append(fig)
791
+ self.daxis = collections.OrderedDict()
792
+ self.vaxis = []
793
+ self.row = 0
794
+ self.sharex = None
795
+ return fig
796
+
797
+ def nextcolor(self, ax):
798
+ """Increment and get next color index for axis.
799
+
800
+ Args:
801
+ ax: Axis object
802
+
803
+ Returns:
804
+ Next color index
805
+ """
806
+ self.coloridx[ax] += 1
807
+ return self.coloridx[ax]
808
+
809
+ def color(self, ax):
810
+ """Get current color for axis.
811
+
812
+ Args:
813
+ ax: Axis object
814
+
815
+ Returns:
816
+ Color string for the current color index
817
+ """
818
+ return self.sch.color(self.coloridx[ax])
819
+
820
+ def zordernext(self, ax):
821
+ """Get next z-order value for axis.
822
+
823
+ Args:
824
+ ax: Axis object
825
+
826
+ Returns:
827
+ Next z-order value (slightly higher or lower than current)
828
+ """
829
+ z = self.zorder[ax]
830
+ if self.sch.zdown:
831
+ return z * 0.9999
832
+ return z * 1.0001
833
+
834
+ def zordercur(self, ax):
835
+ """Get current z-order value for axis.
836
+
837
+ Args:
838
+ ax: Axis object
839
+
840
+ Returns:
841
+ Current z-order value
842
+ """
843
+ return self.zorder[ax]
844
+
845
+
846
+ class Plot_OldSync(ParameterizedBase):
847
+ """Matplotlib-based plotting class for backtrader strategies.
848
+
849
+ This class provides the main plotting functionality for backtrader,
850
+ creating charts with price data, indicators, volume, and trading signals.
851
+
852
+ Attributes:
853
+ scheme: PlotScheme instance with plotting configuration
854
+ """
855
+
856
+ scheme = ParameterDescriptor(default=PlotScheme(), doc="Plotting scheme to use")
857
+
858
+ def __init__(self, **kwargs):
859
+ """Initialize Plot_OldSync with plotting scheme parameters.
860
+
861
+ Args:
862
+ **kwargs: Plotting scheme parameters to override defaults
863
+ """
864
+ # First call parent class initialization, so self.p can be set correctly
865
+ super().__init__()
866
+
867
+ # Then set scheme attributes
868
+ for pname, pvalue in kwargs.items():
869
+ setattr(self.p.scheme, pname, pvalue)
870
+
871
+ def drawtag(self, ax, x, y, facecolor, edgecolor, alpha=0.9, **kwargs):
872
+ """Draw a text tag on the chart at specified coordinates.
873
+
874
+ Args:
875
+ ax: Axis object to draw on
876
+ x: X coordinate
877
+ y: Y coordinate
878
+ facecolor: Background color of the tag
879
+ edgecolor: Border color of the tag
880
+ alpha: Transparency level (default: 0.9)
881
+ **kwargs: Additional keyword arguments for text
882
+ """
883
+ ax.text(
884
+ x,
885
+ y,
886
+ "%.2f" % y,
887
+ va="center",
888
+ ha="left",
889
+ fontsize=self.pinf.sch.subtxtsize,
890
+ bbox={
891
+ "boxstyle": tag_box_style,
892
+ "facecolor": facecolor,
893
+ "edgecolor": edgecolor,
894
+ "alpha": alpha,
895
+ },
896
+ # 3.0 is the minimum default for text
897
+ zorder=self.pinf.zorder[ax] + 3.0,
898
+ **kwargs,
899
+ )
900
+
901
+ def plot(self, strategy, figid=0, numfigs=1, iplot=True, start=None, end=None, **kwargs):
902
+ """Generate plots for a backtrader strategy.
903
+
904
+ Creates matplotlib figures with price data, indicators, volume,
905
+ and other plot elements. Supports multiple figures and date ranges.
906
+
907
+ Args:
908
+ strategy: Strategy object with data and indicators
909
+ figid: Base figure identifier (default: 0)
910
+ numfigs: Number of figures to create (default: 1)
911
+ iplot: Whether to use interactive plotting (default: True)
912
+ start: Start date or index (default: None for beginning)
913
+ end: End date or index (default: None for end)
914
+ **kwargs: Additional plotting arguments
915
+
916
+ Returns:
917
+ List of matplotlib Figure objects
918
+ """
919
+ # pfillers={}):
920
+ if not strategy.datas:
921
+ return None
922
+
923
+ if not len(strategy):
924
+ return None
925
+
926
+ self._iplot = iplot
927
+ if iplot:
928
+ if "ipykernel" in sys.modules:
929
+ matplotlib.use("nbagg")
930
+
931
+ # this import must not happen before matplotlib.use
932
+ import matplotlib.pyplot as mpyplot
933
+
934
+ self.mpyplot = mpyplot
935
+
936
+ self.pinf = PInfo(self.p.scheme)
937
+ self.sortdataindicators(strategy)
938
+ self.calcrows(strategy)
939
+
940
+ st_dtime = strategy.lines.datetime.plot()
941
+ if start is None:
942
+ start = 0
943
+ if end is None:
944
+ end = len(st_dtime)
945
+
946
+ if isinstance(start, datetime.date):
947
+ start = bisect.bisect_left(st_dtime, date2num(start))
948
+
949
+ if isinstance(end, datetime.date):
950
+ end = bisect.bisect_right(st_dtime, date2num(end))
951
+
952
+ if end < 0:
953
+ end = len(st_dtime) + 1 + end # -1 = len() -2 = len() - 1
954
+
955
+ slen = len(st_dtime[start:end])
956
+ d, m = divmod(slen, numfigs)
957
+ pranges = []
958
+ for i in range(numfigs):
959
+ a = d * i + start
960
+ if i == (numfigs - 1):
961
+ d += m # add a remainder to last stint
962
+ b = a + d
963
+
964
+ pranges.append([a, b, d])
965
+
966
+ figs = []
967
+
968
+ for numfig in range(numfigs):
969
+ # prepare a figure
970
+ fig = self.pinf.newfig(figid, numfig, self.mpyplot)
971
+ figs.append(fig)
972
+
973
+ self.pinf.pstart, self.pinf.pend, self.pinf.psize = pranges[numfig]
974
+ self.pinf.xstart = self.pinf.pstart
975
+ self.pinf.xend = self.pinf.pend
976
+
977
+ self.pinf.clock = strategy
978
+ self.pinf.xreal = self.pinf.clock.datetime.plot(self.pinf.pstart, self.pinf.psize)
979
+ self.pinf.xlen = len(self.pinf.xreal)
980
+ self.pinf.x = list(range(self.pinf.xlen))
981
+ # self.pinf.pfillers = {None: []}
982
+ # for key, val in pfillers.items():
983
+ # pfstart = bisect.bisect_left(val, self.pinf.pstart)
984
+ # pfend = bisect.bisect_right(val, self.pinf.pend)
985
+ # self.pinf.pfillers[key] = val[pfstart:pfend]
986
+
987
+ # Do the plotting
988
+ # Things that go always at the top (observers)
989
+ self.pinf.xdata = self.pinf.x
990
+ for ptop in self.dplotstop:
991
+ self.plotind(None, ptop, subinds=self.dplotsover[ptop])
992
+
993
+ # Create the rest on a per-data basis
994
+ dt0, dt1 = self.pinf.xreal[0], self.pinf.xreal[-1]
995
+ for data in strategy.datas:
996
+ if not data.plotinfo.plot:
997
+ continue
998
+
999
+ self.pinf.xdata = self.pinf.x
1000
+ xd = data.datetime.plotrange(self.pinf.xstart, self.pinf.xend)
1001
+ if len(xd) < self.pinf.xlen:
1002
+ self.pinf.xdata = xdata = []
1003
+ xreal = self.pinf.xreal
1004
+ dts = data.datetime.plot()
1005
+ xtemp = []
1006
+ for dt in (x for x in dts if dt0 <= x <= dt1):
1007
+ dtidx = bisect.bisect_left(xreal, dt)
1008
+ xdata.append(dtidx)
1009
+ xtemp.append(dt)
1010
+
1011
+ self.pinf.xstart = bisect.bisect_left(dts, xtemp[0])
1012
+ self.pinf.xend = bisect.bisect_right(dts, xtemp[-1])
1013
+
1014
+ for ind in self.dplotsup[data]:
1015
+ self.plotind(
1016
+ data,
1017
+ ind,
1018
+ subinds=self.dplotsover[ind],
1019
+ upinds=self.dplotsup[ind],
1020
+ downinds=self.dplotsdown[ind],
1021
+ )
1022
+
1023
+ self.plotdata(data, self.dplotsover[data])
1024
+
1025
+ for ind in self.dplotsdown[data]:
1026
+ self.plotind(
1027
+ data,
1028
+ ind,
1029
+ subinds=self.dplotsover[ind],
1030
+ upinds=self.dplotsup[ind],
1031
+ downinds=self.dplotsdown[ind],
1032
+ )
1033
+
1034
+ cursor = MultiCursor(
1035
+ fig.canvas,
1036
+ list(self.pinf.daxis.values()),
1037
+ useblit=True,
1038
+ horizOn=True,
1039
+ vertOn=True,
1040
+ horizMulti=False,
1041
+ vertMulti=True,
1042
+ horizShared=True,
1043
+ vertShared=False,
1044
+ color="black",
1045
+ lw=1,
1046
+ ls=":",
1047
+ )
1048
+
1049
+ self.pinf.cursors.append(cursor)
1050
+
1051
+ # Put the subplots as indicated by hspace
1052
+ fig.subplots_adjust(
1053
+ hspace=self.pinf.sch.plotdist, top=0.98, left=0.05, bottom=0.05, right=0.95
1054
+ )
1055
+
1056
+ laxis = list(self.pinf.daxis.values())
1057
+
1058
+ # Find the last axis which is not a twinx (date locator fails there)
1059
+ i = -1
1060
+ while True:
1061
+ lastax = laxis[i]
1062
+ if lastax not in self.pinf.vaxis:
1063
+ break
1064
+
1065
+ i -= 1
1066
+
1067
+ self.setlocators(lastax) # place the locators/fmts
1068
+
1069
+ # Applying fig.autofmt_xdate if the data axis is the last one
1070
+ # breaks the presentation of the date labels. why?
1071
+ # Applying the manual rotation with setp cures the problem
1072
+ # but the labels from all axis but the last have to be hidden
1073
+ for ax in laxis:
1074
+ self.mpyplot.setp(ax.get_xticklabels(), visible=False)
1075
+
1076
+ self.mpyplot.setp(
1077
+ lastax.get_xticklabels(), visible=True, rotation=self.pinf.sch.tickrotation
1078
+ )
1079
+
1080
+ # Things must be tight along the x-axis (to fill both ends)
1081
+ axtight = "x" if not self.pinf.sch.ytight else "both"
1082
+ # self.mpyplot.xticks(pd.date_range(start,end),rotation=90)
1083
+ self.mpyplot.autoscale(enable=True, axis=axtight, tight=True)
1084
+
1085
+ return figs
1086
+
1087
+ def setlocators(self, ax):
1088
+ """Set date locators and formatters for x-axis.
1089
+
1090
+ Configures automatic date formatting based on the timeframe
1091
+ of the data being plotted.
1092
+
1093
+ Args:
1094
+ ax: Axis object to configure
1095
+ """
1096
+ tframe = getattr(self.pinf.clock, "_timeframe", TimeFrame.Days)
1097
+
1098
+ if self.pinf.sch.fmt_x_data is None:
1099
+ if tframe == TimeFrame.Years:
1100
+ fmtdata = "%Y"
1101
+ elif tframe == TimeFrame.Months:
1102
+ fmtdata = "%Y-%m"
1103
+ elif tframe == TimeFrame.Weeks or tframe == TimeFrame.Days:
1104
+ fmtdata = "%Y-%m-%d"
1105
+ elif tframe == TimeFrame.Minutes:
1106
+ fmtdata = "%Y-%m-%d %H:%M"
1107
+ elif tframe == TimeFrame.Seconds:
1108
+ fmtdata = "%Y-%m-%d %H:%M:%S"
1109
+ elif tframe == TimeFrame.MicroSeconds or tframe == TimeFrame.Ticks:
1110
+ fmtdata = "%Y-%m-%d %H:%M:%S.%f"
1111
+ else:
1112
+ fmtdata = self.pinf.sch.fmt_x_data
1113
+
1114
+ fordata = MyDateFormatter(self.pinf.xreal, fmt=fmtdata)
1115
+ for dax in self.pinf.daxis.values():
1116
+ dax.fmt_xdata = fordata
1117
+
1118
+ # Major locator / formatter
1119
+ locmajor = loc.AutoDateLocator(self.pinf.xreal)
1120
+ ax.xaxis.set_major_locator(locmajor)
1121
+ if self.pinf.sch.fmt_x_ticks is None:
1122
+ autofmt = loc.AutoDateFormatter(self.pinf.xreal, locmajor)
1123
+ else:
1124
+ autofmt = MyDateFormatter(self.pinf.xreal, fmt=self.pinf.sch.fmt_x_ticks)
1125
+ ax.xaxis.set_major_formatter(autofmt)
1126
+
1127
+ def calcrows(self, strategy):
1128
+ """Calculate the total number of rows needed for plotting.
1129
+
1130
+ Determines how many subplot rows are needed based on data feeds,
1131
+ indicators, observers, and volume plots.
1132
+
1133
+ Args:
1134
+ strategy: Strategy object with data and indicators
1135
+ """
1136
+ # Calculate the total number of rows
1137
+ rowsmajor = self.pinf.sch.rowsmajor
1138
+ rowsminor = self.pinf.sch.rowsminor
1139
+ nrows = 0
1140
+
1141
+ datasnoplot = 0
1142
+ for data in strategy.datas:
1143
+ if not data.plotinfo.plot:
1144
+ # neither data nor indicators nor volume add rows
1145
+ datasnoplot += 1
1146
+ self.dplotsup.pop(data, None)
1147
+ self.dplotsdown.pop(data, None)
1148
+ self.dplotsover.pop(data, None)
1149
+
1150
+ else:
1151
+ pmaster = data.plotinfo.plotmaster
1152
+ if pmaster is data:
1153
+ pmaster = None
1154
+ if pmaster is not None:
1155
+ # data doesn't add a row, but volume may
1156
+ if self.pinf.sch.volume:
1157
+ nrows += rowsminor
1158
+ else:
1159
+ # data adds rows, volume may
1160
+ nrows += rowsmajor
1161
+ if self.pinf.sch.volume and not self.pinf.sch.voloverlay:
1162
+ nrows += rowsminor
1163
+
1164
+ if False:
1165
+ # Datas and volumes
1166
+ nrows += (len(strategy.datas) - datasnoplot) * rowsmajor
1167
+ if self.pinf.sch.volume and not self.pinf.sch.voloverlay:
1168
+ nrows += (len(strategy.datas) - datasnoplot) * rowsminor
1169
+
1170
+ # top indicators/observers
1171
+ nrows += len(self.dplotstop) * rowsminor
1172
+
1173
+ # indicators above datas
1174
+ nrows += sum(len(v) for v in self.dplotsup.values())
1175
+ nrows += sum(len(v) for v in self.dplotsdown.values())
1176
+
1177
+ self.pinf.nrows = nrows
1178
+
1179
+ def newaxis(self, obj, rowspan):
1180
+ """Create a new axis for plotting.
1181
+
1182
+ Creates a subplot axis with the specified row span and
1183
+ configures it with appropriate settings.
1184
+
1185
+ Args:
1186
+ obj: Object to associate with this axis
1187
+ rowspan: Number of rows this axis should span
1188
+
1189
+ Returns:
1190
+ Axis object
1191
+ """
1192
+ ax = self.mpyplot.subplot2grid(
1193
+ (self.pinf.nrows, 1), (self.pinf.row, 0), rowspan=rowspan, sharex=self.pinf.sharex
1194
+ )
1195
+
1196
+ # update the sharex information if not available
1197
+ if self.pinf.sharex is None:
1198
+ self.pinf.sharex = ax
1199
+
1200
+ # update the row index with the taken rows
1201
+ self.pinf.row += rowspan
1202
+
1203
+ # save the mapping indicator - axis and return
1204
+ self.pinf.daxis[obj] = ax
1205
+
1206
+ # Activate grid in all axes if requested
1207
+ ax.yaxis.tick_right()
1208
+ ax.grid(self.pinf.sch.grid, which="both")
1209
+
1210
+ return ax
1211
+
1212
+ def plotind(self, iref, ind, subinds=None, upinds=None, downinds=None, masterax=None):
1213
+ """Plot an indicator with optional sub-indicators.
1214
+
1215
+ Plots an indicator on an axis, handling line styling, legends,
1216
+ fills, and recursively plotting sub-indicators.
1217
+
1218
+ Args:
1219
+ iref: Reference object (usually data feed)
1220
+ ind: Indicator object to plot
1221
+ subinds: List of sub-indicators to plot on same axis (default: None)
1222
+ upinds: List of indicators to plot above (default: None)
1223
+ downinds: List of indicators to plot below (default: None)
1224
+ masterax: Master axis to plot on (default: None to create new)
1225
+ """
1226
+ # check subind
1227
+ subinds = subinds or []
1228
+ upinds = upinds or []
1229
+ downinds = downinds or []
1230
+
1231
+ # plot subindicators on self with independent axis above
1232
+ for upind in upinds:
1233
+ self.plotind(iref, upind)
1234
+
1235
+ # Get an axis for this plot
1236
+ ax = masterax or self.newaxis(ind, rowspan=self.pinf.sch.rowsminor)
1237
+
1238
+ indlabel = ind.plotlabel()
1239
+ if not isinstance(indlabel, str):
1240
+ indlabel = ind.__class__.__name__
1241
+
1242
+ # Scan lines quickly to find out if some lines have to be skipped for
1243
+ # legend (because matplotlib reorders the legend)
1244
+ toskip = 0
1245
+ for lineidx in range(ind.size()):
1246
+ line = ind.lines[lineidx]
1247
+ linealias = ind.lines._getlinealias(lineidx)
1248
+ lineplotinfo = getattr(ind.plotlines, "_%d" % lineidx, None)
1249
+ if not lineplotinfo:
1250
+ lineplotinfo = getattr(ind.plotlines, linealias, None)
1251
+ if not lineplotinfo:
1252
+ lineplotinfo = AutoInfoClass()
1253
+ pltmethod = lineplotinfo._get("_method", "plot")
1254
+ if pltmethod != "plot":
1255
+ toskip += 1 - lineplotinfo._get("_plotskip", False)
1256
+
1257
+ if toskip >= ind.size():
1258
+ toskip = 0
1259
+
1260
+ for lineidx in range(ind.size()):
1261
+ line = ind.lines[lineidx]
1262
+ linealias = ind.lines._getlinealias(lineidx)
1263
+
1264
+ lineplotinfo = getattr(ind.plotlines, "_%d" % lineidx, None)
1265
+ if not lineplotinfo:
1266
+ lineplotinfo = getattr(ind.plotlines, linealias, None)
1267
+
1268
+ if not lineplotinfo:
1269
+ lineplotinfo = AutoInfoClass()
1270
+
1271
+ if lineplotinfo._get("_plotskip", False):
1272
+ continue
1273
+
1274
+ # Legend label only when plotting 1st line
1275
+ if masterax and not ind.plotinfo.plotlinelabels:
1276
+ label = indlabel * (not toskip) or "_nolegend"
1277
+ else:
1278
+ label = (indlabel + "\n") * (not toskip)
1279
+ label += lineplotinfo._get("_name", "") or linealias
1280
+
1281
+ toskip -= 1 # one line less until legend can be added
1282
+
1283
+ # plot data
1284
+ lplot = line.plotrange(self.pinf.xstart, self.pinf.xend)
1285
+
1286
+ # Global and generic for indicator
1287
+ if self.pinf.sch.linevalues and ind.plotinfo.plotlinevalues:
1288
+ plotlinevalue = lineplotinfo._get("_plotvalue", True)
1289
+ if len(lplot) > 0:
1290
+ if plotlinevalue and not math.isnan(lplot[-1]):
1291
+ label += " %.2f" % lplot[-1]
1292
+
1293
+ plotkwargs = {}
1294
+ get_linekwargs = getattr(lineplotinfo, "_getkwargs", None)
1295
+ linekwargs = get_linekwargs(skip_=True) if get_linekwargs is not None else {}
1296
+
1297
+ if linekwargs.get("color", None) is None:
1298
+ if not lineplotinfo._get("_samecolor", False):
1299
+ self.pinf.nextcolor(ax)
1300
+ plotkwargs["color"] = self.pinf.color(ax)
1301
+
1302
+ plotkwargs.update({"aa": True, "label": label})
1303
+ plotkwargs.update(**linekwargs)
1304
+
1305
+ if ax in self.pinf.zorder:
1306
+ plotkwargs["zorder"] = self.pinf.zordernext(ax)
1307
+
1308
+ pltmethod = getattr(ax, lineplotinfo._get("_method", "plot"))
1309
+
1310
+ xdata, lplotarray = self.pinf.xdata, lplot
1311
+
1312
+ # CRITICAL FIX: Check if array is empty to avoid dimension mismatch error
1313
+ # Fix dimension mismatch error: ValueError: x and y must have same first dimension
1314
+ if not lplotarray or len(lplotarray) == 0:
1315
+ # If data is empty, skip plotting
1316
+ plottedline = None
1317
+ return # Force exit from this line drawing
1318
+
1319
+ if lineplotinfo._get("_skipnan", False):
1320
+ # Get the full array and a mask to skipnan
1321
+ lplotarray = np.array(lplot)
1322
+ lplotmask = np.isfinite(lplotarray)
1323
+
1324
+ # Get both the axis and the data masked
1325
+ lplotarray = lplotarray[lplotmask]
1326
+ xdata = np.array(xdata)[lplotmask]
1327
+
1328
+ # Check again if array is empty
1329
+ if len(lplotarray) == 0 or len(xdata) == 0 or len(lplotarray) != len(xdata):
1330
+ plottedline = None
1331
+ return # Skip plotting
1332
+
1333
+ plottedline = pltmethod(xdata, lplotarray, **plotkwargs)
1334
+ try:
1335
+ plottedline = plottedline[0]
1336
+ except (TypeError, IndexError):
1337
+ # Possibly a container of artists (when plotting bars)
1338
+ logger.debug("plot:1338 ignored TypeError,IndexError")
1339
+
1340
+ self.pinf.zorder[ax] = plottedline.get_zorder()
1341
+
1342
+ vtags = lineplotinfo._get("plotvaluetags", True)
1343
+ if self.pinf.sch.valuetags and vtags:
1344
+ linetag = lineplotinfo._get("_plotvaluetag", True)
1345
+ if linetag and not math.isnan(lplot[-1]):
1346
+ # line has valid values, plot a tag for the last value
1347
+ self.drawtag(
1348
+ ax,
1349
+ len(self.pinf.xreal),
1350
+ lplot[-1],
1351
+ facecolor="white",
1352
+ edgecolor=self.pinf.color(ax),
1353
+ )
1354
+
1355
+ farts = (
1356
+ ("_gt", operator.gt),
1357
+ ("_lt", operator.lt),
1358
+ ("", None),
1359
+ )
1360
+ for fcmp, fop in farts:
1361
+ fattr = "_fill" + fcmp
1362
+ fref, fcol = lineplotinfo._get(fattr, (None, None))
1363
+ if fref is not None:
1364
+ y1 = np.array(lplot)
1365
+ if isinstance(fref, integer_types):
1366
+ y2 = np.full_like(y1, fref)
1367
+ else: # string, naming a line, nothing else is supported
1368
+ l2 = getattr(ind, fref)
1369
+ prl2 = l2.plotrange(self.pinf.xstart, self.pinf.xend)
1370
+ y2 = np.array(prl2)
1371
+ kwargs = {}
1372
+ if fop is not None:
1373
+ kwargs["where"] = fop(y1, y2)
1374
+
1375
+ falpha = self.pinf.sch.fillalpha
1376
+ if isinstance(fcol, (list, tuple)):
1377
+ fcol, falpha = fcol
1378
+
1379
+ ax.fill_between(
1380
+ self.pinf.xdata,
1381
+ y1,
1382
+ y2,
1383
+ facecolor=fcol,
1384
+ alpha=falpha,
1385
+ interpolate=True,
1386
+ **kwargs,
1387
+ )
1388
+
1389
+ # plot subindicators that were created on self
1390
+ for subind in subinds:
1391
+ self.plotind(iref, subind, subinds=self.dplotsover[subind], masterax=ax)
1392
+
1393
+ if not masterax:
1394
+ # adjust margin if requested ... general of particular
1395
+ ymargin = ind.plotinfo._get("plotymargin", 0.0) or 0.0
1396
+ ymargin = max(ymargin, self.pinf.sch.yadjust or 0.0)
1397
+ if ymargin:
1398
+ ax.margins(y=ymargin)
1399
+
1400
+ # Set specific or generic ticks
1401
+ yticks = ind.plotinfo._get("plotyticks", [])
1402
+ if not yticks:
1403
+ yticks = ind.plotinfo._get("plotyhlines", [])
1404
+
1405
+ if yticks:
1406
+ ax.set_yticks(yticks)
1407
+ else:
1408
+ locator = mticker.MaxNLocator(nbins=4, prune="both")
1409
+ ax.yaxis.set_major_locator(locator)
1410
+
1411
+ # Set specific hlines if asked to
1412
+ hlines = ind.plotinfo._get("plothlines", [])
1413
+ if not hlines:
1414
+ hlines = ind.plotinfo._get("plotyhlines", [])
1415
+ for hline in hlines or []:
1416
+ ax.axhline(
1417
+ hline,
1418
+ color=self.pinf.sch.hlinescolor,
1419
+ ls=self.pinf.sch.hlinesstyle,
1420
+ lw=self.pinf.sch.hlineswidth,
1421
+ )
1422
+
1423
+ if self.pinf.sch.legendind and ind.plotinfo._get("plotlegend", True):
1424
+ handles, labels = ax.get_legend_handles_labels()
1425
+ # Ensure that we have something to show
1426
+ if labels:
1427
+ # location can come from the user
1428
+ loc = getattr(ind.plotinfo, "legendloc", None) or self.pinf.sch.legendindloc
1429
+
1430
+ # Legend done here to ensure it includes all plots
1431
+ legend = ax.legend(
1432
+ loc=loc,
1433
+ numpoints=1,
1434
+ frameon=False,
1435
+ shadow=False,
1436
+ fancybox=False,
1437
+ prop=self.pinf.prop,
1438
+ )
1439
+
1440
+ # Matplotlib legend box defaults to center alignment; override to left
1441
+ legend._legend_box.align = "left"
1442
+
1443
+ # plot subindicators on self with independent axis below
1444
+ for downind in downinds:
1445
+ self.plotind(iref, downind)
1446
+
1447
+ def plotvolume(self, data, opens, highs, lows, closes, volumes, label):
1448
+ """Plot volume for a data feed.
1449
+
1450
+ Creates volume bars with appropriate coloring based on price movement.
1451
+ Can be overlaid on price chart or shown in separate subplot.
1452
+
1453
+ Args:
1454
+ data: Data feed object
1455
+ opens: Array of open prices
1456
+ highs: Array of high prices
1457
+ lows: Array of low prices
1458
+ closes: Array of close prices
1459
+ volumes: Array of volume values
1460
+ label: Label for the volume plot
1461
+
1462
+ Returns:
1463
+ Volume plot artist or None
1464
+ """
1465
+ pmaster = data.plotinfo.plotmaster
1466
+ if pmaster is data:
1467
+ pmaster = None
1468
+ voloverlay = self.pinf.sch.voloverlay and pmaster is None
1469
+
1470
+ # if sefl.pinf.sch.voloverlay:
1471
+ if voloverlay:
1472
+ rowspan = self.pinf.sch.rowsmajor
1473
+ else:
1474
+ rowspan = self.pinf.sch.rowsminor
1475
+
1476
+ ax = self.newaxis(data.volume, rowspan=rowspan)
1477
+
1478
+ # if self.pinf.sch.voloverlay:
1479
+ if voloverlay:
1480
+ volalpha = self.pinf.sch.voltrans
1481
+ else:
1482
+ volalpha = 1.0
1483
+
1484
+ maxvol = volylim = max(volumes)
1485
+ if maxvol:
1486
+ # Plot the volume (no matter if as overlay or standalone)
1487
+ vollabel = label
1488
+ (volplot,) = plot_volume(
1489
+ ax,
1490
+ self.pinf.xdata,
1491
+ opens,
1492
+ closes,
1493
+ volumes,
1494
+ colorup=self.pinf.sch.volup,
1495
+ colordown=self.pinf.sch.voldown,
1496
+ alpha=volalpha,
1497
+ label=vollabel,
1498
+ )
1499
+
1500
+ nbins = 6
1501
+ prune = "both"
1502
+ # if self.pinf.sch.voloverlay:
1503
+ if voloverlay:
1504
+ # store for a potential plot over it
1505
+ nbins = int(nbins / self.pinf.sch.volscaling)
1506
+ prune = None
1507
+
1508
+ volylim /= self.pinf.sch.volscaling
1509
+ ax.set_ylim(0, volylim, auto=True)
1510
+ else:
1511
+ # plot a legend
1512
+ handles, labels = ax.get_legend_handles_labels()
1513
+ if handles:
1514
+ # location can come from the user
1515
+ loc = getattr(data.plotinfo, "legendloc", None) or self.pinf.sch.legendindloc
1516
+
1517
+ # Legend done here to ensure it includes all plots
1518
+ ax.legend(
1519
+ loc=loc,
1520
+ numpoints=1,
1521
+ frameon=False,
1522
+ shadow=False,
1523
+ fancybox=False,
1524
+ prop=self.pinf.prop,
1525
+ )
1526
+
1527
+ locator = mticker.MaxNLocator(nbins=nbins, prune=prune)
1528
+ ax.yaxis.set_major_locator(locator)
1529
+ ax.yaxis.set_major_formatter(MyVolFormatter(maxvol))
1530
+
1531
+ if not maxvol:
1532
+ ax.set_yticks([])
1533
+ return None
1534
+
1535
+ return volplot
1536
+
1537
+ def plotdata(self, data, indicators):
1538
+ """Plot price data for a data feed.
1539
+
1540
+ Creates candlestick, bar, or line chart for price data along
1541
+ with volume and indicators. Handles overlay indicators and
1542
+ proper axis configuration.
1543
+
1544
+ Args:
1545
+ data: Data feed object with OHLCV data
1546
+ indicators: List of indicators to plot with this data
1547
+ """
1548
+ for ind in indicators:
1549
+ upinds = self.dplotsup[ind]
1550
+ for upind in upinds:
1551
+ self.plotind(
1552
+ data,
1553
+ upind,
1554
+ subinds=self.dplotsover[upind],
1555
+ upinds=self.dplotsup[upind],
1556
+ downinds=self.dplotsdown[upind],
1557
+ )
1558
+
1559
+ opens = data.open.plotrange(self.pinf.xstart, self.pinf.xend)
1560
+ highs = data.high.plotrange(self.pinf.xstart, self.pinf.xend)
1561
+ lows = data.low.plotrange(self.pinf.xstart, self.pinf.xend)
1562
+ closes = data.close.plotrange(self.pinf.xstart, self.pinf.xend)
1563
+ volumes = data.volume.plotrange(self.pinf.xstart, self.pinf.xend)
1564
+
1565
+ vollabel = "Volume"
1566
+ pmaster = data.plotinfo.plotmaster
1567
+ if pmaster is data:
1568
+ pmaster = None
1569
+
1570
+ datalabel = ""
1571
+ if hasattr(data, "_name") and data._name:
1572
+ datalabel += data._name
1573
+
1574
+ voloverlay = self.pinf.sch.voloverlay and pmaster is None
1575
+
1576
+ if not voloverlay:
1577
+ vollabel += f" ({datalabel})"
1578
+
1579
+ # if self.pinf.sch.volume and self.pinf.sch.voloverlay:
1580
+ axdatamaster = None
1581
+ if self.pinf.sch.volume and voloverlay:
1582
+ volplot = self.plotvolume(data, opens, highs, lows, closes, volumes, vollabel)
1583
+ axvol = self.pinf.daxis[data.volume]
1584
+ ax = axvol.twinx()
1585
+ self.pinf.daxis[data] = ax
1586
+ self.pinf.vaxis.append(ax)
1587
+ else:
1588
+ if pmaster is None:
1589
+ ax = self.newaxis(data, rowspan=self.pinf.sch.rowsmajor)
1590
+ elif getattr(data.plotinfo, "sameaxis", False):
1591
+ axdatamaster = self.pinf.daxis[pmaster]
1592
+ ax = axdatamaster
1593
+ else:
1594
+ axdatamaster = self.pinf.daxis[pmaster]
1595
+ ax = axdatamaster.twinx()
1596
+ self.pinf.vaxis.append(ax)
1597
+
1598
+ if hasattr(data, "_compression") and hasattr(data, "_timeframe"):
1599
+ tfname = TimeFrame.getname(data._timeframe, data._compression)
1600
+ datalabel += " (%d %s)" % (data._compression, tfname)
1601
+
1602
+ plinevalues = getattr(data.plotinfo, "plotlinevalues", True)
1603
+ if self.pinf.sch.style.startswith("line"):
1604
+ if self.pinf.sch.linevalues and plinevalues:
1605
+ datalabel += " C:%.2f" % closes[-1]
1606
+
1607
+ if axdatamaster is None:
1608
+ color = self.pinf.sch.loc
1609
+ else:
1610
+ self.pinf.nextcolor(axdatamaster)
1611
+ color = self.pinf.color(axdatamaster)
1612
+
1613
+ plotted = plot_lineonclose(ax, self.pinf.xdata, closes, color=color, label=datalabel)
1614
+ else:
1615
+ if self.pinf.sch.linevalues and plinevalues:
1616
+ datalabel += " O:{:.2f} H:{:.2f} L:{:.2f} C:{:.2f}".format(
1617
+ opens[-1],
1618
+ highs[-1],
1619
+ lows[-1],
1620
+ closes[-1],
1621
+ )
1622
+ if self.pinf.sch.style.startswith("candle"):
1623
+ plotted = plot_candlestick(
1624
+ ax,
1625
+ self.pinf.xdata,
1626
+ opens,
1627
+ highs,
1628
+ lows,
1629
+ closes,
1630
+ colorup=self.pinf.sch.barup,
1631
+ colordown=self.pinf.sch.bardown,
1632
+ label=datalabel,
1633
+ alpha=self.pinf.sch.baralpha,
1634
+ fillup=self.pinf.sch.barupfill,
1635
+ filldown=self.pinf.sch.bardownfill,
1636
+ )
1637
+
1638
+ elif self.pinf.sch.style.startswith("bar") or True:
1639
+ # final default option -- should be "else"
1640
+ plotted = plot_ohlc(
1641
+ ax,
1642
+ self.pinf.xdata,
1643
+ opens,
1644
+ highs,
1645
+ lows,
1646
+ closes,
1647
+ colorup=self.pinf.sch.barup,
1648
+ colordown=self.pinf.sch.bardown,
1649
+ label=datalabel,
1650
+ )
1651
+
1652
+ self.pinf.zorder[ax] = plotted[0].get_zorder()
1653
+
1654
+ # Code to place a label at the right-hand side with the last value
1655
+ vtags = data.plotinfo._get("plotvaluetags", True)
1656
+ if self.pinf.sch.valuetags and vtags:
1657
+ self.drawtag(
1658
+ ax, len(self.pinf.xreal), closes[-1], facecolor="white", edgecolor=self.pinf.sch.loc
1659
+ )
1660
+
1661
+ ax.yaxis.set_major_locator(mticker.MaxNLocator(prune="both"))
1662
+ # make sure "over" indicators do not change our scale
1663
+ if data.plotinfo._get("plotylimited", True):
1664
+ if axdatamaster is None:
1665
+ ax.set_ylim(ax.get_ylim())
1666
+
1667
+ if self.pinf.sch.volume:
1668
+ # if not self.pinf.sch.voloverlay:
1669
+ if not voloverlay:
1670
+ self.plotvolume(data, opens, highs, lows, closes, volumes, vollabel)
1671
+ else:
1672
+ # Prepare overlay scaling/pushup or manage own axis
1673
+ if self.pinf.sch.volpushup:
1674
+ # push up the overlaid axis by lowering the bottom limit
1675
+ axbot, axtop = ax.get_ylim()
1676
+ axbot *= 1.0 - self.pinf.sch.volpushup
1677
+ ax.set_ylim(axbot, axtop)
1678
+
1679
+ for ind in indicators:
1680
+ self.plotind(data, ind, subinds=self.dplotsover[ind], masterax=ax)
1681
+
1682
+ handles, labels = ax.get_legend_handles_labels()
1683
+ a = axdatamaster or ax
1684
+ if handles:
1685
+ # put data and volume legend entries in the 1st positions
1686
+ # because they are "collections" they are considered after Line2D
1687
+ # for the legend entries, which is not our desire
1688
+ # if self.pinf.sch.volume and self.pinf.sch.voloverlay:
1689
+
1690
+ ai = self.pinf.legpos[a]
1691
+ if self.pinf.sch.volume and voloverlay:
1692
+ if volplot:
1693
+ # even if volume plot was requested, there may be no volume
1694
+ labels.insert(ai, vollabel)
1695
+ handles.insert(ai, volplot)
1696
+
1697
+ didx = labels.index(datalabel)
1698
+ labels.insert(ai, labels.pop(didx))
1699
+ handles.insert(ai, handles.pop(didx))
1700
+
1701
+ if axdatamaster is None:
1702
+ self.pinf.handles[ax] = handles
1703
+ self.pinf.labels[ax] = labels
1704
+ else:
1705
+ self.pinf.handles[axdatamaster] = handles
1706
+ self.pinf.labels[axdatamaster] = labels
1707
+ # self.pinf.handles[axdatamaster].extend(handles)
1708
+ # self.pinf.labels[axdatamaster].extend(labels)
1709
+
1710
+ h = self.pinf.handles[a]
1711
+ labels = self.pinf.labels[a]
1712
+
1713
+ axlegend = a
1714
+ loc = getattr(data.plotinfo, "legendloc", None) or self.pinf.sch.legenddataloc
1715
+ legend = axlegend.legend(
1716
+ h,
1717
+ labels,
1718
+ loc=loc,
1719
+ frameon=False,
1720
+ shadow=False,
1721
+ fancybox=False,
1722
+ prop=self.pinf.prop,
1723
+ numpoints=1,
1724
+ ncol=1,
1725
+ )
1726
+
1727
+ # Matplotlib legend box defaults to center alignment; override to left
1728
+ legend._legend_box.align = "left"
1729
+
1730
+ for ind in indicators:
1731
+ downinds = self.dplotsdown[ind]
1732
+ for downind in downinds:
1733
+ self.plotind(
1734
+ data,
1735
+ downind,
1736
+ subinds=self.dplotsover[downind],
1737
+ upinds=self.dplotsup[downind],
1738
+ downinds=self.dplotsdown[downind],
1739
+ )
1740
+
1741
+ self.pinf.legpos[a] = len(self.pinf.handles[a])
1742
+
1743
+ if data.plotinfo._get("plotlog", False):
1744
+ a = axdatamaster or ax
1745
+ a.set_yscale("log")
1746
+
1747
+ def show(self):
1748
+ """Display the plot using matplotlib."""
1749
+ if getattr(self, "_iplot", True):
1750
+ self.mpyplot.show()
1751
+
1752
+ def savefig(self, fig, filename, width=16, height=9, dpi=300, tight=True):
1753
+ """Save figure to file.
1754
+
1755
+ Args:
1756
+ fig: Figure object to save
1757
+ filename: Output file path
1758
+ width: Figure width in inches (default: 16)
1759
+ height: Figure height in inches (default: 9)
1760
+ dpi: Resolution in dots per inch (default: 300)
1761
+ tight: Whether to use tight bounding box (default: True)
1762
+ """
1763
+ fig.set_size_inches(width, height)
1764
+ bbox_inches = "tight" * tight or None
1765
+ fig.savefig(filename, dpi=dpi, bbox_inches=bbox_inches)
1766
+
1767
+ def sortdataindicators(self, strategy):
1768
+ """Sort indicators and observers into plotting groups.
1769
+
1770
+ Organizes indicators and observers into groups for plotting:
1771
+ - Top: Observers that go above all data
1772
+ - Up: Indicators that plot above their data
1773
+ - Down: Indicators that plot below their data
1774
+ - Over: Indicators that overlay on their data
1775
+
1776
+ Args:
1777
+ strategy: Strategy object with indicators and observers
1778
+ """
1779
+ # These lists/dictionaries hold the subplots that go above each data
1780
+ self.dplotstop = []
1781
+ self.dplotsup = collections.defaultdict(list)
1782
+ self.dplotsdown = collections.defaultdict(list)
1783
+ self.dplotsover = collections.defaultdict(list)
1784
+
1785
+ # Sort observers in the different lists/dictionaries
1786
+ for x in strategy.getobservers():
1787
+ if not x.plotinfo.plot or x.plotinfo.plotskip:
1788
+ continue
1789
+
1790
+ if x.plotinfo.subplot:
1791
+ self.dplotstop.append(x)
1792
+ else:
1793
+ key = getattr(x._clock, "owner", x._clock)
1794
+ self.dplotsover[key].append(x)
1795
+
1796
+ # Sort indicators in the different lists/dictionaries
1797
+ for x in strategy.getindicators():
1798
+ if not hasattr(x, "plotinfo"):
1799
+ # no plotting support - so far LineSingle derived classes
1800
+ continue
1801
+
1802
+ if not x.plotinfo.plot or x.plotinfo.plotskip:
1803
+ continue
1804
+
1805
+ x._plotinit() # will be plotted ... call its init function
1806
+
1807
+ # support LineSeriesStub, which has "owner" to point to the data
1808
+ key = getattr(x._clock, "owner", x._clock)
1809
+ if key is strategy: # a LinesCoupler
1810
+ key = strategy.data
1811
+
1812
+ if getattr(x.plotinfo, "plotforce", False):
1813
+ if key not in strategy.datas:
1814
+ while True:
1815
+ if key not in strategy.datas:
1816
+ key = key._clock
1817
+ else:
1818
+ break
1819
+
1820
+ xpmaster = x.plotinfo.plotmaster
1821
+ if xpmaster is x:
1822
+ xpmaster = None
1823
+ if xpmaster is not None:
1824
+ key = xpmaster
1825
+
1826
+ if x.plotinfo.subplot and xpmaster is None:
1827
+ if x.plotinfo.plotabove:
1828
+ self.dplotsup[key].append(x)
1829
+ else:
1830
+ self.dplotsdown[key].append(x)
1831
+ else:
1832
+ self.dplotsover[key].append(x)
1833
+
1834
+
1835
+ def plot_results(results, file_name):
1836
+ """write by myself to plot the result, and I will update this function"""
1837
+ # Total leverage
1838
+ df1 = pd.DataFrame([results[0].analyzers._GrossLeverage.get_analysis()]).T
1839
+ df1.columns = ["GrossLeverage"]
1840
+ # Rolling log returns
1841
+ df2 = pd.DataFrame([results[0].analyzers._LogReturnsRolling.get_analysis()]).T
1842
+ df2.columns = ["log_return"]
1843
+
1844
+ # year_rate
1845
+ df3 = pd.DataFrame([results[0].analyzers._AnnualReturn.get_analysis()]).T
1846
+ df3.columns = ["year_rate"]
1847
+
1848
+ #
1849
+ df4 = pd.DataFrame(results[0].analyzers._PositionsValue.get_analysis()).T
1850
+ df4["total_position_value"] = df4.sum(axis=1)
1851
+
1852
+ GrossLeverage = go.Scatter(x=df1.index, y=df1.GrossLeverage, name="gross_leverage")
1853
+ log_return = go.Scatter(
1854
+ x=df2.index, y=df2.log_return, xaxis="x2", yaxis="y2", name="log_return"
1855
+ )
1856
+ cumsum_return = go.Scatter(
1857
+ x=df2.index, y=df2.log_return.cumsum(), xaxis="x2", yaxis="y2", name="cumsum_return"
1858
+ )
1859
+
1860
+ year_rate = go.Bar(x=df3.index, y=df3.year_rate, xaxis="x3", yaxis="y3", name="year_rate")
1861
+ total_position_value = go.Scatter(
1862
+ x=df4.index, y=df4.total_position_value, xaxis="x4", yaxis="y4", name="total_position_value"
1863
+ )
1864
+ data = [GrossLeverage, log_return, cumsum_return, year_rate, total_position_value]
1865
+ layout = go.Layout(
1866
+ xaxis={"domain": [0, 0.45]},
1867
+ yaxis={"domain": [0, 0.45]},
1868
+ xaxis2={"domain": [0.55, 1]},
1869
+ xaxis3={"domain": [0, 0.45], "anchor": "y3"},
1870
+ xaxis4={"domain": [0.55, 1], "anchor": "y4"},
1871
+ yaxis2={"domain": [0, 0.45], "anchor": "x2"},
1872
+ yaxis3={"domain": [0.55, 1]},
1873
+ yaxis4={"domain": [0.55, 1], "anchor": "x4"},
1874
+ )
1875
+ fig = go.Figure(data=data, layout=layout)
1876
+ py.offline.plot(fig, filename=file_name, auto_open=False)
1877
+
1878
+
1879
+ Plot = Plot_OldSync
1880
+
1881
+
1882
+ def create_table(df, max_rows=18):
1883
+ """Create HTML table from dataframe for Dash display.
1884
+
1885
+ Args:
1886
+ df: DataFrame to convert to table
1887
+ max_rows: Maximum number of rows to display (default: 18)
1888
+
1889
+ Returns:
1890
+ Dash HTML table object
1891
+ """
1892
+
1893
+ table = html.Table(
1894
+ # Header
1895
+ [html.Tr([html.Th(col) for col in df.columns])]
1896
+ +
1897
+ # Body
1898
+ [
1899
+ html.Tr([html.Td(df.iloc[i][col]) for col in df.columns])
1900
+ for i in range(min(len(df), max_rows))
1901
+ ]
1902
+ )
1903
+ return table
1904
+
1905
+
1906
+ def get_rate_sharpe_drawdown(data):
1907
+ """Calculate Sharpe ratio, annual return, and maximum drawdown.
1908
+
1909
+ For intraday data, extracts the last value of each day as the daily
1910
+ closing value. Assumes 252 trading days per year.
1911
+
1912
+ Args:
1913
+ data: DataFrame with datetime index and total_value column
1914
+
1915
+ Returns:
1916
+ Tuple of (sharpe_ratio, annual_return, max_drawdown)
1917
+ """
1918
+ # Calculate Sharpe ratio, compound annual return, maximum drawdown
1919
+ # For periods less than daily, extract the last value of each day as the final value of a trading day,
1920
+ # For futures minute data, it's not calculated based on 15:00 close, which may slightly affect Sharpe ratio and other indicators, but the impact is small.
1921
+ data.index = pd.to_datetime(data.index)
1922
+ data["date"] = [str(i)[:10] for i in data.index]
1923
+ data1 = data.drop_duplicates("date", keep="last")
1924
+ data1.index = pd.to_datetime(data1["date"])
1925
+ if len(data1) == 0:
1926
+ return np.nan, np.nan, np.nan
1927
+ try:
1928
+ # Assume 252 trading days in a year
1929
+ data1["rate1"] = np.log(data1["total_value"]) - np.log(data1["total_value"].shift(1))
1930
+ # data['rate2']=data['total_value'].pct_change()
1931
+ data1 = data1.dropna()
1932
+ sharpe_ratio = data1["rate1"].mean() * 252**0.5 / (data1["rate1"].std())
1933
+ # Annualized return is:
1934
+ value_list = list(data["total_value"])
1935
+ begin_value = value_list[0]
1936
+ end_value = value_list[-1]
1937
+ begin_date = data.index[0]
1938
+ end_date = data.index[-1]
1939
+ days = (end_date - begin_date).days
1940
+ # If the calculated actual return is negative, default to maximum of 0, return cannot be negative
1941
+ total_rate = max((end_value - begin_value) / begin_value, -0.9999)
1942
+ average_rate = (1 + total_rate) ** (1 / (days / 365)) - 1
1943
+ # Calculate maximum drawdown
1944
+ data["rate1"] = np.log(data["total_value"]) - np.log(data["total_value"].shift(1))
1945
+ df = data["rate1"].cumsum()
1946
+ df = df.dropna()
1947
+ # index_j = np.argmax(np.maximum.accumulate(df) - df) # End position
1948
+ index_j = np.argmax(np.array(np.maximum.accumulate(df) - df))
1949
+ index_i = np.argmax(np.array(df[:index_j])) # Start position
1950
+ max_drawdown = (np.e ** df[index_j] - np.e ** df[index_i]) / np.e ** df[index_i]
1951
+ """
1952
+ begin_max_drawdown_value = data['total_value'][index_i]
1953
+ end_max_drawdown_value = data['total_value'][index_j]
1954
+ # print("begin_max_drawdown_value",begin_max_drawdown_value) # Removed for performance
1955
+ # print("end_max_drawdown_value",end_max_drawdown_value) # Removed for performance
1956
+ maxdrawdown_rate = (end_max_drawdown_value -begin_max_drawdown_value)/begin_max_drawdown_value # Maximum drawdown ratio
1957
+ maxdrawdown_value = data['total_value'][index_j] -data['total_value'][index_i] #Maximum drawdown value
1958
+ # print("Maximum drawdown value is",maxdrawdown_value) # Removed for performance
1959
+ # print("Maximum drawdown ratio is",maxdrawdown_rate) # Removed for performance
1960
+ # Draw chart
1961
+ plt.plot(df[1:len(df)])
1962
+ plt.plot([index_i], [df[index_i]], 'o', color="r", markersize=10)
1963
+ plt.plot([index_j], [df[index_j]], 'o', color="blue", markersize=10)
1964
+ plt.show()
1965
+ """
1966
+ return sharpe_ratio, average_rate, max_drawdown
1967
+ except Exception as e:
1968
+ logger.warning("plot:1967 fallback on Exception")
1969
+ traceback.format_exception(type(e), e, e.__traceback__)
1970
+ return np.nan, np.nan, np.nan
1971
+
1972
+
1973
+ def get_year_return(data):
1974
+ """Calculate annualized annual return"""
1975
+ data.index = pd.to_datetime(data.index)
1976
+ data["year"] = [i.year for i in data.index]
1977
+ last_data = data.iloc[-1:, ::]
1978
+ data = data.drop_duplicates("year")
1979
+ # data = data.append(last_data)
1980
+ data = pd.concat([data, last_data], axis=0)
1981
+ data["next_year_value"] = data["total_value"].shift(-1)
1982
+ data["return"] = data["next_year_value"] / data["total_value"] - 1
1983
+ data = data.dropna()
1984
+ data["datetime"] = [str(i) + "-6-30" for i in data.year]
1985
+ data.index = pd.to_datetime(data.datetime)
1986
+ data = data[["return", "datetime"]]
1987
+ return data
1988
+
1989
+
1990
+ def run_cerebro_and_plot(
1991
+ cerebro, strategy, params, score=90, port=8050, optimize=True, auto_open=True, result_path=""
1992
+ ):
1993
+ """Run cerebro backtest and save/plot results.
1994
+
1995
+ Executes a backtest with the given strategy and parameters,
1996
+ calculates performance metrics, and saves results to CSV files.
1997
+
1998
+ Args:
1999
+ cerebro: Cerebro instance configured with data
2000
+ strategy: Strategy class to run
2001
+ params: Dictionary of strategy parameters
2002
+ score: Minimum score threshold (default: 90)
2003
+ port: Port for dashboard server (default: 8050)
2004
+ optimize: Whether to run in optimization mode (default: True)
2005
+ auto_open: Whether to auto-open plot (default: True)
2006
+ result_path: Path to save result files (default: current directory)
2007
+
2008
+ Returns:
2009
+ None (saves results to CSV files)
2010
+ """
2011
+ strategy_name = strategy.__name__
2012
+ params_str = ""
2013
+ for key in params:
2014
+ if key != "symbol_list" and key != "datas":
2015
+ params_str = params_str + "__" + key + "__" + str(params[key])
2016
+ file_name = strategy_name + params_str + ".csv"
2017
+ if result_path != "":
2018
+ file_list = os.listdir(result_path)
2019
+ else:
2020
+ file_list = os.listdir(os.getcwd())
2021
+ if file_name in file_list:
2022
+ print(f"backtest {params_str} consume time :0 because of it has run")
2023
+ if file_name not in file_list:
2024
+ print(
2025
+ "begin to run this params:{},now_time is {}".format(
2026
+ params_str, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
2027
+ )
2028
+ )
2029
+ cerebro.addstrategy(strategy, **params)
2030
+ begin_time = time.time()
2031
+ if optimize:
2032
+ cerebro.addanalyzer(analyzers.TotalValue, _name="_TotalValue")
2033
+ results = cerebro.run()
2034
+ # plot_results(results,"/home/yun/index_000300_reverse_strategy_hold_day_90.html")
2035
+ end_time = time.time()
2036
+ print(
2037
+ "backtest {} consume time :{}, end time is:{}".format(
2038
+ params_str,
2039
+ end_time - begin_time,
2040
+ time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
2041
+ )
2042
+ )
2043
+ # Get key account value and calculate three major indicators
2044
+ df0 = pd.DataFrame([results[0].analyzers._TotalValue.get_analysis()]).T
2045
+ df0.columns = ["total_value"]
2046
+ df0["datetime"] = df0.index
2047
+ df0 = df0.sort_values("datetime")
2048
+ del df0["datetime"]
2049
+ df0.to_csv(result_path + strategy_name + params_str + "___value.csv")
2050
+ # Calculate annual return based on daily net value
2051
+ df_return = get_year_return(copy.deepcopy(df0))
2052
+ # Calculate Sharpe ratio, average return, maximum drawdown
2053
+ sharpe_ratio, average_rate, max_drawdown_rate = get_rate_sharpe_drawdown(
2054
+ copy.deepcopy(df0)
2055
+ )
2056
+ # Analyze trading performance
2057
+ performance_dict = OrderedDict()
2058
+ # Performance measurement indicators
2059
+ performance_dict["sharpe_ratio"] = sharpe_ratio
2060
+ performance_dict["average_rate"] = average_rate
2061
+ performance_dict["max_drawdown_rate"] = max_drawdown_rate
2062
+ performance_dict["calmar_ratio"] = np.nan
2063
+ performance_dict["average_drawdown_len"] = np.nan
2064
+ performance_dict["average_drawdown_rate"] = np.nan
2065
+ performance_dict["average_drawdown_money"] = np.nan
2066
+ performance_dict["max_drawdown_len"] = np.nan
2067
+ performance_dict["max_drawdown_money"] = np.nan
2068
+ performance_dict["stddev_rate"] = np.nan
2069
+ performance_dict["positive_year"] = np.nan
2070
+ performance_dict["negative_year"] = np.nan
2071
+ performance_dict["nochange_year"] = np.nan
2072
+ performance_dict["best_year"] = np.nan
2073
+ performance_dict["worst_year"] = np.nan
2074
+ performance_dict["sqn_ratio"] = np.nan
2075
+ performance_dict["vwr_ratio"] = np.nan
2076
+ performance_dict["omega"] = np.nan
2077
+ trade_dict_1 = OrderedDict()
2078
+ trade_dict_2 = OrderedDict()
2079
+ trade_dict_1["total_trade_num"] = np.nan
2080
+ trade_dict_1["total_trade_opened"] = np.nan
2081
+ trade_dict_1["total_trade_closed"] = np.nan
2082
+ trade_dict_1["total_trade_len"] = np.nan
2083
+ trade_dict_1["long_trade_len"] = np.nan
2084
+ trade_dict_1["short_trade_len"] = np.nan
2085
+ trade_dict_1["longest_win_num"] = np.nan
2086
+ trade_dict_1["longest_lost_num"] = np.nan
2087
+ trade_dict_1["net_total_pnl"] = np.nan
2088
+ trade_dict_1["net_average_pnl"] = np.nan
2089
+ trade_dict_1["win_num"] = np.nan
2090
+ trade_dict_1["win_total_pnl"] = np.nan
2091
+ trade_dict_1["win_average_pnl"] = np.nan
2092
+ trade_dict_1["win_max_pnl"] = np.nan
2093
+ trade_dict_1["lost_num"] = np.nan
2094
+ trade_dict_1["lost_total_pnl"] = np.nan
2095
+ trade_dict_1["lost_average_pnl"] = np.nan
2096
+ trade_dict_1["lost_max_pnl"] = np.nan
2097
+
2098
+ trade_dict_2["long_num"] = np.nan
2099
+ trade_dict_2["long_win_num"] = np.nan
2100
+ trade_dict_2["long_lost_num"] = np.nan
2101
+ trade_dict_2["long_total_pnl"] = np.nan
2102
+ trade_dict_2["long_average_pnl"] = np.nan
2103
+ trade_dict_2["long_win_total_pnl"] = np.nan
2104
+ trade_dict_2["long_win_max_pnl"] = np.nan
2105
+ trade_dict_2["long_lost_total_pnl"] = np.nan
2106
+ trade_dict_2["long_lost_max_pnl"] = np.nan
2107
+ trade_dict_2["short_num"] = np.nan
2108
+ trade_dict_2["short_win_num"] = np.nan
2109
+ trade_dict_2["short_lost_num"] = np.nan
2110
+ trade_dict_2["short_total_pnl"] = np.nan
2111
+ trade_dict_2["short_average_pnl"] = np.nan
2112
+ trade_dict_2["short_win_total_pnl"] = np.nan
2113
+ trade_dict_2["short_win_max_pnl"] = np.nan
2114
+ trade_dict_2["short_lost_total_pnl"] = np.nan
2115
+ trade_dict_2["short_lost_max_pnl"] = np.nan
2116
+
2117
+ assert len(performance_dict) == len(trade_dict_2) == len(trade_dict_1)
2118
+ df00 = pd.DataFrame(index=range(18))
2119
+ df01 = pd.DataFrame([performance_dict]).T
2120
+ df01.columns = ["Performance indicator value"]
2121
+ df02 = pd.DataFrame([trade_dict_1]).T
2122
+ df02.columns = ["General trading indicator value"]
2123
+ df03 = pd.DataFrame([trade_dict_2]).T
2124
+ df03.columns = ["Long/short trading indicator value"]
2125
+ try:
2126
+ df00["Performance indicator"] = df01.index
2127
+ df00["Performance indicator value"] = [
2128
+ round(float(i), 4) for i in list(df01["Performance indicator value"])
2129
+ ]
2130
+ df00["General trading indicator"] = df02.index
2131
+ df00["General trading indicator value"] = [
2132
+ round(float(i), 4) for i in list(df02["General trading indicator value"])
2133
+ ]
2134
+ df00["Long/short trading indicator"] = df03.index
2135
+ df00["Long/short trading indicator value"] = [
2136
+ round(float(i), 4) for i in list(df03["Long/short trading indicator value"])
2137
+ ]
2138
+ except Exception as e:
2139
+ logger.warning("plot:2137 fallback on Exception")
2140
+ traceback.format_exception(type(e), e, e.__traceback__)
2141
+ df00["Performance indicator"] = df01.index
2142
+ df00["Performance indicator value"] = df01["Performance indicator value"]
2143
+ df00["General trading indicator"] = df02.index
2144
+ df00["General trading indicator value"] = df02["General trading indicator value"]
2145
+ df00["Long/short trading indicator"] = df03.index
2146
+ df00["Long/short trading indicator value"] = df03[
2147
+ "Long/short trading indicator value"
2148
+ ]
2149
+
2150
+ if not optimize:
2151
+ # Save required trading indicators
2152
+ # cerebro.addanalyzer(analyzers.PyFolio, _name='pyfolio')
2153
+ # cerebro.addanalyzer(analyzers.AnnualReturn, _name='_AnnualReturn') # Annual return calculation has issues, removed
2154
+ cerebro.addanalyzer(analyzers.Calmar, _name="_Calmar")
2155
+ cerebro.addanalyzer(analyzers.DrawDown, _name="_DrawDown")
2156
+ # cerebro.addanalyzer(analyzers.TimeDrawDown, _name='_TimeDrawDown')
2157
+ cerebro.addanalyzer(analyzers.GrossLeverage, _name="_GrossLeverage")
2158
+ cerebro.addanalyzer(analyzers.PositionsValue, _name="_PositionsValue")
2159
+ # cerebro.addanalyzer(analyzers.LogReturnsRolling, _name='_LogReturnsRolling')
2160
+ cerebro.addanalyzer(analyzers.PeriodStats, _name="_PeriodStats")
2161
+ cerebro.addanalyzer(analyzers.Returns, _name="_Returns")
2162
+ cerebro.addanalyzer(analyzers.SharpeRatio, _name="_SharpeRatio")
2163
+ # cerebro.addanalyzer(analyzers.SharpeRatio_A, _name='_SharpeRatio_A')
2164
+ cerebro.addanalyzer(analyzers.SQN, _name="_SQN")
2165
+ cerebro.addanalyzer(analyzers.TimeReturn, _name="_TimeReturn")
2166
+ cerebro.addanalyzer(analyzers.TradeAnalyzer, _name="_TradeAnalyzer")
2167
+ cerebro.addanalyzer(analyzers.Transactions, _name="_Transactions")
2168
+ cerebro.addanalyzer(analyzers.VWR, _name="_VWR")
2169
+ cerebro.addanalyzer(analyzers.TotalValue, _name="_TotalValue")
2170
+ cerebro.addanalyzer(analyzers.PyFolio)
2171
+ results = cerebro.run()
2172
+ # plot_results(results,"/home/yun/index_000300_reverse_strategy_hold_day_90.html")
2173
+ end_time = time.time()
2174
+ print(
2175
+ "backtest {} consume time :{}, end time is:{}".format(
2176
+ params_str,
2177
+ end_time - begin_time,
2178
+ time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
2179
+ )
2180
+ )
2181
+ # Analyze trading performance
2182
+ performance_dict = OrderedDict()
2183
+ drawdown_info = results[0].analyzers._DrawDown.get_analysis()
2184
+ # Calculate periodic indicators
2185
+ PeriodStats_info = results[0].analyzers._PeriodStats.get_analysis()
2186
+ # Calculate sqn indicator
2187
+ SQN_info = results[0].analyzers._SQN.get_analysis()
2188
+ sqn_ratio = SQN_info.get("sqn", np.nan)
2189
+ # Calculate vwr indicator
2190
+ VWR_info = results[0].analyzers._VWR.get_analysis()
2191
+ vwr_ratio = VWR_info.get("vwr", np.nan)
2192
+ # Calculate calmar indicator
2193
+ # calmar_ratio_list = list(results[0].analyzers._Calmar.get_analysis().values())
2194
+ # calmar_ratio = calmar_ratio_list[-1] if len(calmar_ratio_list) > 0 else np.nan
2195
+ calmar_ratio = np.nan
2196
+ # Calculate Sharpe ratio
2197
+ sharpe_info = results[0].analyzers._SharpeRatio.get_analysis()
2198
+ sharpe_ratio = sharpe_info.get("sharperatio", np.nan)
2199
+ # Get average drawdown indicator
2200
+ average_drawdown_len = drawdown_info.get("len", np.nan)
2201
+ average_drawdown_rate = drawdown_info.get("drawdown", np.nan)
2202
+ average_drawdown_money = drawdown_info.get("moneydown", np.nan)
2203
+ # Get maximum drawdown indicator
2204
+ max_drawdown_info = drawdown_info.get("max", {})
2205
+ max_drawdown_len = max_drawdown_info.get("len", np.nan)
2206
+ max_drawdown_rate = max_drawdown_info.get("drawdown", np.nan)
2207
+ max_drawdown_money = max_drawdown_info.get("moneydown", np.nan)
2208
+
2209
+ average_rate = PeriodStats_info.get("average", np.nan)
2210
+ stddev_rate = PeriodStats_info.get("stddev", np.nan)
2211
+ positive_year = PeriodStats_info.get("positive", np.nan)
2212
+ negative_year = PeriodStats_info.get("negative", np.nan)
2213
+ nochange_year = PeriodStats_info.get("nochange", np.nan)
2214
+ best_year = PeriodStats_info.get("best", np.nan)
2215
+ worst_year = PeriodStats_info.get("worst", np.nan)
2216
+
2217
+ # Get key account value and calculate three major indicators
2218
+ df0 = pd.DataFrame([results[0].analyzers._TotalValue.get_analysis()]).T
2219
+ df0.columns = ["total_value"]
2220
+ df0["datetime"] = df0.index
2221
+ df0 = df0.sort_values("datetime")
2222
+ del df0["datetime"]
2223
+ df0.to_csv(result_path + strategy_name + params_str + "___value.csv")
2224
+ # Calculate annual return based on daily net value
2225
+ df_return = get_year_return(copy.deepcopy(df0))
2226
+ # Calculate Sharpe ratio, average return, maximum drawdown
2227
+ sharpe_ratio, average_rate, max_drawdown_rate = get_rate_sharpe_drawdown(
2228
+ copy.deepcopy(df0)
2229
+ )
2230
+
2231
+ # Performance measurement indicators
2232
+ performance_dict["sharpe_ratio"] = sharpe_ratio
2233
+ performance_dict["average_rate"] = average_rate
2234
+ performance_dict["max_drawdown_rate"] = max_drawdown_rate
2235
+ performance_dict["calmar_ratio"] = calmar_ratio
2236
+ performance_dict["average_drawdown_len"] = average_drawdown_len
2237
+ performance_dict["average_drawdown_rate"] = average_drawdown_rate
2238
+ performance_dict["average_drawdown_money"] = average_drawdown_money
2239
+ performance_dict["max_drawdown_len"] = max_drawdown_len
2240
+ performance_dict["max_drawdown_money"] = max_drawdown_money
2241
+ performance_dict["stddev_rate"] = stddev_rate
2242
+ performance_dict["positive_year"] = positive_year
2243
+ performance_dict["negative_year"] = negative_year
2244
+ performance_dict["nochange_year"] = nochange_year
2245
+ performance_dict["best_year"] = best_year
2246
+ performance_dict["worst_year"] = worst_year
2247
+ performance_dict["sqn_ratio"] = sqn_ratio
2248
+ performance_dict["vwr_ratio"] = vwr_ratio
2249
+ performance_dict["omega"] = np.nan
2250
+
2251
+ trade_dict_1 = OrderedDict()
2252
+ trade_dict_2 = OrderedDict()
2253
+
2254
+ try:
2255
+ trade_info = results[0].analyzers._TradeAnalyzer.get_analysis()
2256
+ total_trade_num = trade_info["total"]["total"]
2257
+ total_trade_opened = trade_info["total"]["open"]
2258
+ total_trade_closed = trade_info["total"]["closed"]
2259
+ total_trade_len = trade_info["len"]["total"]
2260
+ long_trade_len = trade_info["len"]["long"]["total"]
2261
+ short_trade_len = trade_info["len"]["short"]["total"]
2262
+ except Exception as e:
2263
+ logger.warning("plot:2260 fallback on Exception")
2264
+ traceback.format_exception(type(e), e, e.__traceback__)
2265
+ total_trade_num = np.nan
2266
+ total_trade_opened = np.nan
2267
+ total_trade_closed = np.nan
2268
+ total_trade_len = np.nan
2269
+ long_trade_len = np.nan
2270
+ short_trade_len = np.nan
2271
+
2272
+ try:
2273
+ longest_win_num = trade_info["streak"]["won"]["longest"]
2274
+ longest_lost_num = trade_info["streak"]["lost"]["longest"]
2275
+ net_total_pnl = trade_info["pnl"]["net"]["total"]
2276
+ net_average_pnl = trade_info["pnl"]["net"]["average"]
2277
+ win_num = trade_info["won"]["total"]
2278
+ win_total_pnl = trade_info["won"]["pnl"]["total"]
2279
+ win_average_pnl = trade_info["won"]["pnl"]["average"]
2280
+ win_max_pnl = trade_info["won"]["pnl"]["max"]
2281
+ lost_num = trade_info["lost"]["total"]
2282
+ lost_total_pnl = trade_info["lost"]["pnl"]["total"]
2283
+ lost_average_pnl = trade_info["lost"]["pnl"]["average"]
2284
+ lost_max_pnl = trade_info["lost"]["pnl"]["max"]
2285
+ except Exception as e:
2286
+ logger.warning("plot:2282 fallback on Exception")
2287
+ traceback.format_exception(type(e), e, e.__traceback__)
2288
+ longest_win_num = np.nan
2289
+ longest_lost_num = np.nan
2290
+ net_total_pnl = np.nan
2291
+ net_average_pnl = np.nan
2292
+ win_num = np.nan
2293
+ win_total_pnl = np.nan
2294
+ win_average_pnl = np.nan
2295
+ win_max_pnl = np.nan
2296
+ lost_num = np.nan
2297
+ lost_total_pnl = np.nan
2298
+ lost_average_pnl = np.nan
2299
+ lost_max_pnl = np.nan
2300
+
2301
+ trade_dict_1["total_trade_num"] = total_trade_num
2302
+ trade_dict_1["total_trade_opened"] = total_trade_opened
2303
+ trade_dict_1["total_trade_closed"] = total_trade_closed
2304
+ trade_dict_1["total_trade_len"] = total_trade_len
2305
+ trade_dict_1["long_trade_len"] = long_trade_len
2306
+ trade_dict_1["short_trade_len"] = short_trade_len
2307
+ trade_dict_1["longest_win_num"] = longest_win_num
2308
+ trade_dict_1["longest_lost_num"] = longest_lost_num
2309
+ trade_dict_1["net_total_pnl"] = net_total_pnl
2310
+ trade_dict_1["net_average_pnl"] = net_average_pnl
2311
+ trade_dict_1["win_num"] = win_num
2312
+ trade_dict_1["win_total_pnl"] = win_total_pnl
2313
+ trade_dict_1["win_average_pnl"] = win_average_pnl
2314
+ trade_dict_1["win_max_pnl"] = win_max_pnl
2315
+ trade_dict_1["lost_num"] = lost_num
2316
+ trade_dict_1["lost_total_pnl"] = lost_total_pnl
2317
+ trade_dict_1["lost_average_pnl"] = lost_average_pnl
2318
+ trade_dict_1["lost_max_pnl"] = lost_max_pnl
2319
+
2320
+ try:
2321
+ long_num = trade_info["long"]["total"]
2322
+ long_win_num = trade_info["long"]["won"]
2323
+ long_lost_num = trade_info["long"]["lost"]
2324
+ long_total_pnl = trade_info["long"]["pnl"]["total"]
2325
+ long_average_pnl = trade_info["long"]["pnl"]["average"]
2326
+ long_win_total_pnl = trade_info["long"]["pnl"]["won"]["total"]
2327
+ long_win_max_pnl = trade_info["long"]["pnl"]["won"]["max"]
2328
+ long_lost_total_pnl = trade_info["long"]["pnl"]["lost"]["total"]
2329
+ long_lost_max_pnl = trade_info["long"]["pnl"]["lost"]["max"]
2330
+
2331
+ short_num = trade_info["short"]["total"]
2332
+ short_win_num = trade_info["short"]["won"]
2333
+ short_lost_num = trade_info["short"]["lost"]
2334
+ short_total_pnl = trade_info["short"]["pnl"]["total"]
2335
+ short_average_pnl = trade_info["short"]["pnl"]["average"]
2336
+ short_win_total_pnl = trade_info["short"]["pnl"]["won"]["total"]
2337
+ short_win_max_pnl = trade_info["short"]["pnl"]["won"]["max"]
2338
+ short_lost_total_pnl = trade_info["short"]["pnl"]["lost"]["total"]
2339
+ short_lost_max_pnl = trade_info["short"]["pnl"]["lost"]["max"]
2340
+ except Exception as e:
2341
+ logger.warning("plot:2336 fallback on Exception")
2342
+ traceback.format_exception(type(e), e, e.__traceback__)
2343
+ long_num = np.nan
2344
+ long_win_num = np.nan
2345
+ long_lost_num = np.nan
2346
+ long_total_pnl = np.nan
2347
+ long_average_pnl = np.nan
2348
+ long_win_total_pnl = np.nan
2349
+ long_win_max_pnl = np.nan
2350
+ long_lost_total_pnl = np.nan
2351
+ long_lost_max_pnl = np.nan
2352
+
2353
+ short_num = np.nan
2354
+ short_win_num = np.nan
2355
+ short_lost_num = np.nan
2356
+ short_total_pnl = np.nan
2357
+ short_average_pnl = np.nan
2358
+ short_win_total_pnl = np.nan
2359
+ short_win_max_pnl = np.nan
2360
+ short_lost_total_pnl = np.nan
2361
+ short_lost_max_pnl = np.nan
2362
+
2363
+ trade_dict_2["long_num"] = long_num
2364
+ trade_dict_2["long_win_num"] = long_win_num
2365
+ trade_dict_2["long_lost_num"] = long_lost_num
2366
+ trade_dict_2["long_total_pnl"] = long_total_pnl
2367
+ trade_dict_2["long_average_pnl"] = long_average_pnl
2368
+ trade_dict_2["long_win_total_pnl"] = long_win_total_pnl
2369
+ trade_dict_2["long_win_max_pnl"] = long_win_max_pnl
2370
+ trade_dict_2["long_lost_total_pnl"] = long_lost_total_pnl
2371
+ trade_dict_2["long_lost_max_pnl"] = long_lost_max_pnl
2372
+ trade_dict_2["short_num"] = short_num
2373
+ trade_dict_2["short_win_num"] = short_win_num
2374
+ trade_dict_2["short_lost_num"] = short_lost_num
2375
+ trade_dict_2["short_total_pnl"] = short_total_pnl
2376
+ trade_dict_2["short_average_pnl"] = short_average_pnl
2377
+ trade_dict_2["short_win_total_pnl"] = short_win_total_pnl
2378
+ trade_dict_2["short_win_max_pnl"] = short_win_max_pnl
2379
+ trade_dict_2["short_lost_total_pnl"] = short_lost_total_pnl
2380
+ trade_dict_2["short_lost_max_pnl"] = short_lost_max_pnl
2381
+
2382
+ assert len(performance_dict) == len(trade_dict_2) == len(trade_dict_1)
2383
+ df00 = pd.DataFrame(index=range(18))
2384
+ df01 = pd.DataFrame([performance_dict]).T
2385
+ df01.columns = ["Performance indicator value"]
2386
+ df02 = pd.DataFrame([trade_dict_1]).T
2387
+ df02.columns = ["General trading indicator value"]
2388
+ df03 = pd.DataFrame([trade_dict_2]).T
2389
+ df03.columns = ["Long/short trading indicator value"]
2390
+ try:
2391
+ df00["Performance indicator"] = df01.index
2392
+ df00["Performance indicator value"] = [
2393
+ round(float(i), 4) for i in list(df01["Performance indicator value"])
2394
+ ]
2395
+ df00["General trading indicator"] = df02.index
2396
+ df00["General trading indicator value"] = [
2397
+ round(float(i), 4) for i in list(df02["General trading indicator value"])
2398
+ ]
2399
+ df00["Long/short trading indicator"] = df03.index
2400
+ df00["Long/short trading indicator value"] = [
2401
+ round(float(i), 4) for i in list(df03["Long/short trading indicator value"])
2402
+ ]
2403
+ except Exception as e:
2404
+ logger.warning("plot:2398 fallback on Exception")
2405
+ traceback.format_exception(type(e), e, e.__traceback__)
2406
+ df00["Performance indicator"] = df01.index
2407
+ df00["Performance indicator value"] = df01["Performance indicator value"]
2408
+ df00["General trading indicator"] = df02.index
2409
+ df00["General trading indicator value"] = df02["General trading indicator value"]
2410
+ df00["Long/short trading indicator"] = df03.index
2411
+ df00["Long/short trading indicator value"] = df03[
2412
+ "Long/short trading indicator value"
2413
+ ]
2414
+
2415
+ # Add table data
2416
+ table_data = [
2417
+ list(df00["Performance indicator"])[:9],
2418
+ list(df00["Performance indicator value"])[:9],
2419
+ list(df00["Performance indicator"])[9:],
2420
+ list(df00["Performance indicator value"])[9:],
2421
+ list(df00["General trading indicator"])[:9],
2422
+ list(df00["General trading indicator value"])[:9],
2423
+ list(df00["General trading indicator"])[9:],
2424
+ list(df00["General trading indicator value"])[9:],
2425
+ list(df00["Long/short trading indicator"])[:9],
2426
+ list(df00["Long/short trading indicator value"])[:9],
2427
+ list(df00["Long/short trading indicator"])[9:],
2428
+ list(df00["Long/short trading indicator value"])[9:],
2429
+ ]
2430
+ fig = ff.create_table(table_data)
2431
+ # Add graph data
2432
+ # Add graph data
2433
+ trace1 = go.Scatter(
2434
+ x=list(df0.index),
2435
+ y=list(df0.total_value),
2436
+ xaxis="x2",
2437
+ yaxis="y2",
2438
+ name="total_value",
2439
+ mode="lines",
2440
+ )
2441
+ trace2 = go.Bar(
2442
+ x=list(df_return.index),
2443
+ y=[str(round(i, 3)) + "%" for i in list(df_return["return"])],
2444
+ xaxis="x2",
2445
+ yaxis="y3",
2446
+ name="year_profit",
2447
+ opacity=0.3,
2448
+ marker={"color": "#ffa631"},
2449
+ )
2450
+ # Add trace data to figure
2451
+ fig.add_traces([trace1, trace2])
2452
+
2453
+ # initialize xaxis2 and yaxis2
2454
+ fig["layout"]["xaxis2"] = {}
2455
+ fig["layout"]["yaxis2"] = {}
2456
+ fig["layout"]["yaxis3"] = {}
2457
+
2458
+ # Edit layout for subplots
2459
+ fig.layout.yaxis.update({"domain": [0.5, 1]})
2460
+ fig.layout.yaxis2.update({"domain": [0, 0.5]})
2461
+ fig.layout.yaxis3.update({"domain": [0, 0.5]})
2462
+
2463
+ # The graph's yaxis2 MUST BE anchored to the graph's xaxis2 and vice versa
2464
+ # fig.layout.yaxis3.update({'anchor': 'x2'})
2465
+ # # fig.layout.xaxis2.update({'anchor': 'y3'})
2466
+ # fig.layout.yaxis3.update({'title': 'year_profit'})
2467
+ # fig.layout.yaxis3.update({'overlaying':'y2', 'side':'right'})
2468
+
2469
+ fig.layout.yaxis2.update({"anchor": "x2"})
2470
+ fig.layout.xaxis2.update({"anchor": "y2"})
2471
+ fig.layout.yaxis2.update({"title": "total_value"})
2472
+ fig.layout.yaxis2.update({"type": "log"})
2473
+
2474
+ fig.layout.yaxis3.update({"anchor": "x2"})
2475
+ # fig.layout.xaxis2.update({'anchor': 'y3'})
2476
+ fig.layout.yaxis3.update({"title": "year_profit"})
2477
+ fig.layout.yaxis3.update({"overlaying": "y2", "side": "right"})
2478
+
2479
+ # Update the margins to add a title and see graph x-labels.
2480
+ fig.layout.margin.update({"t": 75, "l": 50})
2481
+ fig.layout.update(
2482
+ {
2483
+ "title": {
2484
+ "text": strategy.__name__ + params_str,
2485
+ "x": 0.5,
2486
+ "xanchor": "center",
2487
+ "yanchor": "middle",
2488
+ "font": {"family": "Arial", "color": "red"},
2489
+ }
2490
+ }
2491
+ )
2492
+
2493
+ # Update the height because adding a graph vertically will interact with
2494
+ # the plot height calculated for the table
2495
+ fig.layout.update({"height": 800})
2496
+
2497
+ py.plot(fig, auto_open=auto_open, filename=result_path + strategy.__name__ + params_str)
2498
+ df00.to_csv(result_path + strategy.__name__ + params_str + ".csv", encoding="gbk")
2499
+
2500
+ return results