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,1651 @@
1
+ #!/usr/bin/env python
2
+ """Repeated functional-test indicators migrated to contrib.
3
+
4
+ These indicators are re-exported by ``backtrader.indicators`` and are
5
+ therefore available as ``Xxx``. Some historical same-name test
6
+ classes had incompatible line contracts; those variants use explicit names.
7
+ """
8
+
9
+ import math
10
+ from collections import deque
11
+
12
+ from .. import (
13
+ EMA,
14
+ AverageDirectionalMovementIndex,
15
+ AverageTrueRange,
16
+ ExponentialMovingAverage,
17
+ Highest,
18
+ If,
19
+ Indicator,
20
+ Lowest,
21
+ MinusDirectionalIndicator,
22
+ ParabolicSAR,
23
+ PlusDirectionalIndicator,
24
+ SimpleMovingAverage,
25
+ SmoothedMovingAverage,
26
+ StandardDeviation,
27
+ StochasticFull,
28
+ WeightedMovingAverage,
29
+ )
30
+
31
+ __all__ = [
32
+ "SkyscraperFixIndicator",
33
+ "SkyscraperFixDuplexIndicator",
34
+ "SkyscraperFixColorAMLIndicator",
35
+ "AppliedPriceCCI",
36
+ "ColorAMLIndicator",
37
+ "ColorAMLMeanReversionIndicator",
38
+ "X2MACandleApprox",
39
+ "XPeriodCandleColor",
40
+ "XPeriodCandleSystemColor",
41
+ "AcceleratorOscillator",
42
+ "AIAcceleratorOscillator",
43
+ "AdaptiveMarketLevel",
44
+ "AmlIndicator",
45
+ "FunctionalAwesomeOscillator",
46
+ "AIAwesomeOscillator",
47
+ "BlauErgodicMDI",
48
+ "BlauErgodicMDIClassic",
49
+ "BrakeExpIndicator",
50
+ "FlatTrendIndicator",
51
+ "FlatTrendDistanceIndicator",
52
+ "IinMASignalIndicator",
53
+ "KDJ",
54
+ "LaguerreIndicator",
55
+ "LaguerreColorIndicator",
56
+ "RelativeVigorIndex",
57
+ "SmoothedRelativeVigorIndex",
58
+ "SafeCCI",
59
+ "SafeCCIWithFactor",
60
+ "SilverTrendSignalProxy",
61
+ "SilverTrendDirectionSignalProxy",
62
+ ]
63
+
64
+
65
+ def _price_series(data, mode):
66
+ key = str(mode).lower()
67
+ if key in ("1", "close", "price_close"):
68
+ return data.close
69
+ if key in ("2", "open", "price_open"):
70
+ return data.open
71
+ if key in ("3", "high", "price_high"):
72
+ return data.high
73
+ if key in ("4", "low", "price_low"):
74
+ return data.low
75
+ if key in ("5", "median", "price_median"):
76
+ return (data.high + data.low) / 2.0
77
+ if key in ("6", "typical", "price_typical"):
78
+ return (data.high + data.low + data.close) / 3.0
79
+ if key in ("7", "weighted", "price_weighted"):
80
+ return (data.high + data.low + data.close + data.close) / 4.0
81
+ if key in ("8", "simple", "price_simpl"):
82
+ return (data.open + data.close) / 2.0
83
+ if key in ("9", "quarter", "price_quarter"):
84
+ return (data.high + data.low + data.open + data.close) / 4.0
85
+ return data.close
86
+
87
+
88
+ def resolve_ma_class(name):
89
+ """Resolve a moving average class by name.
90
+
91
+ Args:
92
+ name: Moving average mode name (e.g., 'sma', 'ema', 'jjma', 't3').
93
+
94
+ Returns:
95
+ The corresponding moving average indicator class.
96
+ """
97
+ mode = str(name).lower()
98
+ if mode in {"mode_sma", "sma"}:
99
+ return SimpleMovingAverage
100
+ if mode in {
101
+ "mode_ema",
102
+ "ema",
103
+ "mode_jjma",
104
+ "jjma",
105
+ "mode_jurx",
106
+ "jurx",
107
+ "mode_parma",
108
+ "parma",
109
+ "mode_t3",
110
+ "t3",
111
+ "mode_vidya",
112
+ "vidya",
113
+ "mode_ama",
114
+ "ama",
115
+ }:
116
+ return ExponentialMovingAverage
117
+ if mode in {"mode_smma", "smma"}:
118
+ return SmoothedMovingAverage
119
+ return WeightedMovingAverage
120
+
121
+
122
+ class SkyscraperFixIndicator(Indicator):
123
+ """Channel-like adaptive indicator emitting buy/sell buffers and color state."""
124
+
125
+ lines = ("up_buffer", "dn_buffer", "buy_buffer", "sell_buffer", "color_state")
126
+ params = (
127
+ ("length", 10),
128
+ ("kv", 0.9),
129
+ ("percentage", 0.0),
130
+ ("use_high_low", True),
131
+ ("atr_period", 15),
132
+ ("point_size", 0.01),
133
+ )
134
+
135
+ def __init__(self):
136
+ """Initialize internal ATR state and channel persistence variables."""
137
+ self.addminperiod(max(self.p.length, self.p.atr_period) + 3)
138
+ self.atr = AverageTrueRange(self.data, period=self.p.atr_period)
139
+ self.atr_high = Highest(self.atr, period=self.p.length)
140
+ self.atr_low = Lowest(self.atr, period=self.p.length)
141
+ self._prev_smin = None
142
+ self._prev_smax = None
143
+ self._prev_trend = 0
144
+
145
+ @staticmethod
146
+ def _nan():
147
+ return float("nan")
148
+
149
+ @staticmethod
150
+ def _valid(value):
151
+ return value is not None and math.isfinite(value)
152
+
153
+ def next(self):
154
+ """Calculate up/down channel levels, pending buffers, and current color."""
155
+ up = self._nan()
156
+ dn = self._nan()
157
+ buy = self._nan()
158
+ sell = self._nan()
159
+ color = (
160
+ self.lines.color_state[-1]
161
+ if len(self) > 1 and math.isfinite(self.lines.color_state[-1])
162
+ else 1.0
163
+ )
164
+ if self._prev_smin is None:
165
+ close = float(self.data.close[0])
166
+ self._prev_smin = close
167
+ self._prev_smax = close
168
+ self._prev_trend = 0
169
+ self.lines.up_buffer[0] = up
170
+ self.lines.dn_buffer[0] = dn
171
+ self.lines.buy_buffer[0] = buy
172
+ self.lines.sell_buffer[0] = sell
173
+ self.lines.color_state[0] = color
174
+ return
175
+ atrmax = float(self.atr_high[0])
176
+ atrmin = float(self.atr_low[0])
177
+ if not math.isfinite(atrmax) or not math.isfinite(atrmin):
178
+ self.lines.up_buffer[0] = up
179
+ self.lines.dn_buffer[0] = dn
180
+ self.lines.buy_buffer[0] = buy
181
+ self.lines.sell_buffer[0] = sell
182
+ self.lines.color_state[0] = color
183
+ return
184
+ step = int(0.5 * self.p.kv * (atrmax + atrmin) / self.p.point_size)
185
+ xstep = step * self.p.point_size
186
+ x2step = 2.0 * xstep
187
+ close = float(self.data.close[0])
188
+ high = float(self.data.high[0])
189
+ low = float(self.data.low[0])
190
+ if self.p.use_high_low:
191
+ smax0 = low + x2step
192
+ smin0 = high - x2step
193
+ else:
194
+ smax0 = close + x2step
195
+ smin0 = close - x2step
196
+ trend0 = self._prev_trend
197
+ if close > self._prev_smax:
198
+ trend0 = 1
199
+ if close < self._prev_smin:
200
+ trend0 = -1
201
+ if trend0 > 0:
202
+ smin0 = max(smin0, self._prev_smin)
203
+ up = smin0
204
+ color = 0.0
205
+ else:
206
+ smax0 = min(smax0, self._prev_smax)
207
+ dn = smax0
208
+ color = 1.0
209
+ prev_up = self.lines.up_buffer[-1] if len(self) > 1 else self._nan()
210
+ prev_dn = self.lines.dn_buffer[-1] if len(self) > 1 else self._nan()
211
+ if self._valid(prev_dn) and self._valid(up):
212
+ buy = up
213
+ if self._valid(prev_up) and self._valid(dn):
214
+ sell = dn
215
+ self.lines.up_buffer[0] = up
216
+ self.lines.dn_buffer[0] = dn
217
+ self.lines.buy_buffer[0] = buy
218
+ self.lines.sell_buffer[0] = sell
219
+ self.lines.color_state[0] = color
220
+ self._prev_smin = smin0
221
+ self._prev_smax = smax0
222
+ self._prev_trend = trend0
223
+
224
+
225
+ class SkyscraperFixDuplexIndicator(Indicator):
226
+ """Indicator for directional buffer-based skyline reversals."""
227
+
228
+ lines = ("up_buffer", "dn_buffer", "buy_buffer", "sell_buffer")
229
+ params = (
230
+ ("length", 10),
231
+ ("kv", 0.9),
232
+ ("percentage", 0.0),
233
+ ("use_high_low", True),
234
+ ("atr_period", 15),
235
+ ("point_size", 0.01),
236
+ )
237
+
238
+ def __init__(self):
239
+ """Build ATR state and initialize rolling trend context."""
240
+ self.addminperiod(max(self.p.length, self.p.atr_period) + 2)
241
+ self.atr = AverageTrueRange(self.data, period=self.p.atr_period)
242
+ self.atr_high = Highest(self.atr, period=self.p.length)
243
+ self.atr_low = Lowest(self.atr, period=self.p.length)
244
+ self._prev_smin = None
245
+ self._prev_smax = None
246
+ self._prev_trend = 0
247
+
248
+ @staticmethod
249
+ def _nan():
250
+ return float("nan")
251
+
252
+ @staticmethod
253
+ def _valid(value):
254
+ return value is not None and math.isfinite(value)
255
+
256
+ def next(self):
257
+ """Update the skyscraper buffers for the current bar."""
258
+ up = self._nan()
259
+ dn = self._nan()
260
+ buy = self._nan()
261
+ sell = self._nan()
262
+
263
+ if self._prev_smin is None:
264
+ self._prev_smin = float(self.data.close[0])
265
+ self._prev_smax = float(self.data.close[0])
266
+ self._prev_trend = 0
267
+ self.lines.up_buffer[0] = up
268
+ self.lines.dn_buffer[0] = dn
269
+ self.lines.buy_buffer[0] = buy
270
+ self.lines.sell_buffer[0] = sell
271
+ return
272
+
273
+ atrmax = float(self.atr_high[0])
274
+ atrmin = float(self.atr_low[0])
275
+ if not math.isfinite(atrmax) or not math.isfinite(atrmin):
276
+ self.lines.up_buffer[0] = up
277
+ self.lines.dn_buffer[0] = dn
278
+ self.lines.buy_buffer[0] = buy
279
+ self.lines.sell_buffer[0] = sell
280
+ return
281
+
282
+ step = int(0.5 * self.p.kv * (atrmax + atrmin) / self.p.point_size)
283
+ xstep = step * self.p.point_size
284
+ x2step = 2.0 * xstep
285
+
286
+ close = float(self.data.close[0])
287
+ high = float(self.data.high[0])
288
+ low = float(self.data.low[0])
289
+
290
+ if self.p.use_high_low:
291
+ smax0 = low + x2step
292
+ smin0 = high - x2step
293
+ else:
294
+ smax0 = close + x2step
295
+ smin0 = close - x2step
296
+
297
+ trend0 = self._prev_trend
298
+ if close > self._prev_smax:
299
+ trend0 = 1
300
+ if close < self._prev_smin:
301
+ trend0 = -1
302
+
303
+ if trend0 > 0:
304
+ smin0 = max(smin0, self._prev_smin)
305
+ up = smin0
306
+ else:
307
+ smax0 = min(smax0, self._prev_smax)
308
+ dn = smax0
309
+
310
+ prev_up = self.lines.up_buffer[-1] if len(self) > 1 else self._nan()
311
+ prev_dn = self.lines.dn_buffer[-1] if len(self) > 1 else self._nan()
312
+
313
+ if self._valid(prev_dn) and self._valid(up):
314
+ buy = up
315
+ if self._valid(prev_up) and self._valid(dn):
316
+ sell = dn
317
+
318
+ self.lines.up_buffer[0] = up
319
+ self.lines.dn_buffer[0] = dn
320
+ self.lines.buy_buffer[0] = buy
321
+ self.lines.sell_buffer[0] = sell
322
+
323
+ self._prev_smin = smin0
324
+ self._prev_smax = smax0
325
+ self._prev_trend = trend0
326
+
327
+
328
+ class SkyscraperFixColorAMLIndicator(Indicator):
329
+ """Skyscraper fix channel indicator producing buy/sell buffers and color state."""
330
+
331
+ lines = ("up_buffer", "dn_buffer", "buy_buffer", "sell_buffer", "color_state")
332
+ params = (
333
+ ("length", 10),
334
+ ("kv", 0.9),
335
+ ("percentage", 0.0),
336
+ ("use_high_low", True),
337
+ ("atr_period", 15),
338
+ ("point_size", 0.01),
339
+ )
340
+
341
+ def __init__(self):
342
+ """Initialize ATR-derived channel state and lookback counters."""
343
+ self.addminperiod(max(self.p.length, self.p.atr_period) + 3)
344
+ self.atr = AverageTrueRange(self.data, period=self.p.atr_period)
345
+ self.atr_high = Highest(self.atr, period=self.p.length)
346
+ self.atr_low = Lowest(self.atr, period=self.p.length)
347
+ self._prev_smin = None
348
+ self._prev_smax = None
349
+ self._prev_trend = 0
350
+
351
+ @staticmethod
352
+ def _nan():
353
+ return float("nan")
354
+
355
+ @staticmethod
356
+ def _valid(value):
357
+ return value is not None and math.isfinite(value)
358
+
359
+ def next(self):
360
+ """Compute current channel extrema and potential reversal buffers."""
361
+ up = self._nan()
362
+ dn = self._nan()
363
+ buy = self._nan()
364
+ sell = self._nan()
365
+ color = (
366
+ self.lines.color_state[-1]
367
+ if len(self) > 1 and math.isfinite(self.lines.color_state[-1])
368
+ else 1.0
369
+ )
370
+ if self._prev_smin is None:
371
+ close = float(self.data.close[0])
372
+ self._prev_smin = close
373
+ self._prev_smax = close
374
+ self._prev_trend = 0
375
+ self.lines.up_buffer[0] = up
376
+ self.lines.dn_buffer[0] = dn
377
+ self.lines.buy_buffer[0] = buy
378
+ self.lines.sell_buffer[0] = sell
379
+ self.lines.color_state[0] = color
380
+ return
381
+ atrmax = float(self.atr_high[0])
382
+ atrmin = float(self.atr_low[0])
383
+ if not math.isfinite(atrmax) or not math.isfinite(atrmin):
384
+ self.lines.up_buffer[0] = up
385
+ self.lines.dn_buffer[0] = dn
386
+ self.lines.buy_buffer[0] = buy
387
+ self.lines.sell_buffer[0] = sell
388
+ self.lines.color_state[0] = color
389
+ return
390
+ step = int(0.5 * self.p.kv * (atrmax + atrmin) / self.p.point_size)
391
+ x2step = 2.0 * step * self.p.point_size
392
+ close = float(self.data.close[0])
393
+ high = float(self.data.high[0])
394
+ low = float(self.data.low[0])
395
+ if self.p.use_high_low:
396
+ smax0 = low + x2step
397
+ smin0 = high - x2step
398
+ else:
399
+ smax0 = close + x2step
400
+ smin0 = close - x2step
401
+ trend0 = self._prev_trend
402
+ if close > self._prev_smax:
403
+ trend0 = 1
404
+ if close < self._prev_smin:
405
+ trend0 = -1
406
+ if trend0 > 0:
407
+ smin0 = max(smin0, self._prev_smin)
408
+ up = smin0
409
+ color = 0.0
410
+ else:
411
+ smax0 = min(smax0, self._prev_smax)
412
+ dn = smax0
413
+ color = 1.0
414
+ prev_up = self.lines.up_buffer[-1] if len(self) > 1 else self._nan()
415
+ prev_dn = self.lines.dn_buffer[-1] if len(self) > 1 else self._nan()
416
+ if self._valid(prev_dn) and self._valid(up):
417
+ buy = up
418
+ if self._valid(prev_up) and self._valid(dn):
419
+ sell = dn
420
+ self.lines.up_buffer[0] = up
421
+ self.lines.dn_buffer[0] = dn
422
+ self.lines.buy_buffer[0] = buy
423
+ self.lines.sell_buffer[0] = sell
424
+ self.lines.color_state[0] = color
425
+ self._prev_smin = smin0
426
+ self._prev_smax = smax0
427
+ self._prev_trend = trend0
428
+
429
+
430
+ class AppliedPriceCCI(Indicator):
431
+ """Commodity Channel Index computed on an arbitrary applied-price line."""
432
+
433
+ lines = ("cci",)
434
+ params = (
435
+ ("period", 14),
436
+ ("factor", 0.015),
437
+ )
438
+
439
+ def __init__(self):
440
+ """Set the warm-up period to one bar beyond the CCI lookback."""
441
+ self.addminperiod(int(self.p.period) + 1)
442
+
443
+ def next(self):
444
+ """Emit the CCI value: price deviation from its mean over mean abs deviation."""
445
+ period = int(self.p.period)
446
+ prices = [float(self.data[-i]) for i in range(period)]
447
+ mean_price = sum(prices) / period
448
+ mean_dev = sum(abs(price - mean_price) for price in prices) / period
449
+ denom = float(self.p.factor) * mean_dev
450
+ if denom == 0:
451
+ self.lines.cci[0] = 0.0
452
+ return
453
+ self.lines.cci[0] = (float(self.data[0]) - mean_price) / denom
454
+
455
+
456
+ class ColorAMLIndicator(Indicator):
457
+ """Fractal-driven adaptive moving line with trend color transitions."""
458
+
459
+ lines = ("aml", "color_state")
460
+ params = (
461
+ ("fractal", 6),
462
+ ("lag", 7),
463
+ ("shift", 0),
464
+ ("point_size", 0.01),
465
+ )
466
+
467
+ def __init__(self):
468
+ """Initialize smoothing buffers and state for AML computation."""
469
+ self.addminperiod(2 * self.p.fractal + self.p.lag + 5)
470
+ self._smooth_history = []
471
+ self._prev_aml = None
472
+ self._prev_color = 1.0
473
+
474
+ @staticmethod
475
+ def _window_max(line, start_ago, size):
476
+ values = [float(line[-(start_ago + idx)]) for idx in range(size)]
477
+ return max(values)
478
+
479
+ @staticmethod
480
+ def _window_min(line, start_ago, size):
481
+ values = [float(line[-(start_ago + idx)]) for idx in range(size)]
482
+ return min(values)
483
+
484
+ def next(self):
485
+ """Update AML line value and color state for the current candle."""
486
+ if len(self.data) < 2 * self.p.fractal + self.p.lag + 2:
487
+ self.lines.aml[0] = float("nan")
488
+ self.lines.color_state[0] = self._prev_color
489
+ return
490
+ r1 = (
491
+ self._window_max(self.data.high, 0, self.p.fractal)
492
+ - self._window_min(self.data.low, 0, self.p.fractal)
493
+ ) / float(self.p.fractal)
494
+ r2 = (
495
+ self._window_max(self.data.high, self.p.fractal, self.p.fractal)
496
+ - self._window_min(self.data.low, self.p.fractal, self.p.fractal)
497
+ ) / float(self.p.fractal)
498
+ r3 = (
499
+ self._window_max(self.data.high, 0, 2 * self.p.fractal)
500
+ - self._window_min(self.data.low, 0, 2 * self.p.fractal)
501
+ ) / float(2 * self.p.fractal)
502
+ dim = 0.0
503
+ if r1 + r2 > 0 and r3 > 0:
504
+ dim = (math.log(r1 + r2) - math.log(r3)) * 1.44269504088896
505
+ alpha = math.exp(-self.p.lag * (dim - 1.0))
506
+ alpha = min(alpha, 1.0)
507
+ alpha = max(alpha, 0.01)
508
+ price = (
509
+ float(self.data.high[0])
510
+ + float(self.data.low[0])
511
+ + 2.0 * float(self.data.open[0])
512
+ + 2.0 * float(self.data.close[0])
513
+ ) / 6.0
514
+ prev_smooth = self._smooth_history[-1] if self._smooth_history else price
515
+ smooth = alpha * price + (1.0 - alpha) * prev_smooth
516
+ self._smooth_history.append(smooth)
517
+ prev_aml = self._prev_aml if self._prev_aml is not None else smooth
518
+ lag_smooth = (
519
+ self._smooth_history[-(self.p.lag + 1)]
520
+ if len(self._smooth_history) > self.p.lag
521
+ else smooth
522
+ )
523
+ if abs(smooth - lag_smooth) >= self.p.lag * self.p.lag * self.p.point_size:
524
+ aml = smooth
525
+ else:
526
+ aml = prev_aml
527
+ color = self._prev_color
528
+ if aml > prev_aml:
529
+ color = 2.0
530
+ if aml < prev_aml:
531
+ color = 0.0
532
+ self.lines.aml[0] = aml
533
+ self.lines.color_state[0] = color
534
+ self._prev_aml = aml
535
+ self._prev_color = color
536
+
537
+
538
+ class ColorAMLMeanReversionIndicator(Indicator):
539
+ """Adaptive moving-lowpass indicator for color-state trend capture."""
540
+
541
+ lines = ("aml", "color_state")
542
+ params = (
543
+ ("fractal", 6),
544
+ ("lag", 7),
545
+ ("shift", 0),
546
+ ("point_size", 0.01),
547
+ )
548
+
549
+ def __init__(self):
550
+ """Prepare smoothing buffers and history for AML color-state generation."""
551
+ self.addminperiod(2 * self.p.fractal + self.p.lag + 5)
552
+ self._smooth_history = []
553
+ self._prev_aml = None
554
+ self._prev_color = 1.0
555
+
556
+ @staticmethod
557
+ def _window_max(line, start_ago, size):
558
+ values = [float(line[-(start_ago + idx)]) for idx in range(size)]
559
+ return max(values)
560
+
561
+ @staticmethod
562
+ def _window_min(line, start_ago, size):
563
+ values = [float(line[-(start_ago + idx)]) for idx in range(size)]
564
+ return min(values)
565
+
566
+ def next(self):
567
+ """Update smooth and color values for the active bar."""
568
+ if len(self.data) < 2 * self.p.fractal + self.p.lag + 2:
569
+ self.lines.aml[0] = float("nan")
570
+ self.lines.color_state[0] = self._prev_color
571
+ return
572
+ r1 = (
573
+ self._window_max(self.data.high, 0, self.p.fractal)
574
+ - self._window_min(self.data.low, 0, self.p.fractal)
575
+ ) / float(self.p.fractal)
576
+ r2 = (
577
+ self._window_max(self.data.high, self.p.fractal, self.p.fractal)
578
+ - self._window_min(self.data.low, self.p.fractal, self.p.fractal)
579
+ ) / float(self.p.fractal)
580
+ r3 = (
581
+ self._window_max(self.data.high, 0, 2 * self.p.fractal)
582
+ - self._window_min(self.data.low, 0, 2 * self.p.fractal)
583
+ ) / float(2 * self.p.fractal)
584
+ dim = 0.0
585
+ if r1 + r2 > 0 and r3 > 0:
586
+ dim = (math.log(r1 + r2) - math.log(r3)) * 1.44269504088896
587
+ alpha = math.exp(-self.p.lag * (dim - 1.0))
588
+ alpha = min(alpha, 1.0)
589
+ alpha = max(alpha, 0.01)
590
+ price = (
591
+ float(self.data.high[0])
592
+ + float(self.data.low[0])
593
+ + 2.0 * float(self.data.open[0])
594
+ + 2.0 * float(self.data.close[0])
595
+ ) / 6.0
596
+ prev_smooth = self._smooth_history[-1] if self._smooth_history else price
597
+ smooth = alpha * price + (1.0 - alpha) * prev_smooth
598
+ self._smooth_history.append(smooth)
599
+ prev_aml = self._prev_aml if self._prev_aml is not None else smooth
600
+ lag_smooth = (
601
+ self._smooth_history[-(self.p.lag + 1)]
602
+ if len(self._smooth_history) > self.p.lag
603
+ else smooth
604
+ )
605
+ aml = (
606
+ smooth
607
+ if abs(smooth - lag_smooth) >= self.p.lag * self.p.lag * self.p.point_size
608
+ else prev_aml
609
+ )
610
+ color = self._prev_color
611
+ if aml > prev_aml:
612
+ color = 2.0
613
+ if aml < prev_aml:
614
+ color = 0.0
615
+ self.lines.aml[0] = aml
616
+ self.lines.color_state[0] = color
617
+ self._prev_aml = aml
618
+ self._prev_color = color
619
+
620
+
621
+ class X2MACandleApprox(Indicator):
622
+ """Two-stage moving approximation of candle structure and color."""
623
+
624
+ lines = ("open_value", "high_value", "low_value", "close_value", "color_state")
625
+ params = (
626
+ ("length1", 12),
627
+ ("phase1", 15),
628
+ ("length2", 5),
629
+ ("phase2", 15),
630
+ ("gap", 10.0),
631
+ )
632
+
633
+ def __init__(self):
634
+ """Initialize rolling queues and two-stage smoothing states."""
635
+ self._length1 = max(1, int(self.p.length1))
636
+ self._length2 = max(2, int(self.p.length2))
637
+ self._phase2 = max(-100, min(100, int(self.p.phase2)))
638
+ base_alpha = 2.0 / (self._length2 + 1.0)
639
+ self._alpha = max(0.01, min(0.95, base_alpha * (1.0 + self._phase2 / 200.0)))
640
+ self._phase_gain = self._phase2 / 200.0
641
+ self._queues = {
642
+ "open": deque(maxlen=self._length1),
643
+ "high": deque(maxlen=self._length1),
644
+ "low": deque(maxlen=self._length1),
645
+ "close": deque(maxlen=self._length1),
646
+ }
647
+ self._states = {
648
+ "open": {"ema1": None, "ema2": None},
649
+ "high": {"ema1": None, "ema2": None},
650
+ "low": {"ema1": None, "ema2": None},
651
+ "close": {"ema1": None, "ema2": None},
652
+ }
653
+ self.addminperiod(self._length1 + self._length2)
654
+
655
+ @staticmethod
656
+ def _finite(value):
657
+ return value is not None and math.isfinite(value)
658
+
659
+ def _sma(self, key, value):
660
+ queue = self._queues[key]
661
+ queue.append(float(value))
662
+ if len(queue) < self._length1:
663
+ return None
664
+ return sum(queue) / len(queue)
665
+
666
+ def _smooth(self, key, value):
667
+ state = self._states[key]
668
+ if state["ema1"] is None:
669
+ state["ema1"] = value
670
+ state["ema2"] = value
671
+ else:
672
+ state["ema1"] = state["ema1"] + self._alpha * (value - state["ema1"])
673
+ state["ema2"] = state["ema2"] + self._alpha * (state["ema1"] - state["ema2"])
674
+ return state["ema1"] + self._phase_gain * (state["ema1"] - state["ema2"])
675
+
676
+ def _stage_value(self, key, line):
677
+ sma_value = self._sma(key, line[0])
678
+ if sma_value is None:
679
+ return None
680
+ return self._smooth(key, sma_value)
681
+
682
+ def next(self):
683
+ """Update approximated open/high/low/close and color from historical window."""
684
+ open_value = self._stage_value("open", self.data.open)
685
+ high_value = self._stage_value("high", self.data.high)
686
+ low_value = self._stage_value("low", self.data.low)
687
+ close_value = self._stage_value("close", self.data.close)
688
+ if not all(self._finite(v) for v in (open_value, high_value, low_value, close_value)):
689
+ self.lines.open_value[0] = float("nan")
690
+ self.lines.high_value[0] = float("nan")
691
+ self.lines.low_value[0] = float("nan")
692
+ self.lines.close_value[0] = float("nan")
693
+ self.lines.color_state[0] = float("nan")
694
+ return
695
+ max_value = max(open_value, close_value, high_value, low_value)
696
+ min_value = min(open_value, close_value, high_value, low_value)
697
+ adjusted_open = open_value
698
+ if len(self) > 1 and abs(float(self.data.open[0]) - float(self.data.close[0])) <= float(
699
+ self.p.gap
700
+ ):
701
+ prev_close = float(self.lines.close_value[-1])
702
+ if self._finite(prev_close):
703
+ adjusted_open = prev_close
704
+ color_state = (
705
+ 2.0 if adjusted_open < close_value else 0.0 if adjusted_open > close_value else 1.0
706
+ )
707
+ self.lines.open_value[0] = adjusted_open
708
+ self.lines.high_value[0] = max_value
709
+ self.lines.low_value[0] = min_value
710
+ self.lines.close_value[0] = close_value
711
+ self.lines.color_state[0] = color_state
712
+
713
+
714
+ class XPeriodCandleColor(Indicator):
715
+ """Smoothing-based period-candle indicator producing color index."""
716
+
717
+ lines = ("color_idx", "xopen", "xclose", "xhigh", "xlow")
718
+ params = (
719
+ ("cperiod", 5),
720
+ ("ma_length", 3),
721
+ )
722
+
723
+ def __init__(self):
724
+ """Build smoothed OHLC components and set minimum bars."""
725
+ self.smooth_open = SimpleMovingAverage(self.data.open, period=self.p.ma_length)
726
+ self.smooth_high = SimpleMovingAverage(self.data.high, period=self.p.ma_length)
727
+ self.smooth_low = SimpleMovingAverage(self.data.low, period=self.p.ma_length)
728
+ self.smooth_close = SimpleMovingAverage(self.data.close, period=self.p.ma_length)
729
+ self.addminperiod(self.p.ma_length + self.p.cperiod)
730
+
731
+ def next(self):
732
+ """Compute synthetic candle and color value for the current bar."""
733
+ lookback = max(1, int(self.p.cperiod))
734
+ start = -(lookback - 1)
735
+ xopen = float(self.smooth_open[start])
736
+ xclose = float(self.smooth_close[0])
737
+ highs = [float(self.smooth_high[-i]) for i in range(lookback)]
738
+ lows = [float(self.smooth_low[-i]) for i in range(lookback)]
739
+ self.lines.xopen[0] = xopen
740
+ self.lines.xclose[0] = xclose
741
+ self.lines.xhigh[0] = max(highs)
742
+ self.lines.xlow[0] = min(lows)
743
+ self.lines.color_idx[0] = 0.0 if xopen <= xclose else 2.0
744
+
745
+
746
+ class XPeriodCandleSystemColor(Indicator):
747
+ """SMA-smoothed candle color indicator with Bollinger Band breakout detection."""
748
+
749
+ lines = ("color_idx", "upper", "lower", "xopen", "xclose")
750
+ params = (
751
+ ("period", 5),
752
+ ("bb_length", 20),
753
+ ("bands_deviation", 1.001),
754
+ )
755
+
756
+ def __init__(self):
757
+ """Initialize SMA smoothing of OHLC and Bollinger Band components."""
758
+ self.smooth_open = SimpleMovingAverage(self.data.open, period=self.p.period)
759
+ self.smooth_high = SimpleMovingAverage(self.data.high, period=self.p.period)
760
+ self.smooth_low = SimpleMovingAverage(self.data.low, period=self.p.period)
761
+ self.smooth_close = SimpleMovingAverage(self.data.close, period=self.p.period)
762
+ self.mid = SimpleMovingAverage(self.smooth_close, period=self.p.bb_length)
763
+ self.std = StandardDeviation(self.smooth_close, period=self.p.bb_length)
764
+
765
+ def next(self):
766
+ """Assign color index based on smoothed candle direction and Bollinger Band position."""
767
+ xopen = float(self.smooth_open[0])
768
+ xclose = float(self.smooth_close[0])
769
+ upper = float(self.mid[0] + self.std[0] * self.p.bands_deviation)
770
+ lower = float(self.mid[0] - self.std[0] * self.p.bands_deviation)
771
+ color = 2.0
772
+ if xopen <= xclose:
773
+ color = 1.0
774
+ elif xopen > xclose:
775
+ color = 3.0
776
+ if xopen <= xclose and xclose > upper:
777
+ color = 0.0
778
+ if xopen > xclose and xclose < lower:
779
+ color = 4.0
780
+ self.lines.xopen[0] = xopen
781
+ self.lines.xclose[0] = xclose
782
+ self.lines.upper[0] = upper
783
+ self.lines.lower[0] = lower
784
+ self.lines.color_idx[0] = color
785
+
786
+
787
+ class AcceleratorOscillator(Indicator):
788
+ """Compute accelerator oscillator using short and long SMA of median price."""
789
+
790
+ lines = ("ac",)
791
+ params = ()
792
+
793
+ def __init__(self):
794
+ """Build the oscillator line from medians and SMA smoothing."""
795
+ median = (self.data.high + self.data.low) / 2.0
796
+ ao = SimpleMovingAverage(median, period=5) - SimpleMovingAverage(median, period=34)
797
+ self.lines.ac = ao - SimpleMovingAverage(ao, period=5)
798
+
799
+
800
+ class AIAcceleratorOscillator(Indicator):
801
+ """Accelerator Oscillator indicator computed from Awesome Oscillator."""
802
+
803
+ lines = ("ac",)
804
+
805
+ def __init__(self):
806
+ """Create an AO smoothed by a 5-period SMA."""
807
+ ao = AIAwesomeOscillator(self.data)
808
+ ao_sma = SimpleMovingAverage(ao.ao, period=5)
809
+ self.lines.ac = ao.ao - ao_sma
810
+
811
+
812
+ class AdaptiveMarketLevel(Indicator):
813
+ """Adaptive Market Level indicator using fractal dimension and adaptive smoothing.
814
+
815
+ The AML line adapts its smoothing alpha based on the measured fractal
816
+ dimension of the price range, providing faster response in trending markets
817
+ and slower response in mean-reverting regimes.
818
+ """
819
+
820
+ lines = ("aml",)
821
+ params = (
822
+ ("fractal", 70),
823
+ ("lag", 18),
824
+ ("shift", 0),
825
+ ("point", 0.01),
826
+ )
827
+
828
+ def __init__(self):
829
+ """Initialize history deques and minimum period for the indicator."""
830
+ self._smooth_history = []
831
+ self._aml_history = []
832
+ self._min_period = max(int(self.p.fractal) * 2 + int(self.p.lag), 1)
833
+
834
+ def _range(self, count, start):
835
+ highs = []
836
+ lows = []
837
+ for idx in range(start, start + count):
838
+ ago = -idx if idx else 0
839
+ highs.append(float(self.data.high[ago]))
840
+ lows.append(float(self.data.low[ago]))
841
+ return max(highs) - min(lows)
842
+
843
+ def next(self):
844
+ """Compute AML value using fractal-range adaptive smoothing."""
845
+ fractal = int(self.p.fractal)
846
+ lag = int(self.p.lag)
847
+ if len(self.data) < self._min_period:
848
+ self.lines.aml[0] = float(self.data.close[0])
849
+ return
850
+ r1 = self._range(fractal, 0) / fractal
851
+ r2 = self._range(fractal, fractal) / fractal
852
+ r3 = self._range(fractal * 2, 0) / (fractal * 2)
853
+ dim = 0.0
854
+ if r1 + r2 > 0 and r3 > 0:
855
+ dim = (math.log(r1 + r2) - math.log(r3)) * 1.44269504088896
856
+ alpha = math.exp(-lag * (dim - 1.0))
857
+ alpha = min(max(alpha, 0.01), 1.0)
858
+ price = (
859
+ float(self.data.high[0])
860
+ + float(self.data.low[0])
861
+ + 2.0 * float(self.data.open[0])
862
+ + 2.0 * float(self.data.close[0])
863
+ ) / 6.0
864
+ prev_smooth = self._smooth_history[-1] if self._smooth_history else 0.0
865
+ smooth = alpha * price + (1.0 - alpha) * prev_smooth
866
+ lagged_smooth = self._smooth_history[-lag] if len(self._smooth_history) >= lag else 0.0
867
+ prev_aml = self._aml_history[-1] if self._aml_history else smooth
868
+ threshold = lag * lag * float(self.p.point)
869
+ aml = smooth if abs(smooth - lagged_smooth) >= threshold else prev_aml
870
+ self._smooth_history.append(smooth)
871
+ self._aml_history.append(aml)
872
+ self.lines.aml[0] = aml
873
+
874
+
875
+ class AmlIndicator(Indicator):
876
+ """Adaptive Market Level indicator for backtrader (on-chart version).
877
+
878
+ Uses the same fractal-range adaptive smoothing logic as AdaptiveMarketLevel
879
+ but implemented as a Backtrader indicator with minperiod management.
880
+ """
881
+
882
+ lines = ("aml",)
883
+ params = (
884
+ ("fractal", 70),
885
+ ("lag", 18),
886
+ ("shift", 0),
887
+ ("point", 0.01),
888
+ )
889
+
890
+ def __init__(self):
891
+ """Initialize smoothing deque and minperiod based on fractal and lag."""
892
+ lag = max(1, int(self.p.lag))
893
+ fractal = max(1, int(self.p.fractal))
894
+ self._smooth = deque(maxlen=lag + 1)
895
+ self.addminperiod(max(fractal * 2 + 2, lag + 2))
896
+
897
+ def _range(self, start, count):
898
+ highs = [float(self.data.high[-(start + i)]) for i in range(count)]
899
+ lows = [float(self.data.low[-(start + i)]) for i in range(count)]
900
+ return max(highs) - min(lows)
901
+
902
+ def next(self):
903
+ """Compute AML value using fractal-range adaptive smoothing."""
904
+ fractal = max(1, int(self.p.fractal))
905
+ lag = max(1, int(self.p.lag))
906
+ price = (
907
+ float(self.data.high[0])
908
+ + float(self.data.low[0])
909
+ + 2.0 * float(self.data.open[0])
910
+ + 2.0 * float(self.data.close[0])
911
+ ) / 6.0
912
+
913
+ if len(self.data) < fractal * 2 + 1:
914
+ self._smooth.append(price)
915
+ self.lines.aml[0] = float(self.lines.aml[-1]) if len(self) > 1 else price
916
+ return
917
+
918
+ r1 = self._range(0, fractal) / fractal
919
+ r2 = self._range(fractal, fractal) / fractal
920
+ r3 = self._range(0, fractal * 2) / (fractal * 2)
921
+
922
+ dim = 0.0
923
+ if r1 + r2 > 0 and r3 > 0:
924
+ dim = (math.log(r1 + r2) - math.log(r3)) / math.log(2.0)
925
+
926
+ alpha = math.exp(-lag * (dim - 1.0))
927
+ alpha = min(1.0, max(0.01, alpha))
928
+
929
+ prev_smooth = self._smooth[-1] if self._smooth else 0.0
930
+ smooth = alpha * price + (1.0 - alpha) * prev_smooth
931
+ lagged_smooth = self._smooth[0] if len(self._smooth) == self._smooth.maxlen else 0.0
932
+ self._smooth.append(smooth)
933
+
934
+ if abs(smooth - lagged_smooth) >= lag * lag * float(self.p.point):
935
+ self.lines.aml[0] = smooth
936
+ else:
937
+ self.lines.aml[0] = float(self.lines.aml[-1]) if len(self) > 1 else smooth
938
+
939
+
940
+ class FunctionalAwesomeOscillator(Indicator):
941
+ """Awesome Oscillator: fast minus slow SMA of the median price."""
942
+
943
+ lines = ("ao",)
944
+ params = (
945
+ ("fast", 5),
946
+ ("slow", 34),
947
+ )
948
+
949
+ def __init__(self):
950
+ """Build the fast and slow median-price moving averages."""
951
+ median_price = (self.data.high + self.data.low) / 2.0
952
+ self._fast = SimpleMovingAverage(median_price, period=self.p.fast)
953
+ self._slow = SimpleMovingAverage(median_price, period=self.p.slow)
954
+
955
+ def next(self):
956
+ """Emit the fast/slow SMA difference for the current bar."""
957
+ self.lines.ao[0] = float(self._fast[0]) - float(self._slow[0])
958
+
959
+
960
+ class AIAwesomeOscillator(Indicator):
961
+ """Awesome Oscillator indicator using two SMAs on the price midpoint."""
962
+
963
+ lines = ("ao",)
964
+ params = (
965
+ ("fast", 5),
966
+ ("slow", 34),
967
+ )
968
+
969
+ def __init__(self):
970
+ """Create fast and slow moving averages of the midpoint."""
971
+ median = (self.data.high + self.data.low) / 2.0
972
+ fast_ma = SimpleMovingAverage(median, period=self.p.fast)
973
+ slow_ma = SimpleMovingAverage(median, period=self.p.slow)
974
+ self.lines.ao = fast_ma - slow_ma
975
+
976
+
977
+ class BlauErgodicMDI(Indicator):
978
+ """Calculate layered EMA histograms used as the Blau Ergodic MDI signal."""
979
+
980
+ lines = ("up", "dn", "hist", "color_idx")
981
+ params = (
982
+ ("xlength", 20),
983
+ ("xlength1", 5),
984
+ ("xlength2", 5),
985
+ ("xlength3", 5),
986
+ )
987
+
988
+ def __init__(self):
989
+ """Initialize recursive EMA stages and expose indicator lines."""
990
+ price = ExponentialMovingAverage(self.data.close, period=max(2, self.p.xlength))
991
+ xprice = ExponentialMovingAverage(price, period=max(2, self.p.xlength1))
992
+ dif = price - xprice
993
+ xdif = ExponentialMovingAverage(dif, period=max(2, self.p.xlength1))
994
+ xxdif = ExponentialMovingAverage(xdif, period=max(2, self.p.xlength2))
995
+ xxxdif = ExponentialMovingAverage(xxdif, period=max(2, self.p.xlength3))
996
+ self.lines.hist = xxdif
997
+ self.lines.up = xxdif
998
+ self.lines.dn = xxxdif
999
+ self.addminperiod(self.p.xlength + self.p.xlength1 + self.p.xlength2 + self.p.xlength3 + 2)
1000
+
1001
+
1002
+ class BlauErgodicMDIClassic(Indicator):
1003
+ """Ergodic MDI indicator with up/down/histogram smoothing channels."""
1004
+
1005
+ lines = ("up", "down", "hist")
1006
+ params = (
1007
+ ("xlength", 20),
1008
+ ("xlength1", 5),
1009
+ ("xlength2", 3),
1010
+ ("xlength3", 8),
1011
+ ("ipc", "close"),
1012
+ )
1013
+
1014
+ def __init__(self):
1015
+ """Build normalized price deviation and EMA-smoothed up/down/histogram lines."""
1016
+ price = _price_series(self.data, self.p.ipc)
1017
+ xprice = EMA(price, period=int(self.p.xlength))
1018
+ dif = (price - xprice) / 0.01
1019
+ xdif = EMA(dif, period=int(self.p.xlength1))
1020
+ xxdif = EMA(xdif, period=int(self.p.xlength2))
1021
+ xxxdif = EMA(xxdif, period=int(self.p.xlength3))
1022
+ self.l.hist = xxdif
1023
+ self.l.up = xxdif
1024
+ self.l.down = xxxdif
1025
+
1026
+
1027
+ class BrakeExpIndicator(Indicator):
1028
+ """Exponential trailing-stop indicator with trend and flip lines.
1029
+
1030
+ Maintains an exponential-curve stop that rises while long (and falls while
1031
+ short) from a begin price; when price breaks the stop the direction flips.
1032
+ Exposes the active stop on ``up_trend``/``down_trend`` lines and
1033
+ direction-flip cues on ``buy_signal``/``sell_signal`` lines.
1034
+ """
1035
+
1036
+ lines = ("up_trend", "down_trend", "buy_signal", "sell_signal")
1037
+ params = (
1038
+ ("a", 3.0),
1039
+ ("b", 1.0),
1040
+ )
1041
+
1042
+ def __init__(self):
1043
+ """Set the minimum period and initialize the exponential-stop state."""
1044
+ self.addminperiod(5)
1045
+ self._is_long = True
1046
+ self._max_price = float("-inf")
1047
+ self._min_price = float("inf")
1048
+ self._begin_bar = 0
1049
+ self._begin_price = None
1050
+
1051
+ def next(self):
1052
+ """Advance the exponential stop and emit trend and flip lines.
1053
+
1054
+ Extends the stop along the exponential curve, flips direction (resetting
1055
+ the begin price and extremes) when price breaks the stop, and sets the
1056
+ ``up_trend``/``down_trend`` lines plus ``buy_signal``/``sell_signal`` flip
1057
+ cues for the bar.
1058
+ """
1059
+ if self._begin_price is None:
1060
+ self._begin_price = float(self.data.low[0])
1061
+ self._max_price = max(self._max_price, float(self.data.high[0]))
1062
+ self._min_price = min(self._min_price, float(self.data.low[0]))
1063
+ bars_since_begin = max(0, len(self.data) - 1 - self._begin_bar)
1064
+ a = float(self.p.a) * 0.1
1065
+ b = float(self.p.b) * 0.00001
1066
+ exp_val = (math.exp(bars_since_begin * a) - 1.0) * b
1067
+ value = self._begin_price + exp_val if self._is_long else self._begin_price - exp_val
1068
+ if self._is_long and value > float(self.data.low[0]):
1069
+ self._is_long = False
1070
+ self._begin_price = self._max_price
1071
+ self._begin_bar = len(self.data) - 1
1072
+ value = self._begin_price
1073
+ self._max_price = float("-inf")
1074
+ self._min_price = float("inf")
1075
+ elif (not self._is_long) and value < float(self.data.high[0]):
1076
+ self._is_long = True
1077
+ self._begin_price = self._min_price
1078
+ self._begin_bar = len(self.data) - 1
1079
+ value = self._begin_price
1080
+ self._max_price = float("-inf")
1081
+ self._min_price = float("inf")
1082
+ prev_up = float(self.lines.up_trend[-1]) if len(self) > 0 else 0.0
1083
+ prev_dn = float(self.lines.down_trend[-1]) if len(self) > 0 else 0.0
1084
+ if self._is_long:
1085
+ self.lines.up_trend[0] = value
1086
+ self.lines.down_trend[0] = 0.0
1087
+ else:
1088
+ self.lines.up_trend[0] = 0.0
1089
+ self.lines.down_trend[0] = value
1090
+ self.lines.buy_signal[0] = (
1091
+ self.lines.down_trend[0]
1092
+ if prev_up > 0.0 and float(self.lines.down_trend[0]) > 0.0
1093
+ else 0.0
1094
+ )
1095
+ self.lines.sell_signal[0] = (
1096
+ self.lines.up_trend[0] if prev_dn > 0.0 and float(self.lines.up_trend[0]) > 0.0 else 0.0
1097
+ )
1098
+
1099
+
1100
+ class FlatTrendIndicator(Indicator):
1101
+ """Flat-trend regime indicator combining ADX/DI and Parabolic SAR.
1102
+
1103
+ Emits four binary lines (``buy``, ``sell``, ``end_buy``, ``end_sell``) that
1104
+ classify each bar's trend state from the SAR position relative to price and
1105
+ the dominance of the positive over the negative directional indicator.
1106
+ """
1107
+
1108
+ lines = ("sell", "buy", "end_sell", "end_buy")
1109
+
1110
+ def __init__(self):
1111
+ """Build the ADX, +DI, -DI and Parabolic SAR sub-indicators.
1112
+
1113
+ Also sets the minimum period to 20 bars so the directional and SAR
1114
+ components have enough history before producing signals.
1115
+ """
1116
+ self.adx = AverageDirectionalMovementIndex(self.data)
1117
+ self.di_plus = PlusDirectionalIndicator(self.data)
1118
+ self.di_minus = MinusDirectionalIndicator(self.data)
1119
+ self.sar = ParabolicSAR(self.data)
1120
+ self.addminperiod(20)
1121
+
1122
+ def next(self):
1123
+ """Classify the current bar into a buy/sell/end-of-trend state.
1124
+
1125
+ Sets exactly one of the four output lines to 1.0 based on whether the
1126
+ SAR sits below price (uptrend context) and whether +DI exceeds -DI.
1127
+ """
1128
+ sell = buy = end_sell = end_buy = 0.0
1129
+ if self.sar[0] < self.data.close[0]:
1130
+ if self.di_plus[0] > self.di_minus[0]:
1131
+ buy = 1.0
1132
+ else:
1133
+ end_buy = 1.0
1134
+ else:
1135
+ if self.di_plus[0] > self.di_minus[0]:
1136
+ end_sell = 1.0
1137
+ else:
1138
+ sell = 1.0
1139
+ self.lines.sell[0] = sell
1140
+ self.lines.buy[0] = buy
1141
+ self.lines.end_sell[0] = end_sell
1142
+ self.lines.end_buy[0] = end_buy
1143
+
1144
+
1145
+ class FlatTrendDistanceIndicator(Indicator):
1146
+ """Volatility-regime classifier from smoothed ATR and standard-deviation slopes.
1147
+
1148
+ Compares the slopes of smoothed ATR and smoothed standard deviation to emit a
1149
+ ``state`` line flagging rising, falling, or flat volatility.
1150
+ """
1151
+
1152
+ lines = ("state",)
1153
+ params = (
1154
+ ("stdev_period", 20),
1155
+ ("stdev_method", "lwma"),
1156
+ ("stdev_length", 5),
1157
+ ("stdev_phase", 15),
1158
+ ("atr_period", 20),
1159
+ ("atr_method", "lwma"),
1160
+ ("atr_length", 5),
1161
+ ("atr_phase", 15),
1162
+ )
1163
+
1164
+ def __init__(self):
1165
+ """Construct smoothed ATR and standard-deviation components, set min period."""
1166
+ self._atr = AverageTrueRange(self.data, period=max(1, int(self.p.atr_period)))
1167
+ self._std = StandardDeviation(self.data.close, period=max(1, int(self.p.stdev_period)))
1168
+ atr_ma = resolve_ma_class(self.p.atr_method)
1169
+ std_ma = resolve_ma_class(self.p.stdev_method)
1170
+ self._xatr = atr_ma(self._atr, period=max(1, int(self.p.atr_length)))
1171
+ self._xstd = std_ma(self._std, period=max(1, int(self.p.stdev_length)))
1172
+ self.addminperiod(
1173
+ max(
1174
+ int(self.p.atr_period) + int(self.p.atr_length),
1175
+ int(self.p.stdev_period) + int(self.p.stdev_length),
1176
+ )
1177
+ + 3
1178
+ )
1179
+
1180
+ def next(self):
1181
+ """Classify the current volatility regime from ATR/stdev slope direction."""
1182
+ prev_xatr = float(self._xatr[-1])
1183
+ prev_xstd = float(self._xstd[-1])
1184
+ xatr = float(self._xatr[0])
1185
+ xstd = float(self._xstd[0])
1186
+ res = 0
1187
+ if prev_xatr > xatr and prev_xstd > xstd:
1188
+ res = 1
1189
+ if prev_xatr < xatr and prev_xstd < xstd:
1190
+ res = 2
1191
+ self.lines.state[0] = res + 1
1192
+
1193
+
1194
+ class IinMASignalIndicator(Indicator):
1195
+ """Cross-period MA signal indicator producing buy/sell trigger levels."""
1196
+
1197
+ lines = ("buy_signal", "sell_signal")
1198
+ params = (
1199
+ ("fast_period", 10),
1200
+ ("fast_ma", "EMA"),
1201
+ ("slow_period", 22),
1202
+ ("slow_ma", "SMA"),
1203
+ ("atr_period", 10),
1204
+ )
1205
+
1206
+ def __init__(self):
1207
+ """Initialize fast/slow MAs and internal trend state."""
1208
+ ma_map = {
1209
+ "SMA": SimpleMovingAverage,
1210
+ "EMA": ExponentialMovingAverage,
1211
+ "SMMA": SmoothedMovingAverage,
1212
+ "WMA": WeightedMovingAverage,
1213
+ }
1214
+ fast_cls = ma_map.get(str(self.p.fast_ma).upper(), ExponentialMovingAverage)
1215
+ slow_cls = ma_map.get(str(self.p.slow_ma).upper(), SimpleMovingAverage)
1216
+ self.fast_ma = fast_cls(self.data.close, period=self.p.fast_period)
1217
+ self.slow_ma = slow_cls(self.data.close, period=self.p.slow_period)
1218
+ self._trend = 0
1219
+ self.addminperiod(max(self.p.fast_period, self.p.slow_period) + self.p.atr_period + 3)
1220
+
1221
+ def next(self):
1222
+ """Detect MA transitions and write conditional trigger levels."""
1223
+ buy_signal = 0.0
1224
+ sell_signal = 0.0
1225
+ fast_now = float(self.fast_ma[0])
1226
+ fast_prev = float(self.fast_ma[-1])
1227
+ slow_now = float(self.slow_ma[0])
1228
+ slow_prev = float(self.slow_ma[-1])
1229
+ avg_range = 0.0
1230
+ for idx in range(self.p.atr_period):
1231
+ avg_range += abs(float(self.data.high[-idx]) - float(self.data.low[-idx]))
1232
+ avg_range /= float(self.p.atr_period)
1233
+ if self._trend <= 0 and fast_now > slow_now and fast_prev < slow_prev:
1234
+ buy_signal = float(self.data.low[0]) - avg_range * 0.5
1235
+ self._trend = 1
1236
+ if self._trend >= 0 and fast_now < slow_now and fast_prev > slow_prev:
1237
+ sell_signal = float(self.data.high[0]) + avg_range * 0.5
1238
+ self._trend = -1
1239
+ self.lines.buy_signal[0] = buy_signal
1240
+ self.lines.sell_signal[0] = sell_signal
1241
+
1242
+
1243
+ class KDJ(Indicator):
1244
+ """KDJ (Stochastic) Technical Indicator.
1245
+
1246
+ The KDJ indicator is a momentum oscillator that compares a specific closing
1247
+ price of a security to a range of its prices over a certain period of time.
1248
+ It consists of three lines: K, D, and J, where K and D are similar to the
1249
+ Stochastic oscillator, and J is a derivative line.
1250
+
1251
+ The indicator is calculated using the StochasticFull indicator as the base,
1252
+ with J calculated as: J = 3*K - 2*D.
1253
+
1254
+ Refactoring Note:
1255
+ Uses the next() method instead of line binding (self.l.K = self.kd.percD)
1256
+ because line binding has idx synchronization issues in the current
1257
+ architecture.
1258
+
1259
+ Attributes:
1260
+ lines: Tuple containing ('K', 'D', 'J') - the three output lines.
1261
+ params: Tuple containing configuration parameters:
1262
+ - period (int): Lookback period for Stochastic calculation (default: 9).
1263
+ - period_dfast (int): Fast %D smoothing period (default: 3).
1264
+ - period_dslow (int): Slow %D smoothing period (default: 3).
1265
+ kd (StochasticFull): Internal StochasticFull indicator instance.
1266
+ """
1267
+
1268
+ lines = ("K", "D", "J")
1269
+
1270
+ params = (
1271
+ ("period", 9),
1272
+ ("period_dfast", 3),
1273
+ ("period_dslow", 3),
1274
+ )
1275
+
1276
+ def __init__(self):
1277
+ """Initialize the KDJ indicator with a StochasticFull base.
1278
+
1279
+ Creates a StochasticFull indicator with the configured parameters
1280
+ to serve as the foundation for K, D, and J line calculations.
1281
+ """
1282
+ self.kd = StochasticFull(
1283
+ self.data,
1284
+ period=self.p.period,
1285
+ period_dfast=self.p.period_dfast,
1286
+ period_dslow=self.p.period_dslow,
1287
+ )
1288
+
1289
+ def next(self):
1290
+ """Calculate KDJ values for the current bar.
1291
+
1292
+ Updates the K, D, and J lines based on the underlying StochasticFull
1293
+ indicator values. The J line is derived from K and D using the
1294
+ formula: J = 3*K - 2*D.
1295
+ """
1296
+ self.l.K[0] = self.kd.percD[0]
1297
+ self.l.D[0] = self.kd.percDSlow[0]
1298
+ self.l.J[0] = self.l.K[0] * 3 - self.l.D[0] * 2
1299
+
1300
+
1301
+ class LaguerreIndicator(Indicator):
1302
+ """Laguerre RSI-style oscillator over a four-stage Laguerre filter."""
1303
+
1304
+ lines = ("laguerre",)
1305
+ params = (("gamma", 0.7),)
1306
+
1307
+ def __init__(self):
1308
+ """Set the minimum period and initialize Laguerre filter state."""
1309
+ self.addminperiod(2)
1310
+ self._l0 = None
1311
+ self._l1 = None
1312
+ self._l2 = None
1313
+ self._l3 = None
1314
+
1315
+ def next(self):
1316
+ """Advance the Laguerre filter and emit the oscillator value."""
1317
+ price = float(self.data.close[0])
1318
+ gamma = self.p.gamma
1319
+ if self._l0 is None:
1320
+ self._l0 = price
1321
+ self._l1 = price
1322
+ self._l2 = price
1323
+ self._l3 = price
1324
+
1325
+ l0_prev = self._l0
1326
+ l1_prev = self._l1
1327
+ l2_prev = self._l2
1328
+ l3_prev = self._l3
1329
+
1330
+ l0 = (1.0 - gamma) * price + gamma * l0_prev
1331
+ l1 = -gamma * l0 + l0_prev + gamma * l1_prev
1332
+ l2 = -gamma * l1 + l1_prev + gamma * l2_prev
1333
+ l3 = -gamma * l2 + l2_prev + gamma * l3_prev
1334
+
1335
+ cu = 0.0
1336
+ cd = 0.0
1337
+ if l0 >= l1:
1338
+ cu += l0 - l1
1339
+ else:
1340
+ cd += l1 - l0
1341
+ if l1 >= l2:
1342
+ cu += l1 - l2
1343
+ else:
1344
+ cd += l2 - l1
1345
+ if l2 >= l3:
1346
+ cu += l2 - l3
1347
+ else:
1348
+ cd += l3 - l2
1349
+
1350
+ self.lines.laguerre[0] = cu / (cu + cd) if (cu + cd) else 0.0
1351
+ self._l0 = l0
1352
+ self._l1 = l1
1353
+ self._l2 = l2
1354
+ self._l3 = l3
1355
+
1356
+
1357
+ class LaguerreColorIndicator(Indicator):
1358
+ """Ehlers Laguerre RSI oscillator with high/low colour-state transitions."""
1359
+
1360
+ lines = ("value", "color_state")
1361
+ params = (
1362
+ ("gamma", 0.7),
1363
+ ("high_level", 85),
1364
+ ("middle_level", 50),
1365
+ ("low_level", 15),
1366
+ )
1367
+
1368
+ def __init__(self):
1369
+ """Initialize the Laguerre filter stages and minimum period."""
1370
+ self._l0 = 0.0
1371
+ self._l1 = 0.0
1372
+ self._l2 = 0.0
1373
+ self._l3 = 0.0
1374
+ self._initialized = False
1375
+ self.addminperiod(3)
1376
+
1377
+ def _zone(self, value):
1378
+ if value > float(self.p.high_level):
1379
+ return "high"
1380
+ if value > float(self.p.middle_level):
1381
+ return "high_mid"
1382
+ if value < float(self.p.low_level):
1383
+ return "low"
1384
+ return "low_mid"
1385
+
1386
+ def _color_from_state(self, curr_zone, prev_zone, prev_color):
1387
+ if curr_zone == "high":
1388
+ return 1.0
1389
+ if curr_zone == "high_mid":
1390
+ if prev_zone == "high":
1391
+ return 2.0
1392
+ if prev_zone == "high_mid":
1393
+ return prev_color
1394
+ return 1.0
1395
+ if curr_zone == "low_mid":
1396
+ if prev_zone in ("high", "high_mid"):
1397
+ return 2.0
1398
+ if prev_zone == "low_mid":
1399
+ return prev_color
1400
+ return 1.0
1401
+ if curr_zone == "low":
1402
+ return 2.0
1403
+ return prev_color
1404
+
1405
+ def next(self):
1406
+ """Advance the Laguerre filter and update the value/colour lines."""
1407
+ price = float(self.data.close[0])
1408
+ gamma = float(self.p.gamma)
1409
+ prev_l0, prev_l1, prev_l2, prev_l3 = self._l0, self._l1, self._l2, self._l3
1410
+
1411
+ if not self._initialized:
1412
+ self._l0 = price
1413
+ self._l1 = price
1414
+ self._l2 = price
1415
+ self._l3 = price
1416
+ self._initialized = True
1417
+ else:
1418
+ self._l0 = (1.0 - gamma) * price + gamma * prev_l0
1419
+ self._l1 = -gamma * self._l0 + prev_l0 + gamma * prev_l1
1420
+ self._l2 = -gamma * self._l1 + prev_l1 + gamma * prev_l2
1421
+ self._l3 = -gamma * self._l2 + prev_l2 + gamma * prev_l3
1422
+
1423
+ cu = 0.0
1424
+ cd = 0.0
1425
+ pairs = ((self._l0, self._l1), (self._l1, self._l2), (self._l2, self._l3))
1426
+ for a, b in pairs:
1427
+ if a >= b:
1428
+ cu += a - b
1429
+ else:
1430
+ cd += b - a
1431
+ value = 0.0
1432
+ if (cu + cd) > 1e-12:
1433
+ value = 100.0 * cu / (cu + cd)
1434
+
1435
+ prev_value = float(self.lines.value[-1]) if len(self) > 1 else value
1436
+ prev_color = float(self.lines.color_state[-1]) if len(self) > 1 else 1.0
1437
+ curr_zone = self._zone(value)
1438
+ prev_zone = self._zone(prev_value)
1439
+ color = self._color_from_state(curr_zone, prev_zone, prev_color)
1440
+
1441
+ self.lines.value[0] = value
1442
+ self.lines.color_state[0] = color
1443
+
1444
+
1445
+ class RelativeVigorIndex(Indicator):
1446
+ """Relative Vigor Index (RVI) with its 4-point symmetric signal line."""
1447
+
1448
+ lines = ("rvi", "signal")
1449
+ params = (("period", 44),)
1450
+
1451
+ def __init__(self):
1452
+ """Reserve enough warm-up bars for the period plus the 4-bar weighting."""
1453
+ self.addminperiod(self.p.period + 6)
1454
+
1455
+ def next(self):
1456
+ """Compute the RVI ratio and its weighted signal value for the current bar."""
1457
+ numerator_sum = 0.0
1458
+ denominator_sum = 0.0
1459
+ for shift in range(self.p.period):
1460
+ close0 = float(self.data.close[-shift])
1461
+ open0 = float(self.data.open[-shift])
1462
+ close1 = float(self.data.close[-shift - 1])
1463
+ open1 = float(self.data.open[-shift - 1])
1464
+ close2 = float(self.data.close[-shift - 2])
1465
+ open2 = float(self.data.open[-shift - 2])
1466
+ close3 = float(self.data.close[-shift - 3])
1467
+ open3 = float(self.data.open[-shift - 3])
1468
+ high0 = float(self.data.high[-shift])
1469
+ low0 = float(self.data.low[-shift])
1470
+ high1 = float(self.data.high[-shift - 1])
1471
+ low1 = float(self.data.low[-shift - 1])
1472
+ high2 = float(self.data.high[-shift - 2])
1473
+ low2 = float(self.data.low[-shift - 2])
1474
+ high3 = float(self.data.high[-shift - 3])
1475
+ low3 = float(self.data.low[-shift - 3])
1476
+ numerator_sum += (
1477
+ (close0 - open0)
1478
+ + 2.0 * (close1 - open1)
1479
+ + 2.0 * (close2 - open2)
1480
+ + (close3 - open3)
1481
+ ) / 6.0
1482
+ denominator_sum += (
1483
+ (high0 - low0) + 2.0 * (high1 - low1) + 2.0 * (high2 - low2) + (high3 - low3)
1484
+ ) / 6.0
1485
+ rvi_value = numerator_sum / denominator_sum if denominator_sum else 0.0
1486
+ self.lines.rvi[0] = rvi_value
1487
+ if len(self) >= 4:
1488
+ values = [
1489
+ float(self.lines.rvi[0]),
1490
+ float(self.lines.rvi[-1]),
1491
+ float(self.lines.rvi[-2]),
1492
+ float(self.lines.rvi[-3]),
1493
+ ]
1494
+ if all(math.isfinite(value) for value in values):
1495
+ self.lines.signal[0] = (
1496
+ values[0] + 2.0 * values[1] + 2.0 * values[2] + values[3]
1497
+ ) / 6.0
1498
+ else:
1499
+ self.lines.signal[0] = rvi_value
1500
+ else:
1501
+ self.lines.signal[0] = rvi_value
1502
+
1503
+
1504
+ class SmoothedRelativeVigorIndex(Indicator):
1505
+ """Relative Vigor Index indicator with smoothed signal line."""
1506
+
1507
+ lines = ("rvi", "signal")
1508
+ params = (("period", 13),)
1509
+
1510
+ def __init__(self):
1511
+ """Compute weighted numerator/denominator and moving-average filtered lines."""
1512
+ weighted_num = (
1513
+ (self.data.close - self.data.open)
1514
+ + 2.0 * (self.data.close(-1) - self.data.open(-1))
1515
+ + 2.0 * (self.data.close(-2) - self.data.open(-2))
1516
+ + (self.data.close(-3) - self.data.open(-3))
1517
+ ) / 6.0
1518
+ weighted_den = (
1519
+ (self.data.high - self.data.low)
1520
+ + 2.0 * (self.data.high(-1) - self.data.low(-1))
1521
+ + 2.0 * (self.data.high(-2) - self.data.low(-2))
1522
+ + (self.data.high(-3) - self.data.low(-3))
1523
+ ) / 6.0
1524
+ num_ma = SimpleMovingAverage(weighted_num, period=self.p.period)
1525
+ den_ma = SimpleMovingAverage(weighted_den, period=self.p.period)
1526
+ self.lines.rvi = If(den_ma != 0, num_ma / den_ma, 0.0)
1527
+ self.lines.signal = (
1528
+ self.lines.rvi
1529
+ + 2.0 * self.lines.rvi(-1)
1530
+ + 2.0 * self.lines.rvi(-2)
1531
+ + self.lines.rvi(-3)
1532
+ ) / 6.0
1533
+
1534
+
1535
+ class SafeCCI(Indicator):
1536
+ """Safe Commodity Channel Index indicator with guarded zero-variance handling."""
1537
+
1538
+ lines = ("cci",)
1539
+ params = (("period", 14),)
1540
+
1541
+ def __init__(self):
1542
+ """Initialize CCI period warm-up requirement."""
1543
+ self.addminperiod(self.p.period + 3)
1544
+
1545
+ def next(self):
1546
+ """Compute CCI value for the current bar with mean deviation protection."""
1547
+ typical_prices = []
1548
+ for idx in range(self.p.period):
1549
+ typical_prices.append(
1550
+ (
1551
+ float(self.data.high[-idx])
1552
+ + float(self.data.low[-idx])
1553
+ + float(self.data.close[-idx])
1554
+ )
1555
+ / 3.0
1556
+ )
1557
+ tp_now = typical_prices[0]
1558
+ tp_sma = sum(typical_prices) / float(len(typical_prices))
1559
+ mean_dev = sum(abs(tp - tp_sma) for tp in typical_prices) / float(len(typical_prices))
1560
+ if mean_dev <= 1e-12:
1561
+ self.lines.cci[0] = 0.0
1562
+ return
1563
+ self.lines.cci[0] = (tp_now - tp_sma) / (0.015 * mean_dev)
1564
+
1565
+
1566
+ class SafeCCIWithFactor(Indicator):
1567
+ """CCI indicator with mean deviation, returning 0.0 when denominator is zero."""
1568
+
1569
+ lines = ("cci",)
1570
+ params = (
1571
+ ("period", 27),
1572
+ ("factor", 0.015),
1573
+ )
1574
+
1575
+ def __init__(self):
1576
+ """Initialise SafeCCI and set minimum period to `period`."""
1577
+ self.addminperiod(self.p.period)
1578
+
1579
+ def next(self):
1580
+ """Compute CCI from rolling typical-price SMA and mean deviation."""
1581
+ period = self.p.period
1582
+ typical_prices = [
1583
+ (float(self.data.high[-i]) + float(self.data.low[-i]) + float(self.data.close[-i]))
1584
+ / 3.0
1585
+ for i in range(period)
1586
+ ]
1587
+ sma = sum(typical_prices) / period
1588
+ mean_dev = sum(abs(tp - sma) for tp in typical_prices) / period
1589
+ current_tp = typical_prices[0]
1590
+ denominator = self.p.factor * mean_dev
1591
+ self.lines.cci[0] = 0.0 if denominator == 0.0 else (current_tp - sma) / denominator
1592
+
1593
+
1594
+ class SilverTrendSignalProxy(Indicator):
1595
+ """SilverTrend buy/sell signal proxy based on a moving-average crossover.
1596
+
1597
+ Emits a non-zero ``buy`` (or ``sell``) value when price crosses above (or
1598
+ below) a risk-scaled simple moving average, mirroring the EA's signal lines.
1599
+ """
1600
+
1601
+ lines = ("buy", "sell")
1602
+ params = (("risk", 3),)
1603
+
1604
+ def __init__(self):
1605
+ """Build the risk-scaled moving average and set the minimum period."""
1606
+ self.period = max(3, int(self.p.risk) * 2 + 1)
1607
+ self.ma = SimpleMovingAverage(self.data.close, period=self.period)
1608
+ self.addminperiod(self.period + 3)
1609
+
1610
+ def next(self):
1611
+ """Set buy/sell signal lines from the price/MA crossover this bar."""
1612
+ buy = 0.0
1613
+ sell = 0.0
1614
+ close0 = float(self.data.close[0])
1615
+ close1 = float(self.data.close[-1])
1616
+ ma0 = float(self.ma[0])
1617
+ ma1 = float(self.ma[-1])
1618
+ if close1 <= ma1 and close0 > ma0:
1619
+ buy = close0
1620
+ elif close1 >= ma1 and close0 < ma0:
1621
+ sell = close0
1622
+ self.lines.buy[0] = buy
1623
+ self.lines.sell[0] = sell
1624
+
1625
+
1626
+ class SilverTrendDirectionSignalProxy(Indicator):
1627
+ """Proxy indicator emitting +1/-1 on SMA crossover direction flips."""
1628
+
1629
+ lines = ("signal",)
1630
+ params = (("risk", 3),)
1631
+
1632
+ def __init__(self):
1633
+ """Set up the SMA and minimum period from the risk parameter."""
1634
+ self.period = max(3, int(self.p.risk) * 2 + 1)
1635
+ self.ma = SimpleMovingAverage(self.data.close, period=self.period)
1636
+ self.addminperiod(self.period + 2)
1637
+
1638
+ def next(self):
1639
+ """Carry the prior signal forward, flipping it on a fresh MA crossover."""
1640
+ signal = float(self.lines.signal[-1]) if len(self) > 0 else 0.0
1641
+ if not math.isfinite(signal):
1642
+ signal = 0.0
1643
+ close_prev = float(self.data.close[-1])
1644
+ close_now = float(self.data.close[0])
1645
+ ma_prev = float(self.ma[-1])
1646
+ ma_now = float(self.ma[0])
1647
+ if close_prev <= ma_prev and close_now > ma_now:
1648
+ signal = 1.0
1649
+ elif close_prev >= ma_prev and close_now < ma_now:
1650
+ signal = -1.0
1651
+ self.lines.signal[0] = signal