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,470 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""PyFolio Analyzer Module - PyFolio integration.
|
|
3
|
+
|
|
4
|
+
This module provides the PyFolio analyzer for collecting data compatible
|
|
5
|
+
with the pyfolio library for performance analysis.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
PyFolio: Analyzer that collects data for pyfolio.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> cerebro = bt.Cerebro()
|
|
12
|
+
>>> cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
|
|
13
|
+
>>> results = cerebro.run()
|
|
14
|
+
>>> pyfolio_data = results[0].analyzers.pyfolio.get_analysis()
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from ..analyzer import Analyzer
|
|
24
|
+
from ..dataseries import TimeFrame
|
|
25
|
+
from ..metabase import OwnerContext
|
|
26
|
+
from ..utils.py3 import iteritems
|
|
27
|
+
from .leverage import GrossLeverage
|
|
28
|
+
from .positions import PositionsValue
|
|
29
|
+
from .timereturn import TimeReturn
|
|
30
|
+
from .transactions import Transactions
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _daily_returns(values, name):
|
|
34
|
+
"""Copy a daily return series and align timezone-aware and naive date indices."""
|
|
35
|
+
if isinstance(values, pd.DataFrame):
|
|
36
|
+
if name in values:
|
|
37
|
+
values = values[name]
|
|
38
|
+
elif len(values.columns) == 1:
|
|
39
|
+
values = values.iloc[:, 0]
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError(f"Expected one return column or a {name!r} column")
|
|
42
|
+
series = pd.Series(values, dtype=float).copy()
|
|
43
|
+
series.index = pd.to_datetime(series.index, utc=True).tz_localize(None).normalize()
|
|
44
|
+
if series.index.has_duplicates:
|
|
45
|
+
raise ValueError("Formatted PyFolio metrics require one return per day")
|
|
46
|
+
series = series.sort_index().dropna()
|
|
47
|
+
if not np.isfinite(series.to_numpy()).all() or (series < -1).any():
|
|
48
|
+
raise ValueError("Returns must be finite and cannot be less than -100%")
|
|
49
|
+
return series.rename(name)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ratio(numerator, denominator):
|
|
53
|
+
"""Return NaN for a ratio whose denominator does not define a statistic."""
|
|
54
|
+
return numerator / denominator if denominator > 0 else float("nan")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _drawdown_details(returns):
|
|
58
|
+
"""Return signed maximum drawdown and (peak, trough, recovery) dates."""
|
|
59
|
+
if returns.empty:
|
|
60
|
+
return float("nan"), None
|
|
61
|
+
wealth = (1.0 + returns).cumprod()
|
|
62
|
+
peaks = wealth.cummax().clip(lower=1.0)
|
|
63
|
+
drawdowns = wealth.div(peaks).sub(1.0)
|
|
64
|
+
trough_pos = int(np.argmin(drawdowns.to_numpy()))
|
|
65
|
+
drawdown = float(drawdowns.iloc[trough_pos])
|
|
66
|
+
if drawdown == 0:
|
|
67
|
+
return 0.0, None
|
|
68
|
+
previous = wealth.iloc[:trough_pos]
|
|
69
|
+
peak_pos = int(np.argmax(previous.to_numpy())) if not previous.empty else 0
|
|
70
|
+
# None identifies starting capital before the first recorded return.
|
|
71
|
+
peak = previous.index[peak_pos] if not previous.empty and previous.iloc[peak_pos] >= 1 else None
|
|
72
|
+
recovered = wealth.iloc[trough_pos + 1 :]
|
|
73
|
+
recovered = recovered[recovered >= peaks.iloc[trough_pos]]
|
|
74
|
+
recovery = recovered.index[0] if not recovered.empty else None
|
|
75
|
+
return drawdown, (peak, wealth.index[trough_pos], recovery)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# pyfolio analysis module
|
|
79
|
+
class PyFolio(Analyzer):
|
|
80
|
+
"""This analyzer uses 4 children analyzers to collect data and transforms it
|
|
81
|
+
in to a data set compatible with ``pyfolio``
|
|
82
|
+
|
|
83
|
+
Children Analyzer
|
|
84
|
+
|
|
85
|
+
- ``TimeReturn``
|
|
86
|
+
|
|
87
|
+
Used to calculate the returns of the global portfolio value
|
|
88
|
+
|
|
89
|
+
- ``PositionsValue``
|
|
90
|
+
|
|
91
|
+
Used to calculate the value of the positions per data. It sets the
|
|
92
|
+
``headers`` and ``cash`` parameters to ``True``
|
|
93
|
+
|
|
94
|
+
- ``Transactions``
|
|
95
|
+
|
|
96
|
+
Used to record each transaction on a data (size, price, value). Sets
|
|
97
|
+
the ``headers`` parameter to ``True``
|
|
98
|
+
|
|
99
|
+
- ``GrossLeverage``
|
|
100
|
+
|
|
101
|
+
Keeps track of the gross leverage (how much the strategy is invested)
|
|
102
|
+
|
|
103
|
+
Params:
|
|
104
|
+
These are passed transparently to the children
|
|
105
|
+
|
|
106
|
+
- timeframe (default: ``bt.TimeFrame.Days``)
|
|
107
|
+
|
|
108
|
+
If ``None`` then the timeframe of the 1st data of the system will be
|
|
109
|
+
used
|
|
110
|
+
|
|
111
|
+
- compression (default: `1``)
|
|
112
|
+
|
|
113
|
+
If ``None`` then the compression of the 1st data of the system will be
|
|
114
|
+
used
|
|
115
|
+
|
|
116
|
+
Both ``timeframe`` and ``compression`` are set following the default
|
|
117
|
+
behavior of ``pyfolio`` which is working with *daily* data and upsample it
|
|
118
|
+
to obtaine values like yearly returns.
|
|
119
|
+
|
|
120
|
+
Methods:
|
|
121
|
+
|
|
122
|
+
- get_analysis
|
|
123
|
+
|
|
124
|
+
Returns a dictionary with returns as values and the datetime points for
|
|
125
|
+
each return as keys
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# Parameters
|
|
129
|
+
params = (("timeframe", TimeFrame.Days), ("compression", 1))
|
|
130
|
+
|
|
131
|
+
# Initialize
|
|
132
|
+
def __init__(self, *args, **kwargs):
|
|
133
|
+
"""Initialize the PyFolio analyzer.
|
|
134
|
+
|
|
135
|
+
Creates child analyzers (TimeReturn, PositionsValue, Transactions,
|
|
136
|
+
GrossLeverage) to collect data for pyfolio integration.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
*args: Positional arguments.
|
|
140
|
+
**kwargs: Keyword arguments for analyzer parameters.
|
|
141
|
+
"""
|
|
142
|
+
# CRITICAL FIX: Call super().__init__() first to initialize self.p
|
|
143
|
+
super().__init__(*args, **kwargs)
|
|
144
|
+
dtfcomp = {"timeframe": self.p.timeframe, "compression": self.p.compression}
|
|
145
|
+
|
|
146
|
+
# Use OwnerContext so child analyzers can find this as their parent
|
|
147
|
+
with OwnerContext.set_owner(self):
|
|
148
|
+
self._returns = TimeReturn(**dtfcomp)
|
|
149
|
+
self._positions = PositionsValue(headers=True, cash=True)
|
|
150
|
+
self._transactions = Transactions(headers=True)
|
|
151
|
+
self._gross_lev = GrossLeverage()
|
|
152
|
+
|
|
153
|
+
# When stopping, get several analysis results
|
|
154
|
+
def stop(self):
|
|
155
|
+
"""Collect results from child analyzers when backtest ends.
|
|
156
|
+
|
|
157
|
+
Gathers returns, positions, transactions, and gross leverage data
|
|
158
|
+
from the child analyzers for pyfolio processing.
|
|
159
|
+
"""
|
|
160
|
+
super().stop()
|
|
161
|
+
self.rets["returns"] = self._returns.get_analysis()
|
|
162
|
+
self.rets["positions"] = self._positions.get_analysis()
|
|
163
|
+
self.rets["transactions"] = self._transactions.get_analysis()
|
|
164
|
+
self.rets["gross_lev"] = self._gross_lev.get_analysis()
|
|
165
|
+
|
|
166
|
+
# Adjust the results of the above four analyzers to get the input information required by pyfolio
|
|
167
|
+
def get_pf_items(self):
|
|
168
|
+
"""Returns a tuple of 4 elements which can be used for further processing with
|
|
169
|
+
``pyfolio``
|
|
170
|
+
|
|
171
|
+
returns, positions, transactions, gross_leverage
|
|
172
|
+
|
|
173
|
+
Because the objects are meant to be used as direct input to ``pyfolio``
|
|
174
|
+
this method makes a local import of ``pandas`` to convert the internal
|
|
175
|
+
*backtrader* results to *pandas DataFrames* which is the expected input
|
|
176
|
+
by, for example, ``pyfolio.create_full_tear_sheet``
|
|
177
|
+
|
|
178
|
+
The method will break if ``pandas`` is not installed
|
|
179
|
+
"""
|
|
180
|
+
# keep import local to avoid disturbing installations with no pandas
|
|
181
|
+
# Returns
|
|
182
|
+
# Process returns
|
|
183
|
+
cols = ["index", "return"]
|
|
184
|
+
returns = pd.DataFrame.from_records(
|
|
185
|
+
iteritems(self.rets["returns"]), index=cols[0], columns=cols
|
|
186
|
+
)
|
|
187
|
+
returns.index = pd.to_datetime(returns.index)
|
|
188
|
+
returns.index = returns.index.tz_localize("UTC")
|
|
189
|
+
rets = returns["return"]
|
|
190
|
+
#
|
|
191
|
+
# Positions
|
|
192
|
+
# Process position
|
|
193
|
+
pss = self.rets["positions"]
|
|
194
|
+
# ps = [[k] + v[-2:] for k, v in iteritems(pss)]
|
|
195
|
+
ps = [[k] + v for k, v in iteritems(pss)]
|
|
196
|
+
if ps:
|
|
197
|
+
cols = ps.pop(0) # headers are in the first entry
|
|
198
|
+
positions = pd.DataFrame.from_records(ps, columns=cols)
|
|
199
|
+
else:
|
|
200
|
+
positions = pd.DataFrame(columns=["Datetime"])
|
|
201
|
+
positions.index = pd.to_datetime(positions["Datetime"])
|
|
202
|
+
del positions["Datetime"]
|
|
203
|
+
positions.index = positions.index.tz_localize("UTC")
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# Transactions
|
|
207
|
+
# Process transactions
|
|
208
|
+
txss = self.rets["transactions"]
|
|
209
|
+
txs = []
|
|
210
|
+
# The transactions have a common key (date) and can potentially happend
|
|
211
|
+
# for several assets. The dictionary has a single key and a list of
|
|
212
|
+
# lists. Each sublist contains the fields of a transaction
|
|
213
|
+
# Hence the double loop to undo the list indirection
|
|
214
|
+
for k, v in iteritems(txss):
|
|
215
|
+
for v2 in v:
|
|
216
|
+
txs.append([k] + v2)
|
|
217
|
+
|
|
218
|
+
if txs:
|
|
219
|
+
cols = txs.pop(0) # headers are in the first entry
|
|
220
|
+
transactions = pd.DataFrame.from_records(txs, index=cols[0], columns=cols)
|
|
221
|
+
else:
|
|
222
|
+
transactions = pd.DataFrame(
|
|
223
|
+
columns=["date", "amount", "price", "sid", "symbol", "value"]
|
|
224
|
+
).set_index("date")
|
|
225
|
+
transactions.index = pd.to_datetime(transactions.index)
|
|
226
|
+
transactions.index = transactions.index.tz_localize("UTC")
|
|
227
|
+
|
|
228
|
+
# Gross Leverage
|
|
229
|
+
# Process leverage
|
|
230
|
+
cols = ["index", "gross_lev"]
|
|
231
|
+
gross_lev = pd.DataFrame.from_records(
|
|
232
|
+
iteritems(self.rets["gross_lev"]), index=cols[0], columns=cols
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
gross_lev.index = pd.to_datetime(gross_lev.index)
|
|
236
|
+
gross_lev.index = gross_lev.index.tz_localize("UTC")
|
|
237
|
+
glev = gross_lev["gross_lev"]
|
|
238
|
+
|
|
239
|
+
# Return all together
|
|
240
|
+
# Return all results
|
|
241
|
+
return rets, positions, transactions, glev
|
|
242
|
+
|
|
243
|
+
def _get_order_type(self, row):
|
|
244
|
+
"""Return the execution side, including buys that close a short position."""
|
|
245
|
+
if row["TRADE_TYPE"] not in {"开仓", "平仓"} or not row["amount"]:
|
|
246
|
+
return np.nan
|
|
247
|
+
return "买入" if row["amount"] > 0 else "卖出"
|
|
248
|
+
|
|
249
|
+
def _compute_profit_loss(self, trade_info, open_type, close_type):
|
|
250
|
+
"""Fill gross realized PnL for labeled opens/closes, grouped by symbol.
|
|
251
|
+
|
|
252
|
+
Quantities come from ENTRUST_NUM when present; legacy tables containing
|
|
253
|
+
only TRADE_AMT represent one full lot per row. Partial closes use the
|
|
254
|
+
position's weighted entry price, as the broker does.
|
|
255
|
+
"""
|
|
256
|
+
positions = {}
|
|
257
|
+
direction = -1 if open_type == "开空" else 1
|
|
258
|
+
for index, row in trade_info.iterrows():
|
|
259
|
+
if row["ORDER_TYPE"] not in {open_type, close_type}:
|
|
260
|
+
continue
|
|
261
|
+
symbol = row.get("O_CODE", row.get("symbol", ""))
|
|
262
|
+
quantity = abs(float(row.get("ENTRUST_NUM", 1.0)))
|
|
263
|
+
if not quantity:
|
|
264
|
+
continue
|
|
265
|
+
price = abs(float(row["TRADE_AMT"])) / quantity
|
|
266
|
+
size, average = positions.get(symbol, (0.0, 0.0))
|
|
267
|
+
if row["ORDER_TYPE"] == open_type:
|
|
268
|
+
average = (size * average + quantity * price) / (size + quantity)
|
|
269
|
+
positions[symbol] = (size + quantity, average)
|
|
270
|
+
trade_info.at[index, "PROFIT_LOSS"] = 0.0
|
|
271
|
+
else:
|
|
272
|
+
if quantity > size:
|
|
273
|
+
raise ValueError("Closing quantity exceeds the preceding opening quantity")
|
|
274
|
+
trade_info.at[index, "PROFIT_LOSS"] = direction * quantity * (price - average)
|
|
275
|
+
positions[symbol] = (size - quantity, average)
|
|
276
|
+
return trade_info
|
|
277
|
+
|
|
278
|
+
def _get_trade_info(self, trade_info, symbol_name, current_user_name="", pair_num=None):
|
|
279
|
+
"""Format signed transactions into master's 19-column trade table.
|
|
280
|
+
|
|
281
|
+
Position transitions determine opens/closes, replacing the old row-modulo
|
|
282
|
+
pairing (which labeled every row as an opening with pair_num=1). A reversal
|
|
283
|
+
is split into its closing and opening parts. pair_num remains accepted for
|
|
284
|
+
source compatibility. PnL is gross, uses unit price and signed quantity,
|
|
285
|
+
and does not infer futures multipliers absent from this transaction schema.
|
|
286
|
+
COMMISSION is NaN unless the input provides an actual commission column.
|
|
287
|
+
"""
|
|
288
|
+
if pair_num is not None and (not isinstance(pair_num, int) or pair_num < 1):
|
|
289
|
+
raise ValueError("pair_num must be a positive integer or None")
|
|
290
|
+
columns = [
|
|
291
|
+
"TRADE_DATE",
|
|
292
|
+
"ENTRUST_DATE",
|
|
293
|
+
"O_CODE",
|
|
294
|
+
"KIND",
|
|
295
|
+
"TRADE_TYPE",
|
|
296
|
+
"ORDER_TYPE",
|
|
297
|
+
"TRADE_NUM",
|
|
298
|
+
"TRADE_PRICE",
|
|
299
|
+
"TRADE_AMT",
|
|
300
|
+
"ENTRUST_NUM",
|
|
301
|
+
"ENTRUST_PRICE",
|
|
302
|
+
"STATUS",
|
|
303
|
+
"PROFIT_LOSS",
|
|
304
|
+
"COMMISSION",
|
|
305
|
+
"CREATE_DATE",
|
|
306
|
+
"CREATE_USER",
|
|
307
|
+
"UPDATE_DATE",
|
|
308
|
+
"UPDATE_USER",
|
|
309
|
+
"D_FLAG",
|
|
310
|
+
]
|
|
311
|
+
records, positions = [], {}
|
|
312
|
+
next_trade = 0
|
|
313
|
+
today = datetime.date.today()
|
|
314
|
+
for timestamp, row in trade_info.sort_index(kind="stable").iterrows():
|
|
315
|
+
amount, price = float(row["amount"]), float(row["price"])
|
|
316
|
+
if not math.isfinite(amount) or not math.isfinite(price):
|
|
317
|
+
raise ValueError("Transaction quantity and price must be finite")
|
|
318
|
+
if not amount:
|
|
319
|
+
continue
|
|
320
|
+
symbol = row["symbol"]
|
|
321
|
+
key = row.get("sid", symbol)
|
|
322
|
+
size, average, trade_num = positions.get(key, (0.0, 0.0, 0))
|
|
323
|
+
date = pd.Timestamp(timestamp).date()
|
|
324
|
+
commission = float(row.get("commission", np.nan))
|
|
325
|
+
parts = []
|
|
326
|
+
if size * amount < 0:
|
|
327
|
+
closed = min(abs(size), abs(amount))
|
|
328
|
+
signed_closed = math.copysign(closed, amount)
|
|
329
|
+
profit = closed * (price - average) * math.copysign(1.0, size)
|
|
330
|
+
parts.append((signed_closed, "平仓", trade_num, profit))
|
|
331
|
+
size += signed_closed
|
|
332
|
+
amount -= signed_closed
|
|
333
|
+
if amount:
|
|
334
|
+
if not size:
|
|
335
|
+
next_trade += 1
|
|
336
|
+
trade_num = next_trade
|
|
337
|
+
average = (abs(size) * average + abs(amount) * price) / (abs(size) + abs(amount))
|
|
338
|
+
size += amount
|
|
339
|
+
parts.append((amount, "开仓", trade_num, 0.0))
|
|
340
|
+
positions[key] = (size, average, trade_num)
|
|
341
|
+
for quantity, phase, number, profit in parts:
|
|
342
|
+
records.append(
|
|
343
|
+
[
|
|
344
|
+
pd.Timestamp(date),
|
|
345
|
+
pd.Timestamp(date),
|
|
346
|
+
symbol,
|
|
347
|
+
symbol_name,
|
|
348
|
+
phase,
|
|
349
|
+
self._get_order_type({"TRADE_TYPE": phase, "amount": quantity}),
|
|
350
|
+
number,
|
|
351
|
+
price,
|
|
352
|
+
quantity * price,
|
|
353
|
+
quantity,
|
|
354
|
+
price,
|
|
355
|
+
"完成",
|
|
356
|
+
profit,
|
|
357
|
+
commission * abs(quantity / float(row["amount"])),
|
|
358
|
+
today,
|
|
359
|
+
current_user_name,
|
|
360
|
+
today,
|
|
361
|
+
current_user_name,
|
|
362
|
+
0,
|
|
363
|
+
]
|
|
364
|
+
)
|
|
365
|
+
return pd.DataFrame.from_records(records, columns=columns)
|
|
366
|
+
|
|
367
|
+
def _get_performance_indicators(self, results, returns, benchmark_returns, current_user_name):
|
|
368
|
+
"""Return master's performance fields with daily (252-session) statistics.
|
|
369
|
+
|
|
370
|
+
This compatibility report uses pandas/numpy rather than optional empyrical
|
|
371
|
+
APIs missing from released empyrical versions. Returns are decimals; PROFIT,
|
|
372
|
+
PROFIT_Y, MDD and the other legacy return fields remain percentage values.
|
|
373
|
+
Undefined ratios are NaN. DATE_REGION is (peak, trough, recovery), with None
|
|
374
|
+
for no drawdown or unavailable endpoints. TradeAnalyzer, under any name,
|
|
375
|
+
supplies net win/loss counts; without it nonempty trade statistics are NaN.
|
|
376
|
+
"""
|
|
377
|
+
from .tradeanalyzer import TradeAnalyzer
|
|
378
|
+
|
|
379
|
+
strategy_returns = _daily_returns(returns, "return")
|
|
380
|
+
benchmark = _daily_returns(benchmark_returns, "returns")
|
|
381
|
+
paired = pd.concat([strategy_returns, benchmark], axis=1, join="inner").dropna()
|
|
382
|
+
active = paired["return"] - paired["returns"]
|
|
383
|
+
nan = float("nan")
|
|
384
|
+
annualizer = math.sqrt(252)
|
|
385
|
+
|
|
386
|
+
def cumulative(series):
|
|
387
|
+
return float((1 + series).prod() - 1) if not series.empty else nan
|
|
388
|
+
|
|
389
|
+
def sharpe(series):
|
|
390
|
+
return _ratio(float(series.mean()) * annualizer, float(series.std(ddof=1)))
|
|
391
|
+
|
|
392
|
+
profit = cumulative(strategy_returns)
|
|
393
|
+
annual_return = (
|
|
394
|
+
(1 + profit) ** (252.0 / len(strategy_returns)) - 1
|
|
395
|
+
if not strategy_returns.empty
|
|
396
|
+
else nan
|
|
397
|
+
)
|
|
398
|
+
baseline_profit = cumulative(paired["returns"])
|
|
399
|
+
maximum_drawdown, region = _drawdown_details(strategy_returns)
|
|
400
|
+
active_drawdown, _ = _drawdown_details(active)
|
|
401
|
+
variance = paired["returns"].var(ddof=1)
|
|
402
|
+
beta = paired["return"].cov(paired["returns"]) / variance if variance > 0 else nan
|
|
403
|
+
alpha = (1 + (paired["return"] - beta * paired["returns"]).mean()) ** 252 - 1
|
|
404
|
+
downside = (
|
|
405
|
+
float(np.sqrt(np.mean(np.minimum(strategy_returns.to_numpy(), 0.0) ** 2)))
|
|
406
|
+
if not strategy_returns.empty
|
|
407
|
+
else nan
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
analysis = None
|
|
411
|
+
if results:
|
|
412
|
+
for analyzer in results[0].analyzers:
|
|
413
|
+
if isinstance(analyzer, TradeAnalyzer):
|
|
414
|
+
analysis = analyzer.get_analysis()
|
|
415
|
+
break
|
|
416
|
+
win_num = loss_num = win_average = loss_average = nan
|
|
417
|
+
if analysis is not None:
|
|
418
|
+
won, lost = analysis.get("won", {}), analysis.get("lost", {})
|
|
419
|
+
win_num, loss_num = won.get("total", 0), lost.get("total", 0)
|
|
420
|
+
win_average = won.get("pnl", {}).get("average", nan)
|
|
421
|
+
loss_average = lost.get("pnl", {}).get("average", nan)
|
|
422
|
+
elif not self.get_pf_items()[2].shape[0]:
|
|
423
|
+
win_num = loss_num = 0
|
|
424
|
+
total = win_num + loss_num
|
|
425
|
+
win_ratio = win_num / total if total > 0 else (0.0 if total == 0 else nan)
|
|
426
|
+
today = datetime.date.today()
|
|
427
|
+
return {
|
|
428
|
+
"PROFIT": round(profit * 100, 2),
|
|
429
|
+
"PROFIT_Y": round(annual_return * 100, 2),
|
|
430
|
+
"SUPERIOR_PROFIT": round((profit - baseline_profit) * 100, 2),
|
|
431
|
+
"BASE_PROFIT": round(baseline_profit * 100, 2),
|
|
432
|
+
"ALPHA": round(float(alpha), 4),
|
|
433
|
+
"Beta": round(float(beta), 4),
|
|
434
|
+
"SHARPE_RATIO": round(sharpe(strategy_returns), 4),
|
|
435
|
+
"WIN_RATIO": round(win_ratio, 4),
|
|
436
|
+
"RRR": round(_ratio(win_average * 100, abs(loss_average)), 4),
|
|
437
|
+
"MDD": round(-maximum_drawdown * 100, 2),
|
|
438
|
+
"SORTINO_RATIO": round(
|
|
439
|
+
_ratio(float(strategy_returns.mean()) * annualizer, downside), 4
|
|
440
|
+
),
|
|
441
|
+
"DAILY_SUPERIOR_PROFIT": round(float(active.mean()) * 100, 2),
|
|
442
|
+
"MDD_SUPERIOR_PROFIT": round(active_drawdown * 100, 2),
|
|
443
|
+
"SP_SHARPE_RATIO": round(sharpe(active), 4),
|
|
444
|
+
"DAILY_WIN_RATIO": round(win_ratio, 4),
|
|
445
|
+
"WIN_NUM": win_num,
|
|
446
|
+
"LOSS_NUM": loss_num,
|
|
447
|
+
"INFO_RATIO": round(sharpe(active), 4),
|
|
448
|
+
"VIX": round(float(strategy_returns.std(ddof=1)) * annualizer, 4),
|
|
449
|
+
"BASE_VIX": round(float(paired["returns"].std(ddof=1)) * annualizer, 4),
|
|
450
|
+
"DATE_REGION": region,
|
|
451
|
+
"CREATE_DATE": today,
|
|
452
|
+
"CREATE_USER": current_user_name,
|
|
453
|
+
"UPDATE_DATE": today,
|
|
454
|
+
"UPDATE_USER": current_user_name,
|
|
455
|
+
"D_FLAG": 0,
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
def get_format_results(self, results, benchmark_returns, symbol_name, current_user_name):
|
|
459
|
+
"""Return (performance dict, transaction DataFrame) using master's signature.
|
|
460
|
+
|
|
461
|
+
See _get_performance_indicators and _get_trade_info for percentage units,
|
|
462
|
+
unavailable metrics/fees and the position-based replacement of row pairing.
|
|
463
|
+
Input return series and the underlying get_pf_items result are not mutated.
|
|
464
|
+
"""
|
|
465
|
+
returns, _, transactions, _ = self.get_pf_items()
|
|
466
|
+
performance = self._get_performance_indicators(
|
|
467
|
+
results, returns, benchmark_returns, current_user_name
|
|
468
|
+
)
|
|
469
|
+
trades = self._get_trade_info(transactions, symbol_name, current_user_name, pair_num=1)
|
|
470
|
+
return performance, trades
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Returns Analyzer Module - Return statistics calculation.
|
|
3
|
+
|
|
4
|
+
This module provides the Returns analyzer for calculating total, average,
|
|
5
|
+
compound, and annualized returns using a logarithmic approach.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
Returns: Analyzer that calculates return statistics.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> cerebro = bt.Cerebro()
|
|
12
|
+
>>> cerebro.addanalyzer(bt.analyzers.Returns, _name='ret')
|
|
13
|
+
>>> results = cerebro.run()
|
|
14
|
+
>>> print(results[0].analyzers.ret.get_analysis())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import math
|
|
18
|
+
|
|
19
|
+
from ..analyzer import TimeFrameAnalyzerBase
|
|
20
|
+
from ..dataseries import TimeFrame
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Calculate total, average, compound and annualized returns using logarithmic method
|
|
24
|
+
class Returns(TimeFrameAnalyzerBase):
|
|
25
|
+
"""
|
|
26
|
+
Total, Average, Compound and Annualized Returns calculated using a
|
|
27
|
+
logarithmic approach
|
|
28
|
+
|
|
29
|
+
See:
|
|
30
|
+
|
|
31
|
+
- https://www.crystalbull.com/sharpe-ratio-better-with-log-returns/
|
|
32
|
+
|
|
33
|
+
Params:
|
|
34
|
+
|
|
35
|
+
- ``timeframe`` (default: ``None``)
|
|
36
|
+
|
|
37
|
+
If ``None`` the `timeframe` of the first data in the system will be
|
|
38
|
+
used
|
|
39
|
+
|
|
40
|
+
Pass ``TimeFrame.NoTimeFrame`` to consider the entire dataset with no
|
|
41
|
+
time constraints
|
|
42
|
+
|
|
43
|
+
- ``compression`` (default: ``None``)
|
|
44
|
+
|
|
45
|
+
Only used for sub-day timeframes to, for example, work on an hourly
|
|
46
|
+
timeframe by specifying "TimeFrame.Minutes" and 60 as compression
|
|
47
|
+
|
|
48
|
+
If `None`, then the compression of the first data in the system will be
|
|
49
|
+
used
|
|
50
|
+
|
|
51
|
+
- ``tann`` (default: ``None``)
|
|
52
|
+
|
|
53
|
+
Number of periods to use for the annualization (normalization)
|
|
54
|
+
|
|
55
|
+
namely:
|
|
56
|
+
|
|
57
|
+
- ``days: 252``
|
|
58
|
+
- ``weeks: 52``
|
|
59
|
+
- ``months: 12``
|
|
60
|
+
- ``years: 1``
|
|
61
|
+
|
|
62
|
+
- ``fund`` (default: ``None``)
|
|
63
|
+
|
|
64
|
+
If `None`, the actual mode of the broker (fundmode - True/False) will
|
|
65
|
+
be autodetected to decide if the returns are based on the total net
|
|
66
|
+
asset value or on the fund value. See ``set_fundmode`` in the broker
|
|
67
|
+
documentation
|
|
68
|
+
|
|
69
|
+
Set it to ``True`` or ``False`` for a specific behavior
|
|
70
|
+
|
|
71
|
+
Methods:
|
|
72
|
+
|
|
73
|
+
- get_analysis
|
|
74
|
+
|
|
75
|
+
Returns a dictionary with returns as values and the datetime points for
|
|
76
|
+
each return as keys
|
|
77
|
+
|
|
78
|
+
The returned dict the following keys:
|
|
79
|
+
|
|
80
|
+
- ``rtot``: Total compound return
|
|
81
|
+
- ``ravg``: Average return for the entire period (timeframe specific)
|
|
82
|
+
- ``rnorm``: Annualized/Normalized return
|
|
83
|
+
- ``rnorm100``: Annualized/Normalized return expressed in 100%
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
# Parameters
|
|
88
|
+
params = (
|
|
89
|
+
("tann", None),
|
|
90
|
+
("fund", None),
|
|
91
|
+
)
|
|
92
|
+
# Days etc. for calculating annualization
|
|
93
|
+
_TANN = {
|
|
94
|
+
TimeFrame.Days: 252.0,
|
|
95
|
+
TimeFrame.Weeks: 52.0,
|
|
96
|
+
TimeFrame.Months: 12.0,
|
|
97
|
+
TimeFrame.Years: 1.0,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Start
|
|
101
|
+
def __init__(self, *args, **kwargs):
|
|
102
|
+
"""Initialize the Returns analyzer.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
*args: Positional arguments.
|
|
106
|
+
**kwargs: Keyword arguments for analyzer parameters.
|
|
107
|
+
"""
|
|
108
|
+
# Call parent class __init__ method to support timeframe and compression parameters
|
|
109
|
+
super().__init__(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
self._value_end = None
|
|
112
|
+
self._tcount = None
|
|
113
|
+
self._value_start = None
|
|
114
|
+
self._fundmode = None
|
|
115
|
+
|
|
116
|
+
def start(self):
|
|
117
|
+
"""Initialize the analyzer at the start of the backtest.
|
|
118
|
+
|
|
119
|
+
Records the initial portfolio value and sets the fund mode.
|
|
120
|
+
"""
|
|
121
|
+
super().start()
|
|
122
|
+
# If fund is None, _fundmode is broker's fundmode, otherwise equals fund
|
|
123
|
+
if self.p.fund is None:
|
|
124
|
+
self._fundmode = self.strategy.broker.fundmode
|
|
125
|
+
else:
|
|
126
|
+
self._fundmode = self.p.fund
|
|
127
|
+
# If fundmode is False, get value, otherwise get fundvalue
|
|
128
|
+
if not self._fundmode:
|
|
129
|
+
self._value_start = self.strategy.broker.getvalue()
|
|
130
|
+
else:
|
|
131
|
+
self._value_start = self.strategy.broker.fundvalue
|
|
132
|
+
# Count subperiods
|
|
133
|
+
self._tcount = 0
|
|
134
|
+
|
|
135
|
+
# When stopping
|
|
136
|
+
def stop(self):
|
|
137
|
+
"""Calculate and store return statistics at the end of the backtest.
|
|
138
|
+
|
|
139
|
+
Calculates:
|
|
140
|
+
- rtot: Total compound return
|
|
141
|
+
- ravg: Average return for the period
|
|
142
|
+
- rnorm: Annualized return
|
|
143
|
+
- rnorm100: Annualized return in percentage form
|
|
144
|
+
"""
|
|
145
|
+
super().stop()
|
|
146
|
+
# If fundmode is False, get value, otherwise get fundvalue
|
|
147
|
+
if not self._fundmode:
|
|
148
|
+
self._value_end = self.strategy.broker.getvalue()
|
|
149
|
+
else:
|
|
150
|
+
self._value_end = self.strategy.broker.fundvalue
|
|
151
|
+
|
|
152
|
+
# Compound return
|
|
153
|
+
# rtot calculates total log returns
|
|
154
|
+
try:
|
|
155
|
+
nlrtot = self._value_end / self._value_start
|
|
156
|
+
if isinstance(nlrtot, complex) or not math.isfinite(nlrtot) or nlrtot <= 0.0:
|
|
157
|
+
rtot = float("-inf")
|
|
158
|
+
else:
|
|
159
|
+
rtot = math.log(nlrtot)
|
|
160
|
+
if not math.isfinite(rtot):
|
|
161
|
+
rtot = float("-inf")
|
|
162
|
+
except (ZeroDivisionError, TypeError, ValueError):
|
|
163
|
+
rtot = float("-inf")
|
|
164
|
+
|
|
165
|
+
self.rets["rtot"] = rtot
|
|
166
|
+
|
|
167
|
+
# Average return
|
|
168
|
+
# Calculate average return, first calculate log returns, then calculate average log returns
|
|
169
|
+
if self._tcount > 0:
|
|
170
|
+
self.rets["ravg"] = ravg = rtot / self._tcount
|
|
171
|
+
else:
|
|
172
|
+
self.rets["ravg"] = ravg = 0.0
|
|
173
|
+
|
|
174
|
+
# Annualized normalized return
|
|
175
|
+
# Calculate annualized return
|
|
176
|
+
tann = self.p.tann or self._TANN.get(self.timeframe, None)
|
|
177
|
+
if tann is None:
|
|
178
|
+
tann = self._TANN.get(self.data._timeframe, 1.0) # assign default
|
|
179
|
+
|
|
180
|
+
if ravg > float("-inf"):
|
|
181
|
+
self.rets["rnorm"] = rnorm = math.expm1(ravg * tann)
|
|
182
|
+
else:
|
|
183
|
+
self.rets["rnorm"] = rnorm = ravg
|
|
184
|
+
# Annualized return in percentage form
|
|
185
|
+
self.rets["rnorm100"] = rnorm * 100.0 # human-readable %
|
|
186
|
+
|
|
187
|
+
def on_dt_over(self):
|
|
188
|
+
"""Called when a datetime period is over.
|
|
189
|
+
|
|
190
|
+
Increments the subperiod counter.
|
|
191
|
+
"""
|
|
192
|
+
self._tcount += 1 # count the subperiod
|