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.
- back_trader_python-1.4.0.dist-info/METADATA +1491 -0
- back_trader_python-1.4.0.dist-info/RECORD +465 -0
- back_trader_python-1.4.0.dist-info/WHEEL +5 -0
- back_trader_python-1.4.0.dist-info/licenses/LICENSE +674 -0
- back_trader_python-1.4.0.dist-info/top_level.txt +1 -0
- backtrader/__init__.py +148 -0
- backtrader/_cerebro/__init__.py +5 -0
- backtrader/_cerebro/channel.py +382 -0
- backtrader/_cerebro/execution.py +377 -0
- backtrader/_cerebro/lifecycle.py +143 -0
- backtrader/_cerebro/notifications.py +150 -0
- backtrader/_cerebro/presentation.py +230 -0
- backtrader/_cerebro/registry.py +593 -0
- backtrader/_cerebro/runnext.py +551 -0
- backtrader/_cerebro/runonce.py +142 -0
- backtrader/analyzer.py +594 -0
- backtrader/analyzers/__init__.py +50 -0
- backtrader/analyzers/annualreturn.py +226 -0
- backtrader/analyzers/calmar.py +165 -0
- backtrader/analyzers/drawdown.py +287 -0
- backtrader/analyzers/leverage.py +112 -0
- backtrader/analyzers/logreturnsrolling.py +190 -0
- backtrader/analyzers/periodstats.py +153 -0
- backtrader/analyzers/positions.py +119 -0
- backtrader/analyzers/pyfolio.py +470 -0
- backtrader/analyzers/returns.py +192 -0
- backtrader/analyzers/sharpe.py +307 -0
- backtrader/analyzers/sharpe_ratio_stats.py +534 -0
- backtrader/analyzers/sqn.py +112 -0
- backtrader/analyzers/timereturn.py +192 -0
- backtrader/analyzers/total_value.py +75 -0
- backtrader/analyzers/tradeanalyzer.py +278 -0
- backtrader/analyzers/transactions.py +141 -0
- backtrader/analyzers/vwr.py +245 -0
- backtrader/bokeh/__init__.py +155 -0
- backtrader/bokeh/analyzers/__init__.py +13 -0
- backtrader/bokeh/analyzers/plot.py +192 -0
- backtrader/bokeh/analyzers/recorder.py +181 -0
- backtrader/bokeh/app.py +1094 -0
- backtrader/bokeh/live/__init__.py +11 -0
- backtrader/bokeh/live/client.py +352 -0
- backtrader/bokeh/live/datahandler.py +346 -0
- backtrader/bokeh/plot_adapter.py +200 -0
- backtrader/bokeh/schemes/__init__.py +14 -0
- backtrader/bokeh/schemes/blackly.py +76 -0
- backtrader/bokeh/schemes/scheme.py +150 -0
- backtrader/bokeh/schemes/tradimo.py +82 -0
- backtrader/bokeh/tab.py +125 -0
- backtrader/bokeh/tabs/__init__.py +30 -0
- backtrader/bokeh/tabs/analyzer.py +120 -0
- backtrader/bokeh/tabs/config.py +154 -0
- backtrader/bokeh/tabs/live.py +109 -0
- backtrader/bokeh/tabs/log.py +185 -0
- backtrader/bokeh/tabs/metadata.py +182 -0
- backtrader/bokeh/tabs/performance.py +359 -0
- backtrader/bokeh/tabs/source.py +70 -0
- backtrader/bokeh/utils/__init__.py +8 -0
- backtrader/bokeh/utils/helpers.py +167 -0
- backtrader/bokeh/webapp.py +164 -0
- backtrader/broker.py +478 -0
- backtrader/brokers/__init__.py +36 -0
- backtrader/brokers/bbroker.py +2576 -0
- backtrader/brokers/btapibroker.py +8227 -0
- backtrader/brokers/hft/__init__.py +89 -0
- backtrader/brokers/hft/binance_bbo.py +625 -0
- backtrader/brokers/hft/binance_bbo_compare.py +1398 -0
- backtrader/brokers/hft/examples.py +1228 -0
- backtrader/brokers/hft/exchange.py +380 -0
- backtrader/brokers/hft/latency.py +309 -0
- backtrader/brokers/hft/matching_core.py +572 -0
- backtrader/brokers/hft/queue.py +238 -0
- backtrader/brokers/hft/recorder.py +88 -0
- backtrader/brokers/hft/state.py +138 -0
- backtrader/brokers/impact_models.py +118 -0
- backtrader/brokers/mixbroker.py +895 -0
- backtrader/brokers/tickbroker.py +1991 -0
- backtrader/btrun/__init__.py +12 -0
- backtrader/btrun/btrun.py +1218 -0
- backtrader/cerebro.py +828 -0
- backtrader/channel.py +682 -0
- backtrader/channels/__init__.py +23 -0
- backtrader/channels/bridge.py +186 -0
- backtrader/channels/funding.py +248 -0
- backtrader/channels/live_queue.py +216 -0
- backtrader/channels/live_validator.py +294 -0
- backtrader/channels/orderbook.py +257 -0
- backtrader/channels/tick.py +202 -0
- backtrader/comminfo.py +665 -0
- backtrader/commissions/__init__.py +106 -0
- backtrader/commissions/ctpoption.py +993 -0
- backtrader/configs/account_config_example.yaml +8 -0
- backtrader/dataseries.py +379 -0
- backtrader/errors.py +106 -0
- backtrader/events.py +980 -0
- backtrader/feed.py +1523 -0
- backtrader/feeds/__init__.py +75 -0
- backtrader/feeds/barrier.py +2006 -0
- backtrader/feeds/blaze.py +118 -0
- backtrader/feeds/btapifeed.py +1538 -0
- backtrader/feeds/btcsv.py +203 -0
- backtrader/feeds/chainer.py +114 -0
- backtrader/feeds/cryptohftdata.py +164 -0
- backtrader/feeds/csvgeneric.py +1205 -0
- backtrader/feeds/ctpcohort.py +1051 -0
- backtrader/feeds/influxfeed.py +158 -0
- backtrader/feeds/livefeed.py +71 -0
- backtrader/feeds/mixed_channel.py +108 -0
- backtrader/feeds/mt4csv.py +42 -0
- backtrader/feeds/pandafeed.py +381 -0
- backtrader/feeds/quandl.py +256 -0
- backtrader/feeds/rollover.py +229 -0
- backtrader/feeds/sierrachart.py +30 -0
- backtrader/feeds/vchart.py +162 -0
- backtrader/feeds/vchartcsv.py +84 -0
- backtrader/feeds/vchartfile.py +153 -0
- backtrader/feeds/yahoo.py +399 -0
- backtrader/fillers.py +148 -0
- backtrader/filters/__init__.py +34 -0
- backtrader/filters/bsplitter.py +127 -0
- backtrader/filters/calendardays.py +121 -0
- backtrader/filters/datafiller.py +192 -0
- backtrader/filters/datafilter.py +74 -0
- backtrader/filters/daysteps.py +96 -0
- backtrader/filters/heikinashi.py +63 -0
- backtrader/filters/renko.py +164 -0
- backtrader/filters/session.py +289 -0
- backtrader/flt.py +80 -0
- backtrader/functions.py +960 -0
- backtrader/indicator.py +449 -0
- backtrader/indicators/__init__.py +148 -0
- backtrader/indicators/accdecoscillator.py +110 -0
- backtrader/indicators/aroon.py +300 -0
- backtrader/indicators/atr.py +315 -0
- backtrader/indicators/awesomeoscillator.py +122 -0
- backtrader/indicators/basicops.py +834 -0
- backtrader/indicators/bollinger.py +223 -0
- backtrader/indicators/cci.py +89 -0
- backtrader/indicators/channels_ext.py +83 -0
- backtrader/indicators/contrib/__init__.py +228 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma.py +28 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma_color.py +44 -0
- backtrader/indicators/contrib/accumulation_distribution_line.py +92 -0
- backtrader/indicators/contrib/adx_cross_hull_style_indicator.py +249 -0
- backtrader/indicators/contrib/adxdmi.py +34 -0
- backtrader/indicators/contrib/ai_acceleration_deceleration_oscillator.py +34 -0
- backtrader/indicators/contrib/altr_trend_signal_v22.py +85 -0
- backtrader/indicators/contrib/anchored_momentum_line.py +115 -0
- backtrader/indicators/contrib/any_range_cld_tail_indicator.py +82 -0
- backtrader/indicators/contrib/aroon_horn_sign_indicator.py +96 -0
- backtrader/indicators/contrib/aroon_oscillator_sign_alert.py +50 -0
- backtrader/indicators/contrib/arrows_curves_indicator.py +112 -0
- backtrader/indicators/contrib/as_ctrend_indicator.py +143 -0
- backtrader/indicators/contrib/asimmetric_stoch_nr_indicator.py +187 -0
- backtrader/indicators/contrib/atr_normalize_histogram.py +118 -0
- backtrader/indicators/contrib/average_change_candle.py +165 -0
- backtrader/indicators/contrib/bb_squeeze_indicator.py +60 -0
- backtrader/indicators/contrib/bezier_st_dev_indicator.py +135 -0
- backtrader/indicators/contrib/binary_wave_indicator.py +233 -0
- backtrader/indicators/contrib/blau_c_momentum_indicator.py +123 -0
- backtrader/indicators/contrib/blau_cmi_indicator.py +141 -0
- backtrader/indicators/contrib/blau_csi.py +76 -0
- backtrader/indicators/contrib/blau_ergodic.py +53 -0
- backtrader/indicators/contrib/blau_t_stoch_i.py +72 -0
- backtrader/indicators/contrib/blau_ts_stochastic.py +85 -0
- backtrader/indicators/contrib/blau_tvi.py +55 -0
- backtrader/indicators/contrib/brain_trend2_indicator.py +128 -0
- backtrader/indicators/contrib/brain_trend_signal_proxy.py +47 -0
- backtrader/indicators/contrib/brake_parb_indicator.py +85 -0
- backtrader/indicators/contrib/breakout_bars_trend_v2.py +121 -0
- backtrader/indicators/contrib/bsi_indicator.py +87 -0
- backtrader/indicators/contrib/bulls_bears_eyes.py +67 -0
- backtrader/indicators/contrib/bulls_power.py +56 -0
- backtrader/indicators/contrib/bw_wise_man1_signal.py +102 -0
- backtrader/indicators/contrib/bykov_trend_indicator.py +85 -0
- backtrader/indicators/contrib/candle_stop_color.py +46 -0
- backtrader/indicators/contrib/candles_x_smoothed_indicator.py +69 -0
- backtrader/indicators/contrib/candlesticks_bw.py +45 -0
- backtrader/indicators/contrib/caudate_x_period_candle_color.py +56 -0
- backtrader/indicators/contrib/cci_histogram_indicator.py +53 -0
- backtrader/indicators/contrib/cci_woodies_indicator.py +80 -0
- backtrader/indicators/contrib/center_of_gravity_candle_indicator.py +83 -0
- backtrader/indicators/contrib/center_of_gravity_indicator.py +70 -0
- backtrader/indicators/contrib/cg_oscillator.py +40 -0
- backtrader/indicators/contrib/close_line_cci.py +38 -0
- backtrader/indicators/contrib/close_price_fractals.py +47 -0
- backtrader/indicators/contrib/color3rd_gen_xma_indicator.py +122 -0
- backtrader/indicators/contrib/color_bb_candles_indicator.py +108 -0
- backtrader/indicators/contrib/color_coppock_indicator.py +157 -0
- backtrader/indicators/contrib/color_hma.py +71 -0
- backtrader/indicators/contrib/color_j_variation_indicator.py +53 -0
- backtrader/indicators/contrib/color_metro_de_marker_indicator.py +78 -0
- backtrader/indicators/contrib/color_metro_stochastic_indicator.py +93 -0
- backtrader/indicators/contrib/color_metro_wpr_indicator.py +85 -0
- backtrader/indicators/contrib/color_schaff_de_marker_trend_cycle.py +92 -0
- backtrader/indicators/contrib/color_schaff_trend_cycle_indicator.py +203 -0
- backtrader/indicators/contrib/color_step_xccx_indicator.py +193 -0
- backtrader/indicators/contrib/color_x2_ma.py +49 -0
- backtrader/indicators/contrib/color_x_derivative.py +63 -0
- backtrader/indicators/contrib/color_zerolag_de_marker.py +84 -0
- backtrader/indicators/contrib/corrected_average_indicator.py +127 -0
- backtrader/indicators/contrib/darvas_boxes_system.py +73 -0
- backtrader/indicators/contrib/dema_range_channel_color.py +42 -0
- backtrader/indicators/contrib/derivative_indicator.py +95 -0
- backtrader/indicators/contrib/digital_ft01_indicator.py +112 -0
- backtrader/indicators/contrib/digital_macd.py +200 -0
- backtrader/indicators/contrib/donchian_channels_system.py +45 -0
- backtrader/indicators/contrib/dots_indicator.py +93 -0
- backtrader/indicators/contrib/ef_distance_indicator.py +82 -0
- backtrader/indicators/contrib/ema_rsi_va.py +80 -0
- backtrader/indicators/contrib/envelopes_jp_alonso.py +32 -0
- backtrader/indicators/contrib/f2a_ao_indicator.py +120 -0
- backtrader/indicators/contrib/fatl_filter.py +179 -0
- backtrader/indicators/contrib/fibo_candles_indicator.py +78 -0
- backtrader/indicators/contrib/fine_tuning_ma.py +100 -0
- backtrader/indicators/contrib/fisher_org_v1.py +102 -0
- backtrader/indicators/contrib/fisher_org_v1_sign.py +118 -0
- backtrader/indicators/contrib/force_index_ema.py +96 -0
- backtrader/indicators/contrib/force_index_ema_2.py +27 -0
- backtrader/indicators/contrib/forecast_oscilator.py +145 -0
- backtrader/indicators/contrib/fractal_amambk.py +81 -0
- backtrader/indicators/contrib/frama_series.py +84 -0
- backtrader/indicators/contrib/frasm_av2_indicator.py +104 -0
- backtrader/indicators/contrib/go_indicator.py +93 -0
- backtrader/indicators/contrib/hlr_indicator.py +95 -0
- backtrader/indicators/contrib/hma.py +50 -0
- backtrader/indicators/contrib/i4_drfv2.py +34 -0
- backtrader/indicators/contrib/i4_drfv3.py +38 -0
- backtrader/indicators/contrib/i_anch_mom_indicator.py +72 -0
- backtrader/indicators/contrib/i_de_marker_sign_indicator.py +64 -0
- backtrader/indicators/contrib/i_gap_indicator.py +45 -0
- backtrader/indicators/contrib/i_stoch_komposter_indicator.py +77 -0
- backtrader/indicators/contrib/i_trend_indicator.py +125 -0
- backtrader/indicators/contrib/iamma_indicator.py +39 -0
- backtrader/indicators/contrib/indexed_moving_average.py +33 -0
- backtrader/indicators/contrib/instantaneous_trend_filter_indicator.py +51 -0
- backtrader/indicators/contrib/inverse_reaction_indicator.py +41 -0
- backtrader/indicators/contrib/irsi_sign_indicator.py +95 -0
- backtrader/indicators/contrib/iwpr_sign_indicator.py +59 -0
- backtrader/indicators/contrib/j_brain_trend1_sig_indicator.py +233 -0
- backtrader/indicators/contrib/j_tpo_proxy.py +32 -0
- backtrader/indicators/contrib/jma_slope_indicator.py +73 -0
- backtrader/indicators/contrib/kalman_filter_indicator.py +119 -0
- backtrader/indicators/contrib/kalman_filter_line.py +127 -0
- backtrader/indicators/contrib/kama_indicator.py +150 -0
- backtrader/indicators/contrib/karacatica_indicator.py +99 -0
- backtrader/indicators/contrib/kdj_indicator.py +59 -0
- backtrader/indicators/contrib/kwan_ccc_indicator.py +195 -0
- backtrader/indicators/contrib/kwan_nrp_indicator.py +113 -0
- backtrader/indicators/contrib/kwan_rdp_indicator.py +192 -0
- backtrader/indicators/contrib/laguerre_adx_indicator.py +85 -0
- backtrader/indicators/contrib/laguerre_filter_indicator.py +66 -0
- backtrader/indicators/contrib/laguerre_plus_di_proxy.py +57 -0
- backtrader/indicators/contrib/laguerre_roc_indicator.py +81 -0
- backtrader/indicators/contrib/le_man_signal_indicator.py +63 -0
- backtrader/indicators/contrib/linear_reg_slope_v2_indicator.py +136 -0
- backtrader/indicators/contrib/loco_indicator.py +88 -0
- backtrader/indicators/contrib/lrma_indicator.py +185 -0
- backtrader/indicators/contrib/lsma_angle_indicator.py +106 -0
- backtrader/indicators/contrib/ma_rounding_channel_indicator.py +149 -0
- backtrader/indicators/contrib/macd2_indicator.py +61 -0
- backtrader/indicators/contrib/macd_candle_indicator.py +80 -0
- backtrader/indicators/contrib/malr_indicator.py +77 -0
- backtrader/indicators/contrib/momentum_candle_sign_indicator.py +51 -0
- backtrader/indicators/contrib/moving_average_fn_indicator.py +139 -0
- backtrader/indicators/contrib/mt5_stochastic_close_close.py +57 -0
- backtrader/indicators/contrib/muv_nor_diff_cloud_indicator.py +107 -0
- backtrader/indicators/contrib/non_lag_dot_indicator.py +124 -0
- backtrader/indicators/contrib/nrtr_extr_indicator.py +95 -0
- backtrader/indicators/contrib/nrtr_indicator.py +95 -0
- backtrader/indicators/contrib/p_channel_system.py +40 -0
- backtrader/indicators/contrib/percent_envelope.py +37 -0
- backtrader/indicators/contrib/percentage_crossover_channel.py +47 -0
- backtrader/indicators/contrib/pivot_zig_zag_proxy.py +47 -0
- backtrader/indicators/contrib/price_channel_stop_indicator.py +104 -0
- backtrader/indicators/contrib/price_extreme_channel.py +35 -0
- backtrader/indicators/contrib/qqe_cloud_indicator.py +129 -0
- backtrader/indicators/contrib/ravi_indicator.py +40 -0
- backtrader/indicators/contrib/raw_close_close_stochastic.py +74 -0
- backtrader/indicators/contrib/rd_trend_trigger_indicator.py +51 -0
- backtrader/indicators/contrib/renko_level.py +85 -0
- backtrader/indicators/contrib/renko_line_break.py +91 -0
- backtrader/indicators/contrib/rftl_indicator.py +41 -0
- backtrader/indicators/contrib/rkd_indicator.py +53 -0
- backtrader/indicators/contrib/roc2_vg_indicator.py +68 -0
- backtrader/indicators/contrib/rsi_histogram_indicator.py +43 -0
- backtrader/indicators/contrib/rsi_slowdown.py +57 -0
- backtrader/indicators/contrib/rsioma_v2.py +41 -0
- backtrader/indicators/contrib/rvi_histogram_indicator.py +107 -0
- backtrader/indicators/contrib/safe_adx.py +89 -0
- backtrader/indicators/contrib/shared_strategy_indicators.py +1651 -0
- backtrader/indicators/contrib/sidus_indicator.py +105 -0
- backtrader/indicators/contrib/silver_trend_indicator.py +79 -0
- backtrader/indicators/contrib/sliding_range_color.py +56 -0
- backtrader/indicators/contrib/slow_stoch.py +42 -0
- backtrader/indicators/contrib/smoothed_adx_indicator.py +86 -0
- backtrader/indicators/contrib/smoothed_rsi.py +31 -0
- backtrader/indicators/contrib/spearman_rank_correlation_histogram.py +60 -0
- backtrader/indicators/contrib/stalin_indicator.py +152 -0
- backtrader/indicators/contrib/starter_laguerre_filter.py +62 -0
- backtrader/indicators/contrib/step_manrtr_indicator.py +137 -0
- backtrader/indicators/contrib/stochastic_histogram_indicator.py +143 -0
- backtrader/indicators/contrib/t3_alarm_indicator.py +125 -0
- backtrader/indicators/contrib/t3_average.py +76 -0
- backtrader/indicators/contrib/t3_indicator.py +40 -0
- backtrader/indicators/contrib/the20s_v020_signal.py +93 -0
- backtrader/indicators/contrib/three_candles_indicator.py +70 -0
- backtrader/indicators/contrib/three_line_break_indicator.py +64 -0
- backtrader/indicators/contrib/time_line.py +57 -0
- backtrader/indicators/contrib/trading_channel_index_proxy.py +48 -0
- backtrader/indicators/contrib/trend_arrows_indicator.py +109 -0
- backtrader/indicators/contrib/trend_continuation_indicator.py +127 -0
- backtrader/indicators/contrib/trend_intensity_index_proxy.py +51 -0
- backtrader/indicators/contrib/trend_manager_indicator.py +39 -0
- backtrader/indicators/contrib/tri_x_candle_indicator.py +51 -0
- backtrader/indicators/contrib/trigger_line.py +66 -0
- backtrader/indicators/contrib/triple_ema_rate.py +34 -0
- backtrader/indicators/contrib/trvi_indicator.py +194 -0
- backtrader/indicators/contrib/two_pb_ideal_xosma_indicator.py +127 -0
- backtrader/indicators/contrib/ultra_absolutely_no_lag_lwma_color.py +92 -0
- backtrader/indicators/contrib/ultra_wpr_indicator.py +173 -0
- backtrader/indicators/contrib/up_down_candle_strength.py +68 -0
- backtrader/indicators/contrib/vinin_i_trend_indicator.py +139 -0
- backtrader/indicators/contrib/volume_weighted_ma_indicator.py +78 -0
- backtrader/indicators/contrib/volume_weighted_ma_st_dev_indicator.py +111 -0
- backtrader/indicators/contrib/vwap_close_indicator.py +65 -0
- backtrader/indicators/contrib/vwma_candle.py +57 -0
- backtrader/indicators/contrib/vwma_digit_system.py +70 -0
- backtrader/indicators/contrib/wami.py +43 -0
- backtrader/indicators/contrib/wprsi_signal_indicator.py +105 -0
- backtrader/indicators/contrib/x_de_marker_histogram_vol_direct_indicator.py +145 -0
- backtrader/indicators/contrib/x_fisher_indicator.py +64 -0
- backtrader/indicators/contrib/xcci_histogram_vol_direct_indicator.py +56 -0
- backtrader/indicators/contrib/xcci_histogram_vol_indicator.py +85 -0
- backtrader/indicators/contrib/xma_ichimoku.py +163 -0
- backtrader/indicators/contrib/xma_ishimoku_channel_indicator.py +65 -0
- backtrader/indicators/contrib/xma_ishimoku_line.py +68 -0
- backtrader/indicators/contrib/xma_range_bands_indicator.py +107 -0
- backtrader/indicators/contrib/xmacd_indicator.py +70 -0
- backtrader/indicators/contrib/xrsi_de_marker_histogram.py +67 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_direct_indicator.py +52 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_indicator.py +81 -0
- backtrader/indicators/contrib/xrvi_indicator.py +130 -0
- backtrader/indicators/contrib/zero_lag_macd.py +36 -0
- backtrader/indicators/contrib/zig_zag_recent_pivot_signal.py +90 -0
- backtrader/indicators/contrib/zpf_indicator.py +115 -0
- backtrader/indicators/crossover.py +337 -0
- backtrader/indicators/dema.py +175 -0
- backtrader/indicators/demarker.py +270 -0
- backtrader/indicators/deviation.py +284 -0
- backtrader/indicators/directionalmove.py +1071 -0
- backtrader/indicators/dma.py +112 -0
- backtrader/indicators/dpo.py +96 -0
- backtrader/indicators/dv2.py +56 -0
- backtrader/indicators/ema.py +145 -0
- backtrader/indicators/envelope.py +475 -0
- backtrader/indicators/hadelta.py +198 -0
- backtrader/indicators/heikinashi.py +153 -0
- backtrader/indicators/hma.py +153 -0
- backtrader/indicators/hurst.py +151 -0
- backtrader/indicators/ichimoku.py +267 -0
- backtrader/indicators/kama.py +181 -0
- backtrader/indicators/kst.py +159 -0
- backtrader/indicators/lrsi.py +125 -0
- backtrader/indicators/mabase.py +147 -0
- backtrader/indicators/macd.py +322 -0
- backtrader/indicators/momentum.py +267 -0
- backtrader/indicators/moneyflow.py +237 -0
- backtrader/indicators/mt5atr.py +124 -0
- backtrader/indicators/myind.py +179 -0
- backtrader/indicators/obv.py +94 -0
- backtrader/indicators/ols.py +265 -0
- backtrader/indicators/oscillator.py +161 -0
- backtrader/indicators/percentchange.py +83 -0
- backtrader/indicators/percentrank.py +46 -0
- backtrader/indicators/pivotpoint.py +469 -0
- backtrader/indicators/prettygoodoscillator.py +113 -0
- backtrader/indicators/priceops_ext.py +123 -0
- backtrader/indicators/priceoscillator.py +262 -0
- backtrader/indicators/psar.py +212 -0
- backtrader/indicators/rmi.py +69 -0
- backtrader/indicators/rsi.py +440 -0
- backtrader/indicators/sma.py +141 -0
- backtrader/indicators/smma.py +116 -0
- backtrader/indicators/spread.py +54 -0
- backtrader/indicators/stochastic.py +263 -0
- backtrader/indicators/supertrend.py +436 -0
- backtrader/indicators/trend_ext.py +105 -0
- backtrader/indicators/trix.py +202 -0
- backtrader/indicators/tsi.py +155 -0
- backtrader/indicators/ultimateoscillator.py +158 -0
- backtrader/indicators/vortex.py +62 -0
- backtrader/indicators/williams.py +194 -0
- backtrader/indicators/wma.py +103 -0
- backtrader/indicators/zlema.py +135 -0
- backtrader/indicators/zlind.py +104 -0
- backtrader/linebuffer.py +3155 -0
- backtrader/lineiterator.py +2911 -0
- backtrader/lineroot.py +1106 -0
- backtrader/lineseries.py +2559 -0
- backtrader/live_trading/__init__.py +31 -0
- backtrader/live_trading/interface.py +404 -0
- backtrader/mathsupport.py +94 -0
- backtrader/metabase.py +1804 -0
- backtrader/mixins/__init__.py +21 -0
- backtrader/mixins/singleton.py +118 -0
- backtrader/observer.py +106 -0
- backtrader/observers/__init__.py +45 -0
- backtrader/observers/benchmark.py +126 -0
- backtrader/observers/broker.py +184 -0
- backtrader/observers/buysell.py +144 -0
- backtrader/observers/drawdown.py +161 -0
- backtrader/observers/logreturns.py +113 -0
- backtrader/observers/timereturn.py +86 -0
- backtrader/observers/trade_logger.py +2972 -0
- backtrader/observers/tradelogger.py +6 -0
- backtrader/observers/trades.py +258 -0
- backtrader/order.py +1114 -0
- backtrader/parameters.py +2345 -0
- backtrader/plot/__init__.py +54 -0
- backtrader/plot/finance.py +1022 -0
- backtrader/plot/formatters.py +200 -0
- backtrader/plot/locator.py +353 -0
- backtrader/plot/multicursor.py +495 -0
- backtrader/plot/plot.py +2500 -0
- backtrader/plot/plot_plotly.py +1351 -0
- backtrader/plot/scheme.py +253 -0
- backtrader/plot/utils.py +104 -0
- backtrader/position.py +290 -0
- backtrader/position_modes.py +132 -0
- backtrader/profiles.py +254 -0
- backtrader/reports/__init__.py +39 -0
- backtrader/reports/charts.py +371 -0
- backtrader/reports/performance.py +620 -0
- backtrader/reports/reporter.py +660 -0
- backtrader/resamplerfilter.py +1001 -0
- backtrader/signal.py +118 -0
- backtrader/signals/__init__.py +17 -0
- backtrader/sizer.py +114 -0
- backtrader/sizers/__init__.py +26 -0
- backtrader/sizers/fixedsize.py +161 -0
- backtrader/sizers/percents_sizer.py +119 -0
- backtrader/store.py +221 -0
- backtrader/stores/__init__.py +33 -0
- backtrader/stores/btapistore.py +15506 -0
- backtrader/stores/livestore.py +137 -0
- backtrader/stores/vchartfile.py +96 -0
- backtrader/strategy.py +3655 -0
- backtrader/talib.py +280 -0
- backtrader/test_helpers.py +96 -0
- backtrader/timer.py +358 -0
- backtrader/trade.py +442 -0
- backtrader/tradingcal.py +361 -0
- backtrader/utils/__init__.py +68 -0
- backtrader/utils/autodict.py +251 -0
- backtrader/utils/date.py +71 -0
- backtrader/utils/dateintern.py +509 -0
- backtrader/utils/flushfile.py +94 -0
- backtrader/utils/fractal.py +101 -0
- backtrader/utils/get_metrics.py +101 -0
- backtrader/utils/load_data.py +209 -0
- backtrader/utils/log_message.py +998 -0
- backtrader/utils/ordereddefaultdict.py +75 -0
- backtrader/utils/py3.py +296 -0
- backtrader/version.py +21 -0
- 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
|