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,1094 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Bokeh Application Core Classes
4
+
5
+ Provides integration between Backtrader and Bokeh
6
+ """
7
+
8
+ from collections import OrderedDict
9
+ from typing import Any
10
+
11
+ from backtrader.parameters import make_legacy_parameter_accessor
12
+ from backtrader.utils.log_message import get_logger
13
+
14
+ logger = get_logger(__name__)
15
+
16
+ try:
17
+ import pandas as pd
18
+
19
+ PANDAS_AVAILABLE = True
20
+ except ImportError:
21
+ PANDAS_AVAILABLE = False
22
+
23
+ BOKEH_AVAILABLE = False
24
+ Panel: Any = None
25
+
26
+ try:
27
+ from bokeh.layouts import gridplot
28
+ from bokeh.models import ColumnDataSource, CrosshairTool, HoverTool
29
+ from bokeh.plotting import figure
30
+
31
+ BOKEH_AVAILABLE = True
32
+
33
+ # Handle different Bokeh versions for Panel
34
+ try:
35
+ from bokeh.models import TabPanel as _Panel
36
+ except ImportError:
37
+ try:
38
+ from bokeh.models.widgets import Panel as _Panel
39
+ except ImportError:
40
+ from bokeh.models import Panel as _Panel
41
+ Panel = _Panel
42
+ except ImportError:
43
+ # Bokeh is an optional dependency; BOKEH_AVAILABLE stays False when missing.
44
+ logger.warning("app:44 suppressed ImportError")
45
+
46
+ from . import tabs as default_tabs # noqa: E402
47
+ from .schemes import Tradimo # noqa: E402
48
+
49
+ _logger = get_logger(__name__)
50
+
51
+
52
+ class FigurePage:
53
+ """Figure Page
54
+
55
+ Manages a group of related charts and data sources.
56
+ """
57
+
58
+ def __init__(self, strategy=None):
59
+ """Initialize a FigurePage.
60
+
61
+ Args:
62
+ strategy: Strategy instance associated with this figure page.
63
+ """
64
+ self.strategy = strategy
65
+ self.figures = []
66
+ self.cds = None # ColumnDataSource
67
+ self._data = None
68
+
69
+ def set_cds_columns_from_df(self, df):
70
+ """Set CDS columns from DataFrame.
71
+
72
+ Args:
73
+ df: pandas DataFrame
74
+ """
75
+ if not BOKEH_AVAILABLE or df is None:
76
+ return
77
+
78
+ if self.cds is None:
79
+ self.cds = ColumnDataSource(df)
80
+ else:
81
+ self.cds.data = df.to_dict("list")
82
+
83
+ def get_cds_streamdata_from_df(self, df):
84
+ """Get stream data from DataFrame.
85
+
86
+ Args:
87
+ df: pandas DataFrame
88
+
89
+ Returns:
90
+ dict: Stream data dictionary
91
+ """
92
+ if df is None or df.empty:
93
+ return {}
94
+ return df.to_dict("list")
95
+
96
+ def get_cds_patchdata_from_series(self, series):
97
+ """Get patch data from Series.
98
+
99
+ Args:
100
+ series: pandas Series
101
+
102
+ Returns:
103
+ tuple: (patch_data, stream_data)
104
+ """
105
+ return {}, {}
106
+
107
+
108
+ class Figure:
109
+ """Single Figure
110
+
111
+ Wraps a Bokeh figure.
112
+ """
113
+
114
+ def __init__(self, scheme=None):
115
+ """Initialize a Figure.
116
+
117
+ Args:
118
+ scheme: Theme/scheme instance for styling the figure.
119
+ """
120
+ self.scheme = scheme
121
+ self.figure = None
122
+ self.cds = None
123
+
124
+ def get_cds_streamdata_from_df(self, df):
125
+ """Get stream data from DataFrame."""
126
+ if df is None or df.empty:
127
+ return {}
128
+ return df.to_dict("list")
129
+
130
+ def get_cds_patchdata_from_series(self, series, fill_nan=None):
131
+ """Get patch data from Series."""
132
+ return {}, {}
133
+
134
+ def fill_nan(self):
135
+ """Return columns that need NaN filling."""
136
+ return []
137
+
138
+
139
+ class BacktraderBokeh:
140
+ """Backtrader Bokeh Application
141
+
142
+ Provides the following features:
143
+ - Create and manage figure pages
144
+ - Generate Bokeh models
145
+ - Data processing and formatting
146
+
147
+ Args:
148
+ style: Chart style, 'bar' or 'candle'
149
+ scheme: Theme instance
150
+ use_default_tabs: Whether to use default tabs
151
+ filter: Data filter configuration
152
+
153
+ Example:
154
+ app = BacktraderBokeh(style='candle', scheme=Blackly())
155
+ figid, figurepage = app.create_figurepage(strategy)
156
+ panels = app.generate_model_panels()
157
+ """
158
+
159
+ params = (
160
+ ("style", "bar"),
161
+ ("scheme", None),
162
+ ("use_default_tabs", True),
163
+ ("filter", None),
164
+ )
165
+
166
+ def __init__(self, **kwargs):
167
+ """Initialize BacktraderBokeh application.
168
+
169
+ Args:
170
+ **kwargs: Keyword arguments for configuration:
171
+ - style: Chart style ('bar' or 'candle')
172
+ - scheme: Theme instance for styling
173
+ - use_default_tabs: Whether to use default tabs
174
+ - filter: Data filter configuration
175
+ """
176
+ # Process parameters
177
+ self.p = make_legacy_parameter_accessor(
178
+ self.params,
179
+ values={name: kwargs[name] for name, _default in self.params if name in kwargs},
180
+ name=f"{self.__class__.__name__}Params",
181
+ )
182
+
183
+ # Set theme
184
+ self.scheme = self.p.scheme
185
+ if self.scheme is None:
186
+ self.scheme = Tradimo()
187
+
188
+ # Figure page storage
189
+ self._figurepages = OrderedDict()
190
+ self._figid_counter = 0
191
+
192
+ # Current filter settings
193
+ self._filter = self.p.filter or {}
194
+
195
+ # Tab list
196
+ self.tabs = []
197
+ if self.p.use_default_tabs:
198
+ self.tabs = [
199
+ default_tabs.PerformanceTab,
200
+ default_tabs.AnalyzerTab,
201
+ default_tabs.MetadataTab,
202
+ default_tabs.ConfigTab,
203
+ default_tabs.LogTab,
204
+ default_tabs.SourceTab,
205
+ ]
206
+
207
+ def create_figurepage(
208
+ self,
209
+ strategy,
210
+ filldata=True,
211
+ start=None,
212
+ end=None,
213
+ preserveidx=False,
214
+ fill_gaps=False,
215
+ ):
216
+ """Create a figure page.
217
+
218
+ Args:
219
+ strategy: Strategy instance
220
+ filldata: Whether to fill data
221
+ start: Start index for data slicing
222
+ end: End index for data slicing
223
+ preserveidx: Keep original index values in source
224
+ fill_gaps: Unused currently, for future compatibility
225
+
226
+ Returns:
227
+ tuple: (figid, figurepage)
228
+ """
229
+ figid = self._figid_counter
230
+ self._figid_counter += 1
231
+
232
+ figurepage = FigurePage(strategy)
233
+ self._figurepages[figid] = figurepage
234
+
235
+ if filldata:
236
+ self._fill_figurepage(
237
+ figurepage,
238
+ strategy,
239
+ start=start,
240
+ end=end,
241
+ preserveidx=preserveidx,
242
+ fill_gaps=fill_gaps,
243
+ )
244
+
245
+ return figid, figurepage
246
+
247
+ def _fill_figurepage(
248
+ self,
249
+ figurepage,
250
+ strategy,
251
+ start=None,
252
+ end=None,
253
+ preserveidx=False,
254
+ fill_gaps=False,
255
+ ):
256
+ """Fill figure page with data.
257
+
258
+ Args:
259
+ figurepage: Figure page instance
260
+ strategy: Strategy instance
261
+ start: Start index for data slicing
262
+ end: End index for data slicing
263
+ preserveidx: Keep original index values in source
264
+ fill_gaps: Unused currently, for future compatibility
265
+ """
266
+ if not BOKEH_AVAILABLE or not PANDAS_AVAILABLE:
267
+ return
268
+
269
+ if strategy is None or not hasattr(strategy, "datas") or not strategy.datas:
270
+ return
271
+
272
+ # Get the first data source
273
+ data = strategy.datas[0]
274
+
275
+ # Create DataFrame
276
+ df_data = self._create_dataframe(
277
+ data, strategy, start=start, end=end, preserveidx=preserveidx
278
+ )
279
+
280
+ if df_data is not None:
281
+ # Add trade signal data
282
+ df_data = self._add_trade_signals(df_data, strategy)
283
+
284
+ figurepage._data = df_data
285
+ figurepage.cds = ColumnDataSource(df_data)
286
+
287
+ # Create main figure (price + trade signals)
288
+ main_figure = self._create_main_figure(df_data, strategy)
289
+ if main_figure is not None:
290
+ figurepage.figures.append(main_figure)
291
+
292
+ # Create equity curve figure
293
+ equity_figure = self._create_equity_figure(df_data, strategy)
294
+ if equity_figure is not None:
295
+ figurepage.figures.append(equity_figure)
296
+
297
+ # Create drawdown figure
298
+ drawdown_figure = self._create_drawdown_figure(df_data, strategy)
299
+ if drawdown_figure is not None:
300
+ figurepage.figures.append(drawdown_figure)
301
+
302
+ def _create_dataframe(self, data, strategy, start=None, end=None, preserveidx=False):
303
+ """Create DataFrame from data source.
304
+
305
+ Args:
306
+ data: Data source
307
+ strategy: Strategy instance
308
+ start: Start index for slicing
309
+ end: End index for slicing
310
+ preserveidx: Keep original index values in source
311
+
312
+ Returns:
313
+ pandas.DataFrame
314
+ """
315
+ if not PANDAS_AVAILABLE:
316
+ return None
317
+
318
+ length = len(data)
319
+ if length == 0:
320
+ return None
321
+
322
+ df_dict = {
323
+ "index": list(range(length)),
324
+ }
325
+
326
+ # Add datetime
327
+ try:
328
+ df_dict["datetime"] = [data.datetime.datetime(-length + i + 1) for i in range(length)]
329
+ except Exception:
330
+ logger.warning("app:329 fallback on Exception")
331
+ df_dict["datetime"] = list(range(length))
332
+
333
+ # Add OHLCV data
334
+ for name in ["open", "high", "low", "close", "volume"]:
335
+ if hasattr(data, name):
336
+ line = getattr(data, name)
337
+ try:
338
+ df_dict[name] = [line[-length + i + 1] for i in range(length)]
339
+ except Exception:
340
+ logger.warning("app:338 fallback on Exception")
341
+ df_dict[name] = [0] * length
342
+
343
+ df = pd.DataFrame(df_dict)
344
+ return self._slice_dataframe(df, start=start, end=end, preserveidx=preserveidx)
345
+
346
+ def _slice_dataframe(self, df, start=None, end=None, preserveidx=False):
347
+ """Slice dataframe by index.
348
+
349
+ Args:
350
+ df: Source dataframe
351
+ start: Start index (inclusive)
352
+ end: End index (exclusive)
353
+ preserveidx: Keep original index instead of sequential index column
354
+
355
+ Returns:
356
+ pandas.DataFrame
357
+ """
358
+ if df.empty:
359
+ return df
360
+
361
+ length = len(df)
362
+ if start is None:
363
+ start = 0
364
+ if end is None:
365
+ end = length
366
+
367
+ try:
368
+ start = int(start)
369
+ except (TypeError, ValueError):
370
+ start = 0
371
+ try:
372
+ end = int(end)
373
+ except (TypeError, ValueError):
374
+ end = length
375
+
376
+ if start < 0:
377
+ start = length + start
378
+ if end < 0:
379
+ end = length + 1 + end
380
+
381
+ if start < 0:
382
+ start = 0
383
+ if end < 0:
384
+ end = 0
385
+ if start > length:
386
+ start = length
387
+ if end > length:
388
+ end = length
389
+
390
+ df = df.iloc[start:end].copy()
391
+
392
+ if not preserveidx:
393
+ df = df.reset_index(drop=True)
394
+ df["index"] = list(range(len(df)))
395
+
396
+ return df
397
+
398
+ def _add_trade_signals(self, df, strategy):
399
+ """Add trade signal data to DataFrame.
400
+
401
+ Args:
402
+ df: DataFrame
403
+ strategy: Strategy instance
404
+
405
+ Returns:
406
+ DataFrame: DataFrame with trade signals added
407
+ """
408
+ # Initialize trade signal columns
409
+ df = df.copy()
410
+ df["buy_signal"] = None
411
+ df["sell_signal"] = None
412
+ df["buy_price"] = None
413
+ df["sell_price"] = None
414
+
415
+ # Extract trade records from strategy
416
+ if not hasattr(strategy, "_trades") and not hasattr(strategy, "trades"):
417
+ # Try to get trade info from analyzer
418
+ trade_analyzer = None
419
+ for analyzer in getattr(strategy, "analyzers", []):
420
+ if analyzer.__class__.__name__ == "TradeAnalyzer":
421
+ trade_analyzer = analyzer
422
+ break
423
+
424
+ if trade_analyzer is None:
425
+ return df
426
+
427
+ # Extract trade signals from order history
428
+ try:
429
+ orders = getattr(strategy, "_orders", []) or getattr(strategy, "orders", [])
430
+ for order in orders:
431
+ if order.status == order.Completed:
432
+ exec_dt = order.executed.dt
433
+ exec_price = order.executed.price
434
+
435
+ # Convert backtrader date to datetime
436
+ from ..utils.date import num2date
437
+
438
+ try:
439
+ dt = num2date(exec_dt)
440
+ except Exception:
441
+ # Skip orders whose executed datetime cannot be decoded
442
+ # (e.g. uninitialised/NaN). Plot-only path, so log at
443
+ # debug and move on without aborting the plot.
444
+ logger.debug("skip order: bad executed dt %r", exec_dt, exc_info=True)
445
+ continue
446
+
447
+ # Find the corresponding DataFrame row
448
+ mask = df["datetime"] == dt
449
+ if mask.any():
450
+ idx = df[mask].index[0]
451
+ if order.isbuy():
452
+ df.loc[idx, "buy_signal"] = True
453
+ df.loc[idx, "buy_price"] = exec_price
454
+ else:
455
+ df.loc[idx, "sell_signal"] = True
456
+ df.loc[idx, "sell_price"] = exec_price
457
+ except Exception as e:
458
+ logger.debug("Failed to add trade signals: %s", e)
459
+
460
+ # Add equity curve data
461
+ df = self._add_equity_data(df, strategy)
462
+
463
+ return df
464
+
465
+ def _equity_from_broker_observer(self, strategy, length):
466
+ """Equity values from the Broker observer's value line (or all-None)."""
467
+ equity_values = [None] * length
468
+ if hasattr(strategy, "observers"):
469
+ for obs in strategy.observers:
470
+ if obs.__class__.__name__ == "Broker":
471
+ if hasattr(obs.lines, "value"):
472
+ value_line = obs.lines.value
473
+ obs_len = len(value_line)
474
+ for i in range(min(length, obs_len)):
475
+ idx = 1 - obs_len + i
476
+ try:
477
+ equity_values[i] = value_line[idx]
478
+ except Exception as e:
479
+ logger.debug("Failed to get equity value at idx %d: %s", idx, e)
480
+ break
481
+ return equity_values
482
+
483
+ def _equity_from_timereturn(self, df, strategy, equity_values):
484
+ """Fill equity_values in-place from a TimeReturn analyzer (fallback)."""
485
+ time_return = None
486
+ for analyzer in getattr(strategy, "analyzers", []):
487
+ if analyzer.__class__.__name__ == "TimeReturn":
488
+ try:
489
+ time_return = analyzer.get_analysis()
490
+ except Exception as e:
491
+ logger.debug("Failed to get TimeReturn analysis: %s", e)
492
+ break
493
+
494
+ if not time_return:
495
+ return
496
+
497
+ start_cash = 100000 # Default starting capital
498
+ if hasattr(strategy, "broker"):
499
+ try:
500
+ start_cash = strategy.broker.startingcash
501
+ except Exception as e:
502
+ logger.debug("Failed to get starting cash: %s", e)
503
+
504
+ cumulative = start_cash
505
+ sorted_returns = sorted(time_return.items())
506
+ ret_idx = 0
507
+
508
+ for i, row in df.iterrows():
509
+ if ret_idx < len(sorted_returns):
510
+ dt, ret = sorted_returns[ret_idx]
511
+ if row["datetime"].date() >= dt.date() if hasattr(dt, "date") else True:
512
+ cumulative = cumulative * (1 + ret)
513
+ ret_idx += 1
514
+ equity_values[i] = cumulative
515
+
516
+ @staticmethod
517
+ def _compute_drawdown(df, equity_values):
518
+ """Fill df['drawdown']/['drawdown_pct'] from the equity curve."""
519
+ df["drawdown"] = None
520
+ df["drawdown_pct"] = None
521
+
522
+ max_equity = None
523
+ for i, eq in enumerate(equity_values):
524
+ if eq is not None:
525
+ if max_equity is None or eq > max_equity:
526
+ max_equity = eq
527
+ if max_equity > 0:
528
+ dd = max_equity - eq
529
+ dd_pct = (dd / max_equity) * 100
530
+ df.loc[i, "drawdown"] = dd
531
+ df.loc[i, "drawdown_pct"] = dd_pct
532
+
533
+ def _add_equity_data(self, df, strategy):
534
+ """Add equity curve data.
535
+
536
+ Args:
537
+ df: DataFrame
538
+ strategy: Strategy instance
539
+
540
+ Returns:
541
+ DataFrame
542
+ """
543
+ length = len(df)
544
+
545
+ # Get equity data from Broker observer
546
+ equity_values = self._equity_from_broker_observer(strategy, length)
547
+
548
+ # If no Broker observer, try to calculate from TimeReturn analyzer
549
+ if all(v is None for v in equity_values):
550
+ self._equity_from_timereturn(df, strategy, equity_values)
551
+
552
+ df["equity"] = equity_values
553
+
554
+ # Calculate drawdown
555
+ self._compute_drawdown(df, equity_values)
556
+
557
+ return df
558
+
559
+ def _create_main_figure(self, df, strategy=None):
560
+ """Create main figure (price + trade signals).
561
+
562
+ Args:
563
+ df: DataFrame
564
+ strategy: Strategy instance
565
+
566
+ Returns:
567
+ Figure instance
568
+ """
569
+ if not BOKEH_AVAILABLE:
570
+ return None
571
+
572
+ fig = Figure(self.scheme)
573
+
574
+ # Create Bokeh figure
575
+ fig.figure = figure(
576
+ title="Price & Trade Signals",
577
+ x_axis_type="datetime" if "datetime" in df.columns else "linear",
578
+ height=self.scheme.plot_height,
579
+ sizing_mode=self.scheme.plot_sizing_mode,
580
+ tools="pan,wheel_zoom,box_zoom,reset,save",
581
+ toolbar_location=self.scheme.toolbar_location,
582
+ )
583
+
584
+ # Apply theme style
585
+ self._apply_scheme_to_figure(fig.figure)
586
+
587
+ # Plot price data
588
+ if self.p.style == "candle" and all(
589
+ col in df.columns for col in ["open", "high", "low", "close"]
590
+ ):
591
+ self._plot_candlestick(fig, df)
592
+ else:
593
+ self._plot_line(fig, df)
594
+
595
+ # Plot trade signals
596
+ self._plot_trade_signals(fig, df)
597
+
598
+ # Add crosshair
599
+ crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
600
+ fig.figure.add_tools(crosshair)
601
+
602
+ # Add HoverTool
603
+ hover = HoverTool(
604
+ tooltips=[
605
+ ("Date", "@datetime{%F}"),
606
+ ("Open", "@open{0.2f}"),
607
+ ("High", "@high{0.2f}"),
608
+ ("Low", "@low{0.2f}"),
609
+ ("Close", "@close{0.2f}"),
610
+ ],
611
+ formatters={"@datetime": "datetime"},
612
+ mode="vline",
613
+ )
614
+ fig.figure.add_tools(hover)
615
+
616
+ # Create data source
617
+ fig.cds = ColumnDataSource(df)
618
+
619
+ return fig
620
+
621
+ def _plot_trade_signals(self, fig, df):
622
+ """Plot trade signal markers.
623
+
624
+ Args:
625
+ fig: Figure instance
626
+ df: DataFrame
627
+ """
628
+ x_col = "datetime" if "datetime" in df.columns else "index"
629
+
630
+ # Plot buy signals (green up triangle)
631
+ if "buy_signal" in df.columns:
632
+ buy_signal = df["buy_signal"].fillna(False).astype(bool)
633
+ buy_df = df[buy_signal]
634
+ else:
635
+ buy_df = df.iloc[0:0]
636
+
637
+ if len(buy_df) > 0:
638
+ fig.figure.triangle(
639
+ buy_df[x_col],
640
+ buy_df["buy_price"] if "buy_price" in buy_df.columns else buy_df["low"] * 0.98,
641
+ size=12,
642
+ color="#00ff00",
643
+ alpha=0.8,
644
+ legend_label="Buy",
645
+ )
646
+
647
+ # Plot sell signals (red down triangle)
648
+ if "sell_signal" in df.columns:
649
+ sell_signal = df["sell_signal"].fillna(False).astype(bool)
650
+ sell_df = df[sell_signal]
651
+ else:
652
+ sell_df = df.iloc[0:0]
653
+ if len(sell_df) > 0:
654
+ fig.figure.inverted_triangle(
655
+ sell_df[x_col],
656
+ (
657
+ sell_df["sell_price"]
658
+ if "sell_price" in sell_df.columns
659
+ else sell_df["high"] * 1.02
660
+ ),
661
+ size=12,
662
+ color="#ff0000",
663
+ alpha=0.8,
664
+ legend_label="Sell",
665
+ )
666
+
667
+ # Configure legend
668
+ if len(buy_df) > 0 or len(sell_df) > 0:
669
+ fig.figure.legend.location = "top_left"
670
+ fig.figure.legend.click_policy = "hide"
671
+
672
+ def _create_equity_figure(self, df, strategy):
673
+ """Create equity curve figure.
674
+
675
+ Args:
676
+ df: DataFrame
677
+ strategy: Strategy instance
678
+
679
+ Returns:
680
+ Figure instance
681
+ """
682
+ if not BOKEH_AVAILABLE:
683
+ return None
684
+
685
+ if "equity" not in df.columns or df["equity"].isna().all():
686
+ return None
687
+
688
+ fig = Figure(self.scheme)
689
+
690
+ # Create Bokeh figure
691
+ fig.figure = figure(
692
+ title="Equity Curve",
693
+ x_axis_type="datetime" if "datetime" in df.columns else "linear",
694
+ height=int(self.scheme.plot_height * 0.6),
695
+ sizing_mode=self.scheme.plot_sizing_mode,
696
+ tools="pan,wheel_zoom,box_zoom,reset,save",
697
+ toolbar_location=self.scheme.toolbar_location,
698
+ )
699
+
700
+ # Apply theme style
701
+ self._apply_scheme_to_figure(fig.figure)
702
+
703
+ x_col = "datetime" if "datetime" in df.columns else "index"
704
+
705
+ # Plot equity curve
706
+ equity_df = df[df["equity"].notna()]
707
+ if len(equity_df) > 0:
708
+ fig.figure.line(
709
+ equity_df[x_col],
710
+ equity_df["equity"],
711
+ line_width=2,
712
+ color="#2196F3",
713
+ legend_label="Equity",
714
+ )
715
+
716
+ # Fill area
717
+
718
+ ColumnDataSource(
719
+ data={
720
+ "x": equity_df[x_col].tolist(),
721
+ "y": equity_df["equity"].tolist(),
722
+ "lower": [equity_df["equity"].min()] * len(equity_df),
723
+ }
724
+ )
725
+
726
+ # Add crosshair
727
+ crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
728
+ fig.figure.add_tools(crosshair)
729
+
730
+ fig.figure.legend.location = "top_left"
731
+
732
+ return fig
733
+
734
+ def _create_drawdown_figure(self, df, strategy):
735
+ """Create drawdown figure.
736
+
737
+ Args:
738
+ df: DataFrame
739
+ strategy: Strategy instance
740
+
741
+ Returns:
742
+ Figure instance
743
+ """
744
+ if not BOKEH_AVAILABLE:
745
+ return None
746
+
747
+ if "drawdown_pct" not in df.columns or df["drawdown_pct"].isna().all():
748
+ return None
749
+
750
+ fig = Figure(self.scheme)
751
+
752
+ # Create Bokeh figure
753
+ fig.figure = figure(
754
+ title="Drawdown (%)",
755
+ x_axis_type="datetime" if "datetime" in df.columns else "linear",
756
+ height=int(self.scheme.plot_height * 0.5),
757
+ sizing_mode=self.scheme.plot_sizing_mode,
758
+ tools="pan,wheel_zoom,box_zoom,reset,save",
759
+ toolbar_location=self.scheme.toolbar_location,
760
+ )
761
+
762
+ # Apply theme style
763
+ self._apply_scheme_to_figure(fig.figure)
764
+
765
+ x_col = "datetime" if "datetime" in df.columns else "index"
766
+
767
+ # Plot drawdown curve
768
+ dd_df = df[df["drawdown_pct"].notna()]
769
+ if len(dd_df) > 0:
770
+ # Use negative values to display as decline on chart
771
+ fig.figure.varea(
772
+ x=dd_df[x_col],
773
+ y1=0,
774
+ y2=-dd_df["drawdown_pct"],
775
+ fill_color="#f44336",
776
+ fill_alpha=0.5,
777
+ legend_label="Drawdown",
778
+ )
779
+
780
+ fig.figure.line(dd_df[x_col], -dd_df["drawdown_pct"], line_width=1, color="#d32f2f")
781
+
782
+ # Add crosshair
783
+ crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
784
+ fig.figure.add_tools(crosshair)
785
+
786
+ fig.figure.legend.location = "bottom_left"
787
+ fig.figure.y_range.flipped = False
788
+
789
+ return fig
790
+
791
+ def _apply_scheme_to_figure(self, fig):
792
+ """Apply theme style to figure.
793
+
794
+ Args:
795
+ fig: Bokeh figure
796
+ """
797
+ fig.background_fill_color = self.scheme.background_fill
798
+ fig.border_fill_color = self.scheme.border_fill
799
+
800
+ fig.xgrid.grid_line_color = self.scheme.grid_line_color
801
+ fig.ygrid.grid_line_color = self.scheme.grid_line_color
802
+
803
+ fig.xaxis.axis_line_color = self.scheme.axis_line_color
804
+ fig.yaxis.axis_line_color = self.scheme.axis_line_color
805
+
806
+ fig.xaxis.major_tick_line_color = self.scheme.tick_line_color
807
+ fig.yaxis.major_tick_line_color = self.scheme.tick_line_color
808
+
809
+ fig.xaxis.axis_label_text_color = self.scheme.axis_label_text_color
810
+ fig.yaxis.axis_label_text_color = self.scheme.axis_label_text_color
811
+
812
+ fig.title.text_color = self.scheme.plot_title_text_color
813
+
814
+ def _plot_candlestick(self, fig, df):
815
+ """Plot candlestick chart.
816
+
817
+ Args:
818
+ fig: Figure instance
819
+ df: DataFrame
820
+ """
821
+ if "datetime" not in df.columns:
822
+ return
823
+
824
+ # Calculate up/down
825
+ df = df.copy()
826
+ df["is_up"] = df["close"] >= df["open"]
827
+
828
+ # Separate up and down data
829
+ up = df[df["is_up"]]
830
+ down = df[~df["is_up"]]
831
+
832
+ # Calculate candle width
833
+ width = 0.5 * 24 * 60 * 60 * 1000 # Half day in milliseconds
834
+
835
+ # Plot up candles
836
+ if len(up) > 0:
837
+ fig.figure.segment(
838
+ up["datetime"], up["high"], up["datetime"], up["low"], color=self.scheme.barup_wick
839
+ )
840
+ fig.figure.vbar(
841
+ up["datetime"],
842
+ width,
843
+ up["open"],
844
+ up["close"],
845
+ fill_color=self.scheme.barup,
846
+ line_color=self.scheme.barup_outline,
847
+ )
848
+
849
+ # Plot down candles
850
+ if len(down) > 0:
851
+ fig.figure.segment(
852
+ down["datetime"],
853
+ down["high"],
854
+ down["datetime"],
855
+ down["low"],
856
+ color=self.scheme.bardown_wick,
857
+ )
858
+ fig.figure.vbar(
859
+ down["datetime"],
860
+ width,
861
+ down["open"],
862
+ down["close"],
863
+ fill_color=self.scheme.bardown,
864
+ line_color=self.scheme.bardown_outline,
865
+ )
866
+
867
+ def _plot_line(self, fig, df):
868
+ """Plot line chart.
869
+
870
+ Args:
871
+ fig: Figure instance
872
+ df: DataFrame
873
+ """
874
+ x_col = "datetime" if "datetime" in df.columns else "index"
875
+
876
+ if "close" in df.columns:
877
+ fig.figure.line(
878
+ df[x_col], df["close"], line_width=self.scheme.line_width, color=self.scheme.barup
879
+ )
880
+
881
+ def get_figurepage(self, figid):
882
+ """Get figure page.
883
+
884
+ Args:
885
+ figid: Figure page ID
886
+
887
+ Returns:
888
+ FigurePage instance or None
889
+ """
890
+ return self._figurepages.get(figid)
891
+
892
+ def get_last_idx(self, figid):
893
+ """Get last data index of figure page.
894
+
895
+ Args:
896
+ figid: Figure page ID
897
+
898
+ Returns:
899
+ int: Last index
900
+ """
901
+ figurepage = self.get_figurepage(figid)
902
+ if figurepage is None or figurepage._data is None:
903
+ return -1
904
+
905
+ if "index" in figurepage._data.columns:
906
+ return figurepage._data["index"].iloc[-1]
907
+ return len(figurepage._data) - 1
908
+
909
+ def generate_data(
910
+ self, figid=None, start=None, end=None, back=None, preserveidx=False, fill_gaps=False
911
+ ):
912
+ """Generate chart data.
913
+
914
+ Args:
915
+ figid: Figure page ID
916
+ start: Start index
917
+ end: End index
918
+ back: Number of bars to look back
919
+ preserveidx: Whether to preserve original index
920
+ fill_gaps: Whether to fill gaps
921
+
922
+ Returns:
923
+ pandas.DataFrame
924
+ """
925
+ if not PANDAS_AVAILABLE:
926
+ return None
927
+
928
+ figurepage = self.get_figurepage(figid) if figid is not None else None
929
+
930
+ if figurepage is None or figurepage._data is None:
931
+ return pd.DataFrame()
932
+
933
+ df = figurepage._data.copy()
934
+
935
+ # Apply range limits
936
+ if back is not None:
937
+ if end is not None:
938
+ start_idx = max(0, end - back + 1)
939
+ df = df.iloc[start_idx : end + 1]
940
+ else:
941
+ df = df.tail(back)
942
+ elif start is not None:
943
+ if "index" in df.columns:
944
+ df = df[df["index"] > start]
945
+ else:
946
+ df = df.iloc[start:]
947
+
948
+ if not preserveidx:
949
+ df = df.reset_index(drop=True)
950
+
951
+ return df
952
+
953
+ def update_figurepage(self, filter=None):
954
+ """Update figure page.
955
+
956
+ Args:
957
+ filter: Filter configuration
958
+ """
959
+ if filter is not None:
960
+ self._filter = filter
961
+
962
+ def generate_model_panels(self, figurepages=None):
963
+ """Generate model panels.
964
+
965
+ Returns:
966
+ list: Panel list
967
+ """
968
+ if not BOKEH_AVAILABLE:
969
+ return []
970
+
971
+ panels = []
972
+ source_pages = figurepages
973
+ if source_pages is None:
974
+ source_pages = self._figurepages.items()
975
+
976
+ for figid, figurepage in source_pages:
977
+ # Create chart panel
978
+ if figurepage.figures:
979
+ figures = [fig.figure for fig in figurepage.figures if fig.figure is not None]
980
+ if figures:
981
+ grid = gridplot(
982
+ [[fig] for fig in figures], sizing_mode=self.scheme.plot_sizing_mode
983
+ )
984
+ panel = Panel(child=grid, title="Charts")
985
+ panels.append(panel)
986
+
987
+ return panels
988
+
989
+ def build_model(self, figurepages=None):
990
+ """Build a bokeh model from figure pages.
991
+
992
+ Args:
993
+ figurepages: Iterable of (figid, figurepage) tuples. Defaults to all.
994
+
995
+ Returns:
996
+ Tabs model or None
997
+ """
998
+ if not BOKEH_AVAILABLE:
999
+ return None
1000
+
1001
+ from bokeh.models import Tabs
1002
+
1003
+ panels = self.generate_model_panels(figurepages=figurepages)
1004
+ if not panels:
1005
+ return None
1006
+
1007
+ return Tabs(tabs=[panel for panel in panels if panel is not None])
1008
+
1009
+ def build_full_model(self, figurepage=None, figurepages=None):
1010
+ """Build a Tabs model with chart panels plus the extra tabs.
1011
+
1012
+ Extra tabs (Performance/Analyzer/Metadata/Config/Log/Source) are attached
1013
+ to a single figure page. This is the reusable build path used by both
1014
+ ``plot()`` and the cerebro-compatible ``BokehPlot`` adapter; it performs
1015
+ no display and writes no file.
1016
+
1017
+ Args:
1018
+ figurepage: FigurePage to attach the extra tabs to. If ``None``, the
1019
+ most recently created figure page is used, or the extra tabs are
1020
+ skipped when no figure page exists.
1021
+ figurepages: Iterable of ``(figid, figurepage)`` tuples for the chart
1022
+ panels. Defaults to all accumulated figure pages.
1023
+
1024
+ Returns:
1025
+ Bokeh ``Tabs`` model, or ``None`` if bokeh is unavailable or no
1026
+ panels could be generated.
1027
+ """
1028
+ if not BOKEH_AVAILABLE:
1029
+ _logger.error("Bokeh is not available")
1030
+ return None
1031
+
1032
+ model = self.build_model(figurepages=figurepages)
1033
+ panels = list(model.tabs) if model is not None else []
1034
+
1035
+ # Extra tabs attach to a single figure page; prefer the explicit one,
1036
+ # else fall back to the most recently created.
1037
+ if figurepage is None and self._figurepages:
1038
+ figurepage = next(reversed(self._figurepages.values()))
1039
+
1040
+ if figurepage is not None:
1041
+ for tab_class in self.tabs:
1042
+ tab = tab_class(self, figurepage, None)
1043
+ if tab.is_useable():
1044
+ panels.append(tab.get_panel())
1045
+
1046
+ if not panels:
1047
+ _logger.warning("No panels generated")
1048
+ return None
1049
+
1050
+ from bokeh.models import Tabs
1051
+
1052
+ if model is not None:
1053
+ model.tabs = [p for p in panels if p is not None]
1054
+ return model
1055
+ return Tabs(tabs=[p for p in panels if p is not None])
1056
+
1057
+ def plot(self, strategy=None, show=True, filename=None):
1058
+ """Bindto strategy and generate static chart.
1059
+
1060
+ Args:
1061
+ strategy: Strategy instance
1062
+ show: Whether to show chart
1063
+ filename: Save filename
1064
+
1065
+ Returns:
1066
+ Bokeh model or None
1067
+ """
1068
+ if not BOKEH_AVAILABLE:
1069
+ _logger.error("Bokeh is not available")
1070
+ return None
1071
+
1072
+ if strategy is None:
1073
+ _logger.warning("No strategy provided")
1074
+ return None
1075
+
1076
+ # Create figure page
1077
+ figid, figurepage = self.create_figurepage(strategy, filldata=True)
1078
+
1079
+ model = self.build_full_model(figurepage=figurepage)
1080
+ if model is None:
1081
+ return None
1082
+
1083
+ if show:
1084
+ from bokeh.io import show as bokeh_show
1085
+
1086
+ bokeh_show(model)
1087
+
1088
+ if filename:
1089
+ from bokeh.io import output_file, save
1090
+
1091
+ output_file(filename)
1092
+ save(model)
1093
+
1094
+ return model