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,226 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Annual Return Analyzer Module - Annual return calculation.
|
|
3
|
+
|
|
4
|
+
This module provides the AnnualReturn analyzer for calculating
|
|
5
|
+
year-by-year returns of a strategy.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
AnnualReturn: Analyzer that calculates annual returns.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> cerebro = bt.Cerebro()
|
|
12
|
+
>>> cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annret')
|
|
13
|
+
>>> results = cerebro.run()
|
|
14
|
+
>>> print(results[0].analyzers.annret.get_analysis())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import math
|
|
18
|
+
from collections import OrderedDict
|
|
19
|
+
|
|
20
|
+
from ..analyzer import Analyzer
|
|
21
|
+
from ..utils.date import num2date
|
|
22
|
+
from ..utils.log_message import get_logger
|
|
23
|
+
from ..utils.py3 import range
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Calculate annual returns. The algorithm implementation is somewhat complex, so a pandas-based version MyAnnualReturn was written later with much simpler logic
|
|
29
|
+
class AnnualReturn(Analyzer):
|
|
30
|
+
"""
|
|
31
|
+
This analyzer calculates the AnnualReturns by looking at the beginning
|
|
32
|
+
and end of the year
|
|
33
|
+
|
|
34
|
+
Params:
|
|
35
|
+
|
|
36
|
+
- (None)
|
|
37
|
+
|
|
38
|
+
Member Attributes:
|
|
39
|
+
|
|
40
|
+
- ``rets``: list of calculated annual returns
|
|
41
|
+
|
|
42
|
+
- ``ret``: dictionary (key: year) of annual returns
|
|
43
|
+
|
|
44
|
+
**get_analysis**:
|
|
45
|
+
|
|
46
|
+
- Returns a dictionary of annual returns (key: year)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
"""Initialize the AnnualReturn analyzer.
|
|
51
|
+
|
|
52
|
+
Initializes cache lists for storing dates and values during backtesting.
|
|
53
|
+
"""
|
|
54
|
+
super().__init__()
|
|
55
|
+
# Cache data
|
|
56
|
+
self._dt_cache = []
|
|
57
|
+
self._value_cache = []
|
|
58
|
+
|
|
59
|
+
def next(self):
|
|
60
|
+
"""Cache current date and account value on each bar.
|
|
61
|
+
|
|
62
|
+
Stores the current datetime and portfolio value for later
|
|
63
|
+
annual return calculation.
|
|
64
|
+
"""
|
|
65
|
+
# Cache current date and account value each time next is called
|
|
66
|
+
dt_val = self.data.datetime[0]
|
|
67
|
+
value_val = self.strategy.broker.getvalue()
|
|
68
|
+
self._dt_cache.append(dt_val)
|
|
69
|
+
self._value_cache.append(value_val)
|
|
70
|
+
|
|
71
|
+
def stop(self):
|
|
72
|
+
"""Calculate annual returns from cached data.
|
|
73
|
+
|
|
74
|
+
Iterates through cached date-value pairs to calculate returns
|
|
75
|
+
for each calendar year. Stores results in self.rets (list) and
|
|
76
|
+
self.ret (dictionary keyed by year).
|
|
77
|
+
"""
|
|
78
|
+
# Must have stats.broker
|
|
79
|
+
# Current year
|
|
80
|
+
cur_year = -1
|
|
81
|
+
# Start value
|
|
82
|
+
value_start = 0.0
|
|
83
|
+
# End value
|
|
84
|
+
value_end = 0.0
|
|
85
|
+
# Save return data
|
|
86
|
+
setattr(self, "rets", [])
|
|
87
|
+
setattr(self, "ret", OrderedDict())
|
|
88
|
+
|
|
89
|
+
# Calculate using cached data
|
|
90
|
+
for i in range(len(self._dt_cache)):
|
|
91
|
+
dt_val = self._dt_cache[i]
|
|
92
|
+
value_cur = self._value_cache[i]
|
|
93
|
+
|
|
94
|
+
# Convert date
|
|
95
|
+
try:
|
|
96
|
+
dt = num2date(dt_val)
|
|
97
|
+
except (ValueError, TypeError, OverflowError) as e:
|
|
98
|
+
logger.warning("Failed to convert date value %s: %s", dt_val, e)
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# If the year at index i is greater than current year, if current year > 0, calculate return and save to self.ret, and start value equals end value
|
|
102
|
+
# When years are not equal, it indicates current i is a new year
|
|
103
|
+
if dt.year > cur_year:
|
|
104
|
+
if cur_year >= 0:
|
|
105
|
+
annual_ret = self._safe_annual_return(value_start, value_end)
|
|
106
|
+
self.rets.append(annual_ret)
|
|
107
|
+
self.ret[cur_year] = annual_ret
|
|
108
|
+
|
|
109
|
+
# changing between real years, use last value as new start
|
|
110
|
+
value_start = value_end
|
|
111
|
+
else:
|
|
112
|
+
# No value set whatsoever, use the currently loaded value
|
|
113
|
+
value_start = value_cur
|
|
114
|
+
|
|
115
|
+
cur_year = dt.year
|
|
116
|
+
|
|
117
|
+
# No matter what, the last value is always the last loaded value
|
|
118
|
+
value_end = value_cur
|
|
119
|
+
# If current year hasn't ended and return hasn't been calculated, calculate at the end even if less than a full year
|
|
120
|
+
if cur_year >= 0 and cur_year not in self.ret:
|
|
121
|
+
# finish calculating pending data
|
|
122
|
+
annual_ret = self._safe_annual_return(value_start, value_end)
|
|
123
|
+
self.rets.append(annual_ret)
|
|
124
|
+
self.ret[cur_year] = annual_ret
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _safe_annual_return(value_start, value_end):
|
|
128
|
+
"""Compute (value_end/value_start - 1), returning 0.0 for any invalid
|
|
129
|
+
(zero/NaN/inf/complex) inputs or result. Shared by the year-boundary
|
|
130
|
+
and final-pending paths in stop()."""
|
|
131
|
+
try:
|
|
132
|
+
valid_values = (
|
|
133
|
+
value_start != 0 and math.isfinite(value_start) and math.isfinite(value_end)
|
|
134
|
+
)
|
|
135
|
+
except TypeError:
|
|
136
|
+
valid_values = False
|
|
137
|
+
if not valid_values:
|
|
138
|
+
return 0.0
|
|
139
|
+
try:
|
|
140
|
+
annual_ret = (value_end / value_start) - 1.0
|
|
141
|
+
if isinstance(annual_ret, complex) or not math.isfinite(annual_ret):
|
|
142
|
+
return 0.0
|
|
143
|
+
return annual_ret
|
|
144
|
+
except (ZeroDivisionError, TypeError, ValueError):
|
|
145
|
+
return 0.0
|
|
146
|
+
|
|
147
|
+
def get_analysis(self):
|
|
148
|
+
"""Return the annual return analysis results.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
OrderedDict: Dictionary mapping years to their annual returns.
|
|
152
|
+
"""
|
|
153
|
+
return self.ret
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class MyAnnualReturn(Analyzer):
|
|
157
|
+
"""
|
|
158
|
+
This analyzer calculates the AnnualReturns by looking at the beginning
|
|
159
|
+
and end of the year
|
|
160
|
+
|
|
161
|
+
Params:
|
|
162
|
+
|
|
163
|
+
- (None)
|
|
164
|
+
|
|
165
|
+
Member Attributes:
|
|
166
|
+
|
|
167
|
+
- ``rets``: list of calculated annual returns
|
|
168
|
+
|
|
169
|
+
- ``ret``: dictionary (key: year) of annual returns
|
|
170
|
+
|
|
171
|
+
**get_analysis**:
|
|
172
|
+
|
|
173
|
+
- Returns a dictionary of annual returns (key: year)
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def stop(self):
|
|
177
|
+
"""Calculate annual returns using pandas.
|
|
178
|
+
|
|
179
|
+
Uses pandas DataFrame operations to group data by year and
|
|
180
|
+
calculate annual returns based on beginning and ending values
|
|
181
|
+
for each year.
|
|
182
|
+
|
|
183
|
+
Note:
|
|
184
|
+
This method requires pandas to be installed.
|
|
185
|
+
"""
|
|
186
|
+
# Container for saving data - dictionary
|
|
187
|
+
if not hasattr(self, "ret"):
|
|
188
|
+
setattr(self, "ret", OrderedDict())
|
|
189
|
+
# Get data time and convert to date
|
|
190
|
+
dt_list = self.data.datetime.get(0, size=len(self.data))
|
|
191
|
+
dt_list = [num2date(i) for i in dt_list]
|
|
192
|
+
# Get account assets
|
|
193
|
+
value_list = self.strategy.stats.broker.value.get(0, size=len(self.data))
|
|
194
|
+
# Convert to pandas format
|
|
195
|
+
import pandas as pd
|
|
196
|
+
|
|
197
|
+
df = pd.DataFrame([dt_list, value_list]).T
|
|
198
|
+
df.columns = ["datetime", "value"]
|
|
199
|
+
df["pre_value"] = df["value"].shift(1)
|
|
200
|
+
# Calculate simple returns for each year
|
|
201
|
+
df["year"] = [i.year for i in df["datetime"]]
|
|
202
|
+
for year, data in df.groupby("year"):
|
|
203
|
+
begin_value = list(data["pre_value"])[0]
|
|
204
|
+
end_value = list(data["value"])[-1]
|
|
205
|
+
try:
|
|
206
|
+
valid_values = math.isfinite(begin_value) and math.isfinite(end_value)
|
|
207
|
+
except TypeError:
|
|
208
|
+
valid_values = False
|
|
209
|
+
if not valid_values or begin_value == 0:
|
|
210
|
+
annual_return = 0.0
|
|
211
|
+
else:
|
|
212
|
+
try:
|
|
213
|
+
annual_return = (end_value / begin_value) - 1
|
|
214
|
+
if isinstance(annual_return, complex) or not math.isfinite(annual_return):
|
|
215
|
+
annual_return = 0.0
|
|
216
|
+
except (ZeroDivisionError, TypeError, ValueError):
|
|
217
|
+
annual_return = 0.0
|
|
218
|
+
self.ret[year] = annual_return
|
|
219
|
+
|
|
220
|
+
def get_analysis(self):
|
|
221
|
+
"""Return the annual return analysis results.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
OrderedDict: Dictionary mapping years to their annual returns.
|
|
225
|
+
"""
|
|
226
|
+
return self.ret
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Calmar Ratio Analyzer Module - Calmar ratio calculation.
|
|
3
|
+
|
|
4
|
+
This module provides the Calmar analyzer for calculating the Calmar
|
|
5
|
+
ratio (annual return divided by maximum drawdown).
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
Calmar: Analyzer that calculates Calmar ratio.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> cerebro = bt.Cerebro()
|
|
12
|
+
>>> cerebro.addanalyzer(bt.analyzers.Calmar, _name='calmar')
|
|
13
|
+
>>> results = cerebro.run()
|
|
14
|
+
>>> print(results[0].analyzers.calmar.get_analysis())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import collections
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
from ..analyzer import TimeFrameAnalyzerBase
|
|
21
|
+
from ..dataseries import TimeFrame
|
|
22
|
+
from ..metabase import OwnerContext
|
|
23
|
+
from .drawdown import TimeDrawDown
|
|
24
|
+
|
|
25
|
+
__all__ = ["Calmar"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Calculate Calmar ratio. Overall, this Calmar calculation is not very successful, or the analyzer/observer series indicators are not very efficient in usage
|
|
29
|
+
# Consider creating an analysis module similar to pyfolio
|
|
30
|
+
class Calmar(TimeFrameAnalyzerBase):
|
|
31
|
+
"""This analyzer calculates the CalmarRatio
|
|
32
|
+
timeframe which can be different from the one used in the underlying data
|
|
33
|
+
Params:
|
|
34
|
+
|
|
35
|
+
- ``timeframe`` (default: ``None``)
|
|
36
|
+
If ``None`` the ``timeframe`` of the first data in the system will be
|
|
37
|
+
used
|
|
38
|
+
|
|
39
|
+
Pass ``TimeFrame.NoTimeFrame`` to consider the entire dataset with no
|
|
40
|
+
time constraints
|
|
41
|
+
|
|
42
|
+
- ``compression`` (default: ``None``)
|
|
43
|
+
|
|
44
|
+
Only used for sub-day timeframes to, for example, work on an hourly
|
|
45
|
+
timeframe by specifying "TimeFrame.Minutes" and 60 as compression
|
|
46
|
+
|
|
47
|
+
If compression is None, then the compression of the first data in the system will be
|
|
48
|
+
used
|
|
49
|
+
- *None*
|
|
50
|
+
|
|
51
|
+
- ``fund`` (default: ``None``)
|
|
52
|
+
|
|
53
|
+
If ``None``, the actual mode of the broker (fundmode - True/False) will
|
|
54
|
+
be autodetected to decide if the returns are based on the total net
|
|
55
|
+
asset value or on the fund value. See ``set_fundmode`` in the broker
|
|
56
|
+
documentation
|
|
57
|
+
|
|
58
|
+
Set it to ``True`` or ``False`` for a specific behavior
|
|
59
|
+
|
|
60
|
+
See also:
|
|
61
|
+
|
|
62
|
+
- https://en.wikipedia.org/wiki/Calmar_ratio
|
|
63
|
+
|
|
64
|
+
Methods:
|
|
65
|
+
- ``get_analysis``
|
|
66
|
+
|
|
67
|
+
Returns a OrderedDict with a key for the time period and the
|
|
68
|
+
corresponding rolling Calmar ratio
|
|
69
|
+
|
|
70
|
+
Attributes:
|
|
71
|
+
- ``calmar`` the latest calculated calmar ratio
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# Modules used
|
|
75
|
+
packages = (
|
|
76
|
+
"collections",
|
|
77
|
+
"math",
|
|
78
|
+
)
|
|
79
|
+
# Parameters
|
|
80
|
+
params = (
|
|
81
|
+
("timeframe", TimeFrame.Months), # default in calmar
|
|
82
|
+
("period", 36),
|
|
83
|
+
("fund", None),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Calculate max drawdown
|
|
87
|
+
def __init__(self, *args, **kwargs):
|
|
88
|
+
"""Initialize the Calmar analyzer.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
*args: Positional arguments.
|
|
92
|
+
**kwargs: Keyword arguments for analyzer parameters.
|
|
93
|
+
"""
|
|
94
|
+
# Call parent class __init__ method to support timeframe and compression parameters
|
|
95
|
+
super().__init__(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
self.calmar = None
|
|
98
|
+
self._fundmode = None
|
|
99
|
+
self._values = None
|
|
100
|
+
self._mdd = None
|
|
101
|
+
# Use OwnerContext so child analyzer can find this as its parent
|
|
102
|
+
with OwnerContext.set_owner(self):
|
|
103
|
+
self._maxdd = TimeDrawDown(timeframe=self.p.timeframe, compression=self.p.compression)
|
|
104
|
+
|
|
105
|
+
# Start
|
|
106
|
+
def start(self):
|
|
107
|
+
"""Initialize the analyzer at the start of the backtest.
|
|
108
|
+
|
|
109
|
+
Sets up the maximum drawdown tracking, value history queue,
|
|
110
|
+
and fund mode.
|
|
111
|
+
"""
|
|
112
|
+
# Max drawdown rate
|
|
113
|
+
self._mdd = float("-inf")
|
|
114
|
+
# Double-ended queue, saves period values, default is 36
|
|
115
|
+
self._values = collections.deque([float("Nan")] * self.p.period, maxlen=self.p.period)
|
|
116
|
+
# fundmode
|
|
117
|
+
if self.p.fund is None:
|
|
118
|
+
self._fundmode = self.strategy.broker.fundmode
|
|
119
|
+
else:
|
|
120
|
+
self._fundmode = self.p.fund
|
|
121
|
+
# Add different values to self._values based on fundmode
|
|
122
|
+
if not self._fundmode:
|
|
123
|
+
self._values.append(self.strategy.broker.getvalue())
|
|
124
|
+
else:
|
|
125
|
+
self._values.append(self.strategy.broker.fundvalue)
|
|
126
|
+
|
|
127
|
+
def on_dt_over(self):
|
|
128
|
+
"""Calculate Calmar ratio when timeframe period ends.
|
|
129
|
+
|
|
130
|
+
Updates maximum drawdown and calculates Calmar ratio as
|
|
131
|
+
annualized return divided by maximum drawdown.
|
|
132
|
+
"""
|
|
133
|
+
# Max drawdown rate
|
|
134
|
+
self._mdd = max(self._mdd, self._maxdd.maxdd)
|
|
135
|
+
# Add value to self._values
|
|
136
|
+
if not self._fundmode:
|
|
137
|
+
self._values.append(self.strategy.broker.getvalue())
|
|
138
|
+
else:
|
|
139
|
+
self._values.append(self.strategy.broker.fundvalue)
|
|
140
|
+
# Calculate average monthly return by default
|
|
141
|
+
try:
|
|
142
|
+
ratio = self._values[-1] / self._values[0]
|
|
143
|
+
if isinstance(ratio, complex) or not math.isfinite(ratio) or ratio <= 0:
|
|
144
|
+
raise ValueError(f"invalid calmar ratio input: {ratio}")
|
|
145
|
+
rann = math.log(ratio) / len(self._values)
|
|
146
|
+
if not math.isfinite(rann):
|
|
147
|
+
raise ValueError(f"invalid calmar annualized return: {rann}")
|
|
148
|
+
except (ZeroDivisionError, TypeError, ValueError):
|
|
149
|
+
rann = 0.0
|
|
150
|
+
# Calculate Calmar indicator
|
|
151
|
+
try:
|
|
152
|
+
self.calmar = calmar = rann / (self._mdd or float("Inf"))
|
|
153
|
+
except (ZeroDivisionError, TypeError, ValueError):
|
|
154
|
+
self.calmar = calmar = 0.0
|
|
155
|
+
if isinstance(calmar, complex) or not math.isfinite(calmar):
|
|
156
|
+
self.calmar = calmar = 0.0
|
|
157
|
+
# Save result
|
|
158
|
+
self.rets[self.dtkey] = calmar
|
|
159
|
+
|
|
160
|
+
def stop(self):
|
|
161
|
+
"""Finalize the analysis when backtest ends.
|
|
162
|
+
|
|
163
|
+
Triggers one final Calmar ratio calculation.
|
|
164
|
+
"""
|
|
165
|
+
self.on_dt_over() # update last values
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""DrawDown Analyzer Module - Drawdown statistics calculation.
|
|
3
|
+
|
|
4
|
+
This module provides analyzers for calculating drawdown statistics including
|
|
5
|
+
current drawdown, maximum drawdown, and drawdown duration.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
DrawDown: Analyzer that calculates drawdown statistics.
|
|
9
|
+
TimeDrawDown: Time-frame based drawdown analyzer.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> cerebro = bt.Cerebro()
|
|
13
|
+
>>> cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd')
|
|
14
|
+
>>> results = cerebro.run()
|
|
15
|
+
>>> print(results[0].analyzers.dd.get_analysis())
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
from ..analyzer import Analyzer, TimeFrameAnalyzerBase
|
|
21
|
+
from ..mathsupport import is_finite_real
|
|
22
|
+
from ..utils import AutoOrderedDict
|
|
23
|
+
|
|
24
|
+
__all__ = ["DrawDown", "TimeDrawDown"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Analyze drawdown situation
|
|
28
|
+
class DrawDown(Analyzer):
|
|
29
|
+
"""This analyzer calculates trading system drawdowns stats such as drawdown
|
|
30
|
+
values in %s and in dollars, max drawdown in %s and in dollars, drawdown
|
|
31
|
+
length and drawdown max length
|
|
32
|
+
|
|
33
|
+
Params:
|
|
34
|
+
|
|
35
|
+
- ``fund`` (default: ``None``)
|
|
36
|
+
|
|
37
|
+
If ``None``, the actual mode of the broker (fundmode - True/False) will
|
|
38
|
+
be autodetected to decide if the returns are based on the total net
|
|
39
|
+
asset value or on the fund value. See ``set_fundmode`` in the broker
|
|
40
|
+
documentation
|
|
41
|
+
|
|
42
|
+
Set it to ``True`` or ``False`` for a specific behavior
|
|
43
|
+
|
|
44
|
+
Methods:
|
|
45
|
+
|
|
46
|
+
- ``get_analysis``
|
|
47
|
+
|
|
48
|
+
Returns a dictionary (with . notation support and subdctionaries) with
|
|
49
|
+
drawdown stats as values, the following keys/attributes are available:
|
|
50
|
+
|
|
51
|
+
- ``drawdown`` - drawdown value in 0.xx %
|
|
52
|
+
- ``moneydown`` - drawdown value in monetary units
|
|
53
|
+
- ``len`` - drawdown length
|
|
54
|
+
|
|
55
|
+
- ``max.drawdown`` - max drawdown value in 0.xx %
|
|
56
|
+
- ``max.moneydown`` - max drawdown value in monetary units
|
|
57
|
+
- ``max.len`` - max drawdown length
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
params = (("fund", None),)
|
|
61
|
+
|
|
62
|
+
# Start, get fundmode
|
|
63
|
+
def start(self):
|
|
64
|
+
"""Initialize the analyzer at the start of the backtest.
|
|
65
|
+
|
|
66
|
+
Sets the fund mode based on parameters or broker settings.
|
|
67
|
+
"""
|
|
68
|
+
super().start()
|
|
69
|
+
if self.p.fund is None:
|
|
70
|
+
# self._fundmode = self.strategy.broker.fundmode
|
|
71
|
+
setattr(self, "_fundmode", self.strategy.broker.fundmode)
|
|
72
|
+
else:
|
|
73
|
+
# self._fundmode = self.p.fund
|
|
74
|
+
setattr(self, "_fundmode", self.p.fund)
|
|
75
|
+
|
|
76
|
+
# Create indicator values to analyze
|
|
77
|
+
def create_analysis(self):
|
|
78
|
+
"""Create the analysis result data structure.
|
|
79
|
+
|
|
80
|
+
Initializes the results dictionary with all drawdown metrics set to zero.
|
|
81
|
+
"""
|
|
82
|
+
self.rets = AutoOrderedDict() # dict with. notation
|
|
83
|
+
|
|
84
|
+
self.rets.len = 0
|
|
85
|
+
self.rets.drawdown = 0.0
|
|
86
|
+
self.rets.moneydown = 0.0
|
|
87
|
+
|
|
88
|
+
self.rets.max.len = 0.0
|
|
89
|
+
self.rets.max.drawdown = 0.0
|
|
90
|
+
self.rets.max.moneydown = 0.0
|
|
91
|
+
|
|
92
|
+
self._maxvalue = float("-inf") # any value will outdo it
|
|
93
|
+
|
|
94
|
+
# Stop
|
|
95
|
+
def stop(self):
|
|
96
|
+
"""Finalize the analysis when backtest ends.
|
|
97
|
+
|
|
98
|
+
Closes the results dictionary to prevent further modifications.
|
|
99
|
+
"""
|
|
100
|
+
self.rets._close() # . notation cannot create more keys
|
|
101
|
+
|
|
102
|
+
# Notify fund situation
|
|
103
|
+
def notify_fund(self, cash, value, fundvalue, shares):
|
|
104
|
+
"""Update drawdown calculation with current fund values.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
cash: Current cash amount.
|
|
108
|
+
value: Current portfolio value.
|
|
109
|
+
fundvalue: Current fund value.
|
|
110
|
+
shares: Number of fund shares.
|
|
111
|
+
"""
|
|
112
|
+
current_value = value if not self._fundmode else fundvalue
|
|
113
|
+
self._value = current_value
|
|
114
|
+
if is_finite_real(current_value):
|
|
115
|
+
if not is_finite_real(self._maxvalue):
|
|
116
|
+
self._maxvalue = current_value
|
|
117
|
+
else:
|
|
118
|
+
self._maxvalue = max(self._maxvalue, current_value)
|
|
119
|
+
elif not is_finite_real(self._maxvalue):
|
|
120
|
+
self._maxvalue = 0.0
|
|
121
|
+
|
|
122
|
+
def next(self):
|
|
123
|
+
"""Calculate drawdown for the current period.
|
|
124
|
+
|
|
125
|
+
Updates current and maximum drawdown values and lengths.
|
|
126
|
+
"""
|
|
127
|
+
# PERFORMANCE OPTIMIZATION: Cache attribute access to reduce lookups
|
|
128
|
+
# Called 688K+ times, attribute caching helps
|
|
129
|
+
r = self.rets
|
|
130
|
+
maxvalue = self._maxvalue
|
|
131
|
+
value = self._value
|
|
132
|
+
r_max = r.max
|
|
133
|
+
|
|
134
|
+
# calculate current drawdown values
|
|
135
|
+
if not (is_finite_real(maxvalue) and is_finite_real(value)):
|
|
136
|
+
moneydown = 0.0
|
|
137
|
+
drawdown = 0.0
|
|
138
|
+
else:
|
|
139
|
+
moneydown = maxvalue - value
|
|
140
|
+
drawdown = 100.0 * moneydown / maxvalue if maxvalue else 0.0
|
|
141
|
+
if isinstance(moneydown, complex) or not math.isfinite(moneydown):
|
|
142
|
+
moneydown = 0.0
|
|
143
|
+
if isinstance(drawdown, complex) or not math.isfinite(drawdown):
|
|
144
|
+
drawdown = 0.0
|
|
145
|
+
|
|
146
|
+
r.moneydown = moneydown
|
|
147
|
+
r.drawdown = drawdown
|
|
148
|
+
|
|
149
|
+
# maximum drawdown values
|
|
150
|
+
if moneydown > r_max.moneydown:
|
|
151
|
+
r_max.moneydown = moneydown
|
|
152
|
+
if drawdown > r_max.drawdown:
|
|
153
|
+
r_max.drawdown = drawdown
|
|
154
|
+
|
|
155
|
+
r.len = r.len + 1 if drawdown else 0
|
|
156
|
+
if r.len > r_max.len:
|
|
157
|
+
r_max.len = r.len
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Analyze time drawdown situation (max drawdown)
|
|
161
|
+
class TimeDrawDown(TimeFrameAnalyzerBase):
|
|
162
|
+
"""This analyzer calculates trading system drawdowns on the chosen
|
|
163
|
+
timeframe which can be different from the one used in the underlying data
|
|
164
|
+
Params:
|
|
165
|
+
|
|
166
|
+
- ``timeframe`` (default: ``None``)
|
|
167
|
+
If ``None`` the ``timeframe`` of the 1st data in the system will be
|
|
168
|
+
used
|
|
169
|
+
|
|
170
|
+
Pass ``TimeFrame.NoTimeFrame`` to consider the entire dataset with no
|
|
171
|
+
time constraints
|
|
172
|
+
|
|
173
|
+
- ``compression`` (default: ``None``)
|
|
174
|
+
|
|
175
|
+
Only used for sub-day timeframes to, for example, work on an hourly
|
|
176
|
+
timeframe by specifying "TimeFrame.Minutes" and 60 as compression
|
|
177
|
+
|
|
178
|
+
If None, then the compression of the 1st data of the system will be
|
|
179
|
+
used
|
|
180
|
+
- *None*
|
|
181
|
+
|
|
182
|
+
- ``fund`` (default: ``None``)
|
|
183
|
+
|
|
184
|
+
If ``None``, the actual mode of the broker (fundmode - True/False) will
|
|
185
|
+
be autodetected to decide if the returns are based on the total net
|
|
186
|
+
asset value or on the fund value. See ``set_fundmode`` in the broker
|
|
187
|
+
documentation
|
|
188
|
+
|
|
189
|
+
Set it to ``True`` or ``False`` for a specific behavior
|
|
190
|
+
|
|
191
|
+
Methods:
|
|
192
|
+
|
|
193
|
+
- ``get_analysis``
|
|
194
|
+
|
|
195
|
+
Returns a dictionary (with . notation support and subdctionaries) with
|
|
196
|
+
drawdown stats as values, the following keys/attributes are available:
|
|
197
|
+
|
|
198
|
+
- ``drawdown`` - drawdown value in 0.xx %
|
|
199
|
+
- ``maxdrawdown`` - drawdown value in monetary units
|
|
200
|
+
- ``maxdrawdownperiod`` - drawdown length
|
|
201
|
+
|
|
202
|
+
- Those are available during runs as attributes
|
|
203
|
+
- ``dd``
|
|
204
|
+
- ``maxdd``
|
|
205
|
+
- ``maxddlen``
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
params = (("fund", None),)
|
|
209
|
+
|
|
210
|
+
def __init__(self, *args, **kwargs):
|
|
211
|
+
"""Initialize the TimeDrawDown analyzer.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
*args: Positional arguments.
|
|
215
|
+
**kwargs: Keyword arguments for analyzer parameters.
|
|
216
|
+
"""
|
|
217
|
+
# Call parent class __init__ method to support timeframe and compression parameters
|
|
218
|
+
super().__init__(*args, **kwargs)
|
|
219
|
+
|
|
220
|
+
self.ddlen = None
|
|
221
|
+
self.peak = None
|
|
222
|
+
self.maxddlen = None
|
|
223
|
+
self.maxdd = None
|
|
224
|
+
self.dd = None
|
|
225
|
+
self._fundmode = None
|
|
226
|
+
|
|
227
|
+
def start(self):
|
|
228
|
+
"""Initialize the analyzer at the start of the backtest.
|
|
229
|
+
|
|
230
|
+
Sets the fund mode and initializes drawdown tracking variables.
|
|
231
|
+
"""
|
|
232
|
+
super().start()
|
|
233
|
+
# fundmode
|
|
234
|
+
if self.p.fund is None:
|
|
235
|
+
self._fundmode = self.strategy.broker.fundmode
|
|
236
|
+
else:
|
|
237
|
+
self._fundmode = self.p.fund
|
|
238
|
+
# Initialize parameters
|
|
239
|
+
self.dd = 0.0
|
|
240
|
+
self.maxdd = 0.0
|
|
241
|
+
self.maxddlen = 0
|
|
242
|
+
self.peak = float("-inf")
|
|
243
|
+
self.ddlen = 0
|
|
244
|
+
|
|
245
|
+
# Calculate max drawdown and max drawdown length
|
|
246
|
+
def on_dt_over(self):
|
|
247
|
+
"""Called when a datetime period is over.
|
|
248
|
+
|
|
249
|
+
Updates drawdown calculations for the timeframe period.
|
|
250
|
+
"""
|
|
251
|
+
if not self._fundmode:
|
|
252
|
+
value = self.strategy.broker.getvalue()
|
|
253
|
+
else:
|
|
254
|
+
value = self.strategy.broker.fundvalue
|
|
255
|
+
value_valid = is_finite_real(value)
|
|
256
|
+
if not is_finite_real(self.peak):
|
|
257
|
+
self.peak = 0.0
|
|
258
|
+
|
|
259
|
+
# update the maximum seen peak
|
|
260
|
+
if value_valid and value > self.peak:
|
|
261
|
+
self.peak = value
|
|
262
|
+
self.ddlen = 0 # start of streak
|
|
263
|
+
|
|
264
|
+
# calculate the current drawdown
|
|
265
|
+
try:
|
|
266
|
+
if value_valid and self.peak:
|
|
267
|
+
self.dd = dd = 100.0 * (self.peak - value) / self.peak
|
|
268
|
+
if isinstance(dd, complex) or not math.isfinite(dd):
|
|
269
|
+
self.dd = dd = 0.0
|
|
270
|
+
else:
|
|
271
|
+
self.dd = dd = 0.0
|
|
272
|
+
except (TypeError, ValueError, ZeroDivisionError):
|
|
273
|
+
self.dd = dd = 0.0
|
|
274
|
+
self.ddlen += bool(dd) # if peak == value -> dd = 0
|
|
275
|
+
|
|
276
|
+
# update the maxdrawdown if needed
|
|
277
|
+
self.maxdd = max(self.maxdd if is_finite_real(self.maxdd) else 0.0, dd)
|
|
278
|
+
self.maxddlen = max(self.maxddlen, self.ddlen)
|
|
279
|
+
|
|
280
|
+
# When stopping, add max drawdown and max drawdown length to dictionary
|
|
281
|
+
def stop(self):
|
|
282
|
+
"""Finalize the analysis when backtest ends.
|
|
283
|
+
|
|
284
|
+
Stores the maximum drawdown and maximum drawdown period.
|
|
285
|
+
"""
|
|
286
|
+
self.rets["maxdrawdown"] = self.maxdd
|
|
287
|
+
self.rets["maxdrawdownperiod"] = self.maxddlen
|