trigerr 0.7.1__tar.gz → 0.7.2__tar.gz
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.
- {trigerr-0.7.1/trigerr.egg-info → trigerr-0.7.2}/PKG-INFO +17 -13
- trigerr-0.7.2/README.md +58 -0
- trigerr-0.7.2/tests/test_bt_orders.py +109 -0
- trigerr-0.7.2/tests/test_ledger_contract.py +33 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/__init__.py +1 -1
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/__init__.py +1 -0
- trigerr-0.7.2/trigerr/framework/bt_state.py +44 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/compiler.py +8 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/data_feeds.py +7 -23
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/execution_core.py +114 -7
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/point_in_time.py +1 -2
- trigerr-0.7.2/trigerr/orders/backtest.py +115 -0
- {trigerr-0.7.1 → trigerr-0.7.2/trigerr.egg-info}/PKG-INFO +17 -13
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/SOURCES.txt +3 -0
- trigerr-0.7.1/README.md +0 -54
- trigerr-0.7.1/trigerr/orders/backtest.py +0 -56
- {trigerr-0.7.1 → trigerr-0.7.2}/LICENSE +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/pyproject.toml +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/setup.cfg +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_crypto_utils.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_data_market.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_exit_mutex.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_historical_client_config.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_live_orders_correlation.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_logging_utils.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_orders_schema.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_package.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_reference_symbol_details.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_schema_parity.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_symbols.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/config.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/crypto_utils.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/custom_indicators.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/historical.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/market.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/reference.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/candle_transforms.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/catalog.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/clock.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/exit_engine.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/expressions.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/instrument.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/position_sizing.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/tick_sources.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/jobs.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/live.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/orders_utils.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/virtual.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/symbols.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/__main__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/_typing.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_doji.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_inside.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_pattern.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_z.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/ha.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/core.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/custom.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/ebsw.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/reflex.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/ma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/maps.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/ao.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/apo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/bias.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/bop.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/brar.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cci.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cfo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cg.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cmo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/coppock.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/crsi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cti.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/dm.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/er.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/eri.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/exhc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/fisher.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/inertia.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/kdj.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/kst.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/macd.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/mom.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/pgo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/ppo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/psl.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/qqe.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/roc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rsi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rsx.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rvgi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/slope.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/smc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/smi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze_pro.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stoch.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stochf.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stochrsi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/tmo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/trix.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/tsi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/uo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/willr.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/alligator.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/alma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/dema.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ema.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/fwma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hilo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hl2.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hlc3.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hwma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ichimoku.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/jma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/kama.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/linreg.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/mama.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/mcgd.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/midpoint.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/midprice.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ohlc4.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/pivots.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/pwma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/rma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/sinwma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/sma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/smma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ssf.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ssf3.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/supertrend.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/swma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/t3.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/tema.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/trima.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/vidya.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/wcp.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/wma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/zlma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/drawdown.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/log_return.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/percent_return.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/py.typed +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/entropy.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/kurtosis.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/mad.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/median.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/quantile.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/skew.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/stdev.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/tos_stdevall.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/variance.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/zscore.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/adx.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/alphatrend.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/amat.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/aroon.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/chop.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/cksp.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/decay.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/decreasing.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/dpo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/ht_trendline.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/increasing.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/long_run.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/psar.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/qstick.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/rwi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/short_run.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/trendflex.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/ttm_trend.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/vhf.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/vortex.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/zigzag.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_candles.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_core.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_math.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_numba.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_signals.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_study.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_time.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_validate.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/aberration.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/accbands.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/atr.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/atrts.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/bbands.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/chandelier_exit.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/donchian.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/hwc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/kc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/massi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/natr.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/pdist.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/rvi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/thermo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/true_range.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/ui.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/__init__.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/ad.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/adosc.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/aobv.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/cmf.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/efi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/eom.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/kvo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/mfi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/nvi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/obv.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvi.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvo.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvol.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvr.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvt.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/tsv.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vhm.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vp.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vwap.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vwma.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/utils.py +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/dependency_links.txt +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/requires.txt +0 -0
- {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trigerr
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Python tools for quantitative trading research and analysis.
|
|
5
5
|
Author: Anurag Singh Kushwah
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,18 +31,19 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
# Trigerr
|
|
33
33
|
|
|
34
|
-
Trigerr is a Python package for
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
Trigerr is a Python package for algorithmic trading — technical indicators,
|
|
35
|
+
OHLCV utilities, market-structure analysis, P&L calculations, reports,
|
|
36
|
+
in-memory backtesting, and network-connected data and order execution
|
|
37
|
+
(historical/live market data, backtest/virtual/live order engines, a
|
|
38
|
+
canonical-AST strategy framework). It is the successor to an earlier internal
|
|
39
|
+
package.
|
|
39
40
|
|
|
40
41
|
## Status
|
|
41
42
|
|
|
42
|
-
Trigerr is
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
Trigerr is at v0.7.1 and in production use on the Trigerr white-label trading
|
|
44
|
+
platform (live, virtual and backtest order flow; live market data over Redis
|
|
45
|
+
Streams). The public API is versioned but still evolving — pin a version in
|
|
46
|
+
production and read the changelog before upgrading.
|
|
46
47
|
|
|
47
48
|
Trigerr supports Python 3.10 and newer.
|
|
48
49
|
|
|
@@ -76,9 +77,12 @@ Do not use real trading credentials in examples, tests, or committed files.
|
|
|
76
77
|
|
|
77
78
|
## Roadmap
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
Near-term work is on the platform side rather than the SDK's own shape:
|
|
81
|
+
onboarding more brokers to live order execution (Dhan, then Upstox), and
|
|
82
|
+
extending the canonical-AST strategy framework (`trigerr.framework`) to more
|
|
83
|
+
strategy families. See `PLATFORM_TARGET_ARCHITECTURE.md` in the
|
|
84
|
+
`trigerr-trading-strategies` repo for the platform-wide plan this SDK
|
|
85
|
+
implements.
|
|
82
86
|
|
|
83
87
|
## License
|
|
84
88
|
|
trigerr-0.7.2/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Trigerr
|
|
2
|
+
|
|
3
|
+
Trigerr is a Python package for algorithmic trading — technical indicators,
|
|
4
|
+
OHLCV utilities, market-structure analysis, P&L calculations, reports,
|
|
5
|
+
in-memory backtesting, and network-connected data and order execution
|
|
6
|
+
(historical/live market data, backtest/virtual/live order engines, a
|
|
7
|
+
canonical-AST strategy framework). It is the successor to an earlier internal
|
|
8
|
+
package.
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
Trigerr is at v0.7.1 and in production use on the Trigerr white-label trading
|
|
13
|
+
platform (live, virtual and backtest order flow; live market data over Redis
|
|
14
|
+
Streams). The public API is versioned but still evolving — pin a version in
|
|
15
|
+
production and read the changelog before upgrading.
|
|
16
|
+
|
|
17
|
+
Trigerr supports Python 3.10 and newer.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install trigerr
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For local development from a checkout:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
python -m pytest
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Current configuration API
|
|
33
|
+
|
|
34
|
+
The existing configuration helpers remain available while the integration
|
|
35
|
+
layer is migrated:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import trigerr
|
|
39
|
+
|
|
40
|
+
trigerr.set_api_key("your-api-key")
|
|
41
|
+
trigerr.set_data_url("https://data.example.com/")
|
|
42
|
+
trigerr.set_orders_url("https://orders.example.com/")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Do not use real trading credentials in examples, tests, or committed files.
|
|
46
|
+
|
|
47
|
+
## Roadmap
|
|
48
|
+
|
|
49
|
+
Near-term work is on the platform side rather than the SDK's own shape:
|
|
50
|
+
onboarding more brokers to live order execution (Dhan, then Upstox), and
|
|
51
|
+
extending the canonical-AST strategy framework (`trigerr.framework`) to more
|
|
52
|
+
strategy families. See `PLATFORM_TARGET_ARCHITECTURE.md` in the
|
|
53
|
+
`trigerr-trading-strategies` repo for the platform-wide plan this SDK
|
|
54
|
+
implements.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
Trigerr is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
""" The two pieces a backtest stands on: the in-memory state cursor and the bt order document.
|
|
2
|
+
|
|
3
|
+
A backtest's order must be the vt order — same keys, same stringification, read back through the same
|
|
4
|
+
orders list — because one execution core serves vt, lt and bt. It may differ only where a backtest
|
|
5
|
+
must: stamped from the replayed candle, no database, no alert, no swallowed errors.
|
|
6
|
+
"""
|
|
7
|
+
import datetime
|
|
8
|
+
import types
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
import trigerr.orders.virtual as virtual
|
|
13
|
+
import trigerr.utils as utils
|
|
14
|
+
from trigerr.framework.bt_state import memory_state_cursor
|
|
15
|
+
from trigerr.orders.backtest import place_bt_leg_order
|
|
16
|
+
from trigerr.orders.orders_utils import acquire_exit_mutex, fetch_orders_list, release_exit_mutex, renew_exit_mutex
|
|
17
|
+
|
|
18
|
+
CANDLE = {"symbol": "NIFTY_50", "close": 101.5, "timestamp": datetime.datetime(2025, 6, 2, 9, 31, 7, 500)}
|
|
19
|
+
ORDER = dict(quantity=5, quantity_left=5, position_type="LONG", transaction_type="BUY", trade_action="ENTRY",
|
|
20
|
+
user_id="u1", strategy_id="s1", request_id="r1", market="IN", exchange="NSE", venue="XNSE",
|
|
21
|
+
data_key="UPSTOX:XNSE:NIFTY_50", group_id="r1:2025-06-02:1", leg_key="SPOT", lot_size=1,
|
|
22
|
+
params={"investment": 100000, "sl_price": 70.0})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------- memory_state_cursor
|
|
26
|
+
|
|
27
|
+
def test_memory_cursor_lists_behave_like_redis_lists():
|
|
28
|
+
c = memory_state_cursor()
|
|
29
|
+
assert c.rpush("k", "a") == 1 and c.rpush("k", "b", "c") == 3
|
|
30
|
+
assert c.lrange("k", 0, -1) == ["a", "b", "c"] and c.lrange("k", 1, 1) == ["b"]
|
|
31
|
+
assert c.llen("k") == 3 and c.llen("missing") == 0 and c.lrange("missing", 0, -1) == []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_memory_cursor_strings_and_set_nx():
|
|
35
|
+
c = memory_state_cursor()
|
|
36
|
+
assert c.set("m", "t1", nx=True, ex=120) is True
|
|
37
|
+
assert c.set("m", "t2", nx=True, ex=120) is None # already held
|
|
38
|
+
assert c.get("m") == "t1" and c.get("nope") is None
|
|
39
|
+
assert c.delete("m") == 1 and c.delete("m") == 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_memory_cursor_publish_and_expire_are_harmless():
|
|
43
|
+
c = memory_state_cursor()
|
|
44
|
+
c.rpush("k", "x")
|
|
45
|
+
assert c.publish("channel", "msg") == 0 and c.expire("k", 10) is True and c.expire("none", 10) is False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_exit_mutex_helpers_work_on_the_memory_cursor():
|
|
49
|
+
c = memory_state_cursor()
|
|
50
|
+
token = acquire_exit_mutex(c, "r1")
|
|
51
|
+
assert token and acquire_exit_mutex(c, "r1", wait_s=0) is None # mutually exclusive
|
|
52
|
+
renew_exit_mutex(c, "r1", token)
|
|
53
|
+
release_exit_mutex(c, "r1", "someone-elses-token") # must not free it
|
|
54
|
+
assert acquire_exit_mutex(c, "r1", wait_s=0) is None
|
|
55
|
+
release_exit_mutex(c, "r1", token)
|
|
56
|
+
assert acquire_exit_mutex(c, "r1", wait_s=0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------- place_bt_leg_order
|
|
60
|
+
|
|
61
|
+
def _vt_order_keys(monkeypatch):
|
|
62
|
+
monkeypatch.setattr(virtual, "send_order_alert", lambda alert: None)
|
|
63
|
+
db = {"vt_orders": types.SimpleNamespace(insert_one=lambda doc: types.SimpleNamespace(inserted_id="OID1"))}
|
|
64
|
+
cursor = memory_state_cursor()
|
|
65
|
+
orders = virtual.place_vt_order(app_db_cursor=db, redis_cursor=cursor, order_candle=CANDLE, **ORDER)
|
|
66
|
+
return set(orders[-1])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_bt_order_has_exactly_the_vt_order_shape(monkeypatch):
|
|
70
|
+
vt_keys = _vt_order_keys(monkeypatch)
|
|
71
|
+
bt = place_bt_leg_order(redis_cursor=memory_state_cursor(), order_candle=CANDLE, **ORDER)[-1]
|
|
72
|
+
assert vt_keys - {"db_order_id"} == set(bt) # the only vt-only field is the database id
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_bt_order_is_stamped_from_the_candle_and_keeps_its_fields():
|
|
76
|
+
order = place_bt_leg_order(redis_cursor=memory_state_cursor(), order_candle=CANDLE, **ORDER)[-1]
|
|
77
|
+
assert order["date"] == "2025-06-02 00:00:00"
|
|
78
|
+
assert order["order_timestamp"] == "2025-06-02 09:31:07" # microseconds dropped, like vt
|
|
79
|
+
assert order["day"] == "Monday" and order["symbol"] == "NIFTY_50"
|
|
80
|
+
assert order["trigger_price"] == 101.5 # no explicit trigger -> the candle close
|
|
81
|
+
assert order["sl_price"] == 70.0 and order["investment"] == 100000 # params folded in, like vt
|
|
82
|
+
assert order["group_id"] == "r1:2025-06-02:1" and order["leg_key"] == "SPOT" and order["venue"] == "XNSE"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_bt_order_accumulates_on_the_request_orders_list():
|
|
86
|
+
c = memory_state_cursor()
|
|
87
|
+
place_bt_leg_order(redis_cursor=c, order_candle=CANDLE, **ORDER)
|
|
88
|
+
second = place_bt_leg_order(redis_cursor=c, order_candle=CANDLE, **{**ORDER, "trade_action": "EXIT", "exit_type": "T1"})
|
|
89
|
+
assert [o["trade_action"] for o in second] == ["ENTRY", "EXIT"]
|
|
90
|
+
assert [o["trade_action"] for o in fetch_orders_list(c, "r1")] == ["ENTRY", "EXIT"]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_bt_order_sends_no_alert(monkeypatch):
|
|
94
|
+
monkeypatch.setattr(utils, "send_order_alert", lambda *a, **k: (_ for _ in ()).throw(AssertionError("alert sent")))
|
|
95
|
+
monkeypatch.setattr(virtual, "send_order_alert", lambda *a, **k: (_ for _ in ()).throw(AssertionError("alert sent")))
|
|
96
|
+
place_bt_leg_order(redis_cursor=memory_state_cursor(), order_candle=CANDLE, **ORDER)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_bt_order_rejects_a_candle_without_a_datetime_timestamp():
|
|
100
|
+
with pytest.raises(TypeError, match="datetime"):
|
|
101
|
+
place_bt_leg_order(redis_cursor=memory_state_cursor(), order_candle={**CANDLE, "timestamp": "2025-06-02 09:31"},
|
|
102
|
+
**ORDER)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_bt_order_raises_when_nothing_was_stored():
|
|
106
|
+
broken = types.SimpleNamespace(rpush=lambda *a: 0, expire=lambda *a: 0, publish=lambda *a: 0,
|
|
107
|
+
lrange=lambda *a: [], llen=lambda k: 0)
|
|
108
|
+
with pytest.raises(RuntimeError, match="not stored"):
|
|
109
|
+
place_bt_leg_order(redis_cursor=broken, order_candle=CANDLE, **ORDER)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
""" The order ledger must account for every lot, not the last entry per symbol.
|
|
2
|
+
|
|
3
|
+
`check_open_orders` folds an orders list into open positions keyed by SYMBOL, and each ENTRY order overwrites the
|
|
4
|
+
previous one's fields for that symbol (quantity, quantity_left, trigger_price, group_id ...). Two entries in the
|
|
5
|
+
same instrument — pyramiding, a grid, an adjustment that re-opens a strike, two overlapping structures — therefore
|
|
6
|
+
report only the last lot, and a restart rebuilds only that one. No running strategy does this today; the
|
|
7
|
+
families on the roadmap do.
|
|
8
|
+
"""
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from trigerr.orders.orders_utils import check_open_orders
|
|
12
|
+
|
|
13
|
+
SYMBOL = "NIFTY_50_24000_CE_2026-08-27"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _entry(group, quantity, price, minute):
|
|
17
|
+
return {"symbol": SYMBOL, "trade_action": "ENTRY", "quantity": quantity, "quantity_left": quantity,
|
|
18
|
+
"trigger_price": price, "order_timestamp": f"2025-06-02 09:{minute:02d}:00", "position_type": "LONG",
|
|
19
|
+
"group_id": group, "leg_key": "CE", "data_key": f"UPSTOX:XNSE:{SYMBOL}", "venue": "XNSE", "lot_size": 25}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_one_entry_is_one_open_position():
|
|
23
|
+
open_positions = check_open_orders([_entry("r1:2025-06-02:1", 10, 100.0, 16)])
|
|
24
|
+
assert {s: p["quantity_left"] for s, p in open_positions.items()} == {SYMBOL: 10}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.xfail(strict=True, raises=AssertionError,
|
|
28
|
+
reason="S6: open positions are keyed by symbol and the second ENTRY overwrites the first, so a "
|
|
29
|
+
"second lot in the same instrument hides the first (15 open lots are reported as 5)")
|
|
30
|
+
def test_two_entries_in_the_same_instrument_are_both_open():
|
|
31
|
+
orders = [_entry("r1:2025-06-02:1", 10, 100.0, 16), _entry("r1:2025-06-02:2", 5, 110.0, 30)]
|
|
32
|
+
open_positions = check_open_orders(orders)
|
|
33
|
+
assert sum(p["quantity_left"] for p in open_positions.values()) == 15
|
|
@@ -11,6 +11,7 @@ from trigerr.framework.data_feeds import resolve_feeds
|
|
|
11
11
|
from trigerr.framework.point_in_time import truncate_feeds_to_moment
|
|
12
12
|
from trigerr.framework.clock import iterate_clock_bt, evaluate_should_trade_today
|
|
13
13
|
from trigerr.framework.tick_sources import clock_feed_ticks_live
|
|
14
|
+
from trigerr.framework.bt_state import memory_state_cursor
|
|
14
15
|
from trigerr.framework.execution_core import (
|
|
15
16
|
wait_for_entry_signal, monitor_open_position, enter_legs, place_entry_order_for_leg,
|
|
16
17
|
place_exit_order_for_leg, convert_leg_orders_to_trade, rebuild_legs_from_open_orders,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
""" The state a backtest runs against: an in-process stand-in for the tenant's state Redis.
|
|
2
|
+
|
|
3
|
+
The execution core keeps a request's orders list, its exit mutex and its pub/sub in
|
|
4
|
+
ctx["state_cursor"]. A live or virtual run hands it the real state Redis; a backtest hands it this
|
|
5
|
+
instead, so a backtest cannot read or write a real Redis key even by mistake — a stray real call
|
|
6
|
+
raises instead of reaching production. It implements only the commands the core and the
|
|
7
|
+
orders_utils helpers actually use, and nothing expires (a run is one process).
|
|
8
|
+
|
|
9
|
+
A plain dict behind closures, not a class, like the rest of the framework. """
|
|
10
|
+
|
|
11
|
+
import types
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def memory_state_cursor():
|
|
15
|
+
store = {} # key -> str (strings) or list (lists)
|
|
16
|
+
|
|
17
|
+
def rpush(key, *values):
|
|
18
|
+
store.setdefault(key, []).extend(values)
|
|
19
|
+
return len(store[key])
|
|
20
|
+
|
|
21
|
+
def lrange(key, start, stop):
|
|
22
|
+
items = store.get(key, [])
|
|
23
|
+
return list(items[start:] if stop == -1 else items[start:stop + 1])
|
|
24
|
+
|
|
25
|
+
def llen(key):
|
|
26
|
+
return len(store.get(key, []))
|
|
27
|
+
|
|
28
|
+
def set_(key, value, nx=False, ex=None):
|
|
29
|
+
if nx and key in store:
|
|
30
|
+
return None
|
|
31
|
+
store[key] = value
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def get(key):
|
|
35
|
+
value = store.get(key)
|
|
36
|
+
return value if isinstance(value, str) else None
|
|
37
|
+
|
|
38
|
+
def delete(key):
|
|
39
|
+
return 1 if store.pop(key, None) is not None else 0
|
|
40
|
+
|
|
41
|
+
return types.SimpleNamespace(
|
|
42
|
+
rpush=rpush, lrange=lrange, llen=llen, set=set_, get=get, delete=delete,
|
|
43
|
+
expire=lambda key, seconds: key in store,
|
|
44
|
+
publish=lambda channel, message: 0)
|
|
@@ -302,6 +302,14 @@ def validate(ast):
|
|
|
302
302
|
elif ast["clock"] not in ast["feeds"]:
|
|
303
303
|
errors.append(f"clock feed '{ast['clock']}' is not declared in feeds")
|
|
304
304
|
|
|
305
|
+
from trigerr.framework.data_feeds import DATA_FEED_KINDS # lazy: data_feeds imports this module
|
|
306
|
+
for name, spec in ast["feeds"].items():
|
|
307
|
+
if not isinstance(spec, dict) or "derive" in spec or "kind" not in spec:
|
|
308
|
+
continue
|
|
309
|
+
kind = spec["kind"]
|
|
310
|
+
if kind not in DATA_FEED_KINDS and not (isinstance(kind, str) and kind.startswith("$")):
|
|
311
|
+
errors.append(f"feed '{name}' has unknown kind '{kind}' (known: {', '.join(sorted(DATA_FEED_KINDS))})")
|
|
312
|
+
|
|
305
313
|
subscriptions = infer_subscriptions(ast)
|
|
306
314
|
for feed_name in subscriptions:
|
|
307
315
|
if feed_name not in ast["feeds"]:
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
""" DATA_FEED_KINDS: one resolver per data source a strategy can declare in
|
|
2
2
|
its FEEDS block. Each resolver is `fetch_historical(feed_spec, ctx) -> rows`
|
|
3
|
-
— a list of dict candles
|
|
4
|
-
collection/model kinds.
|
|
3
|
+
— a list of dict candles.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
an engine-side adapter).
|
|
5
|
+
Resolvers call the SDK's own HTTP-based historical fetchers directly — pure,
|
|
6
|
+
no Redis/Mongo, which is what keeps this package infra-free (Decision #8: the
|
|
7
|
+
SDK is the pure core; live/DB access is an engine-side adapter). There is
|
|
8
|
+
deliberately no kind that reads a Mongo collection or loads a model file by
|
|
9
|
+
name: market data comes from the Data API, and a new source is added as a
|
|
10
|
+
named kind (one resolver), never as a pass-through to storage.
|
|
13
11
|
|
|
14
12
|
`resolve_feeds` is the entry point: it resolves every declared feed — base
|
|
15
13
|
feeds via a DATA_FEED_KINDS resolver, derived feeds via a CANDLE_TRANSFORMS
|
|
@@ -76,16 +74,6 @@ def _fetch_options(feed_spec, ctx):
|
|
|
76
74
|
return _apply_declared_indicators(rows, feed_spec)
|
|
77
75
|
|
|
78
76
|
|
|
79
|
-
def _fetch_mongo_collection(feed_spec, ctx):
|
|
80
|
-
return ctx["collections"][feed_spec["name"]]
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _fetch_pickled_model(feed_spec, ctx):
|
|
84
|
-
""" A model is a single object, not a candle series — a "ref" op can't
|
|
85
|
-
read it; a native node's plugin reads ctx["feeds"][name] directly. """
|
|
86
|
-
return ctx["models"][feed_spec["path"]]
|
|
87
|
-
|
|
88
|
-
|
|
89
77
|
DATA_FEED_KINDS = {
|
|
90
78
|
"eod": {"fetch_historical": _fetch_eod,
|
|
91
79
|
"spec": {"label": "End-of-day candles", "params": ["symbol", "lookback_days", "exchange"]}},
|
|
@@ -97,10 +85,6 @@ DATA_FEED_KINDS = {
|
|
|
97
85
|
"options": {"fetch_historical": _fetch_options,
|
|
98
86
|
"spec": {"label": "Option candles",
|
|
99
87
|
"params": ["symbol", "option_type", "strike_price", "expiry", "granularity"]}},
|
|
100
|
-
"mongo_collection": {"fetch_historical": _fetch_mongo_collection,
|
|
101
|
-
"spec": {"label": "Mongo collection (e.g. nse_pre_open)", "params": ["name"]}},
|
|
102
|
-
"pickled_model": {"fetch_historical": _fetch_pickled_model,
|
|
103
|
-
"spec": {"label": "Pickled ML model", "params": ["path"]}},
|
|
104
88
|
}
|
|
105
89
|
|
|
106
90
|
|
|
@@ -28,12 +28,19 @@ is fetched live (vt/lt, via last_candle) but falls back to the clock candle
|
|
|
28
28
|
in bt, since no strike-aware historical options feed is resolved after entry
|
|
29
29
|
here (that would need historical option data fetched reactively once a
|
|
30
30
|
strike is known - out of scope; this preserves, not introduces, the original
|
|
31
|
-
harness's bt-side simplification for options legs).
|
|
31
|
+
harness's bt-side simplification for options legs).
|
|
32
|
+
|
|
33
|
+
bt is a first-class mode here, not a variant of vt: every placement site names vt, lt and bt
|
|
34
|
+
explicitly and raises on anything else, so a backtest can never fall through into a live order.
|
|
35
|
+
A backtest's ctx carries an in-memory state cursor (trigerr.framework.bt_state) and no market-data
|
|
36
|
+
cursor; its orders are stamped from the replayed candle and its trades collect in
|
|
37
|
+
ctx["bt_trades"] for the driver to persist. """
|
|
32
38
|
|
|
33
39
|
import datetime
|
|
34
40
|
|
|
35
41
|
from trigerr.orders.virtual import place_vt_order, save_vt_trade
|
|
36
42
|
from trigerr.orders.live import place_lt_order, save_lt_order, poll_order_status, save_lt_trade
|
|
43
|
+
from trigerr.orders.backtest import place_bt_leg_order
|
|
37
44
|
from trigerr.orders.orders_utils import (fetch_orders_list, convert_to_trades, check_existing_order, sequence,
|
|
38
45
|
check_open_orders, acquire_exit_mutex, release_exit_mutex, renew_exit_mutex)
|
|
39
46
|
from trigerr.data.market import ltp, last_candle
|
|
@@ -89,8 +96,8 @@ def build_leg_order_params(ctx, leg):
|
|
|
89
96
|
return params
|
|
90
97
|
|
|
91
98
|
|
|
92
|
-
def _entry_pricing_window_ok(ctx, price):
|
|
93
|
-
today = datetime.datetime.today().strftime("%A")
|
|
99
|
+
def _entry_pricing_window_ok(ctx, price, day=None):
|
|
100
|
+
today = (day or datetime.datetime.today()).strftime("%A")
|
|
94
101
|
return any(slot["start_price"] <= price <= slot["end_price"]
|
|
95
102
|
for slot in ctx["market_config"]["entry_pricing"][today])
|
|
96
103
|
|
|
@@ -130,6 +137,11 @@ def place_entry_order_for_leg(ctx, leg):
|
|
|
130
137
|
ladder, and registers it in ctx["legs"]. Returns True on success. Does
|
|
131
138
|
NOT poll for lt — enter_legs places every leg first, then reconciles
|
|
132
139
|
(defect #4's ordering fix). """
|
|
140
|
+
if ctx["mode"] == "bt":
|
|
141
|
+
return _place_bt_entry_for_leg(ctx, leg)
|
|
142
|
+
if ctx["mode"] not in ("vt", "lt"):
|
|
143
|
+
raise ValueError(f"unknown mode {ctx['mode']!r}: refusing to place an entry")
|
|
144
|
+
|
|
133
145
|
inputs = ctx["parameters"]
|
|
134
146
|
rdb_cursor = ctx["rdb_cursor"]
|
|
135
147
|
|
|
@@ -237,6 +249,60 @@ def place_entry_order_for_leg(ctx, leg):
|
|
|
237
249
|
return True
|
|
238
250
|
|
|
239
251
|
|
|
252
|
+
def _bt_clock_candle(ctx):
|
|
253
|
+
""" The candle the strategy acted on: the last row of the clock feed's point-in-time view. A
|
|
254
|
+
backtest fills at its close (PLATFORM_TARGET_ARCHITECTURE §7), and — until historical option
|
|
255
|
+
pricing exists — prices every leg off it, exactly as _refresh_leg_candles already does. """
|
|
256
|
+
rows = ctx["feeds"].get(ctx["clock_feed"])
|
|
257
|
+
if not rows:
|
|
258
|
+
raise ValueError("backtest has no clock candle to price against")
|
|
259
|
+
return rows[-1]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _place_bt_entry_for_leg(ctx, leg):
|
|
263
|
+
""" place_entry_order_for_leg's backtest twin: same sizing, same target/stop ladder, same order
|
|
264
|
+
shape — but priced off the replayed candle instead of a live tick, and stored only on the
|
|
265
|
+
in-memory state cursor. Touches no live Redis, database, broker or alert channel. """
|
|
266
|
+
inputs = ctx["parameters"]
|
|
267
|
+
candle = _bt_clock_candle(ctx)
|
|
268
|
+
order_candle = {"symbol": leg["exit_symbol"], "close": candle["close"], "timestamp": candle["timestamp"]}
|
|
269
|
+
|
|
270
|
+
if not _entry_pricing_window_ok(ctx, order_candle["close"], day=candle["timestamp"]):
|
|
271
|
+
ctx["logger"].warning(f"Entry price {order_candle['close']} outside the pricing window "
|
|
272
|
+
f"for {candle['timestamp']:%A}")
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
t1_percent = float(inputs.get("t1_percent", 0))
|
|
276
|
+
sl_percent = float(inputs.get("c1_sl_percent", inputs.get("sl_percent", 0)))
|
|
277
|
+
|
|
278
|
+
entry_price = order_candle["close"]
|
|
279
|
+
leg["entry_price"] = entry_price
|
|
280
|
+
leg.update(calculate_target_and_stoploss_prices(entry_price, leg["position_type"], t1_percent, sl_percent))
|
|
281
|
+
leg["trailing_sl"] = leg["sl_price"]
|
|
282
|
+
leg["quantity"] = _size_leg(ctx, leg, entry_price)
|
|
283
|
+
if not leg["quantity"]:
|
|
284
|
+
return False
|
|
285
|
+
leg["quantity_left"] = leg["quantity"]
|
|
286
|
+
leg["order_params"] = build_leg_order_params(ctx, leg)
|
|
287
|
+
|
|
288
|
+
orders_list = place_bt_leg_order(
|
|
289
|
+
redis_cursor=ctx["state_cursor"], order_candle=order_candle,
|
|
290
|
+
quantity=leg["quantity"], quantity_left=leg["quantity"], position_type=leg["position_type"],
|
|
291
|
+
transaction_type=leg["transaction_type"], order_type=inputs.get("order_type", "MARKET"),
|
|
292
|
+
exit_type=None, trade_action="ENTRY", lot_size=leg["lot_size"],
|
|
293
|
+
user_id=ctx["user_id"], strategy_id=ctx["strategy_id"], request_id=ctx["request_id"],
|
|
294
|
+
market=ctx["market"], market_type=ctx["market_type"], exchange=leg["order_exchange"],
|
|
295
|
+
params=leg["order_params"], data_key=leg["data_key"], venue=ctx["venue"],
|
|
296
|
+
group_id=leg.get("group_id"), leg_key=leg["leg_key"])
|
|
297
|
+
leg["entry_time"] = _as_datetime(orders_list[-1]["order_timestamp"])
|
|
298
|
+
leg["tradingsymbol"] = orders_list[-1]["symbol"]
|
|
299
|
+
ctx["orders_list"] = orders_list
|
|
300
|
+
|
|
301
|
+
ctx["legs"][leg["leg_key"]] = leg
|
|
302
|
+
ctx["entry_spot"] = ctx.get("spot_price", leg["entry_price"])
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
|
|
240
306
|
def _unwind_filled_legs(ctx, filled_legs):
|
|
241
307
|
""" Immediately market-exits every already-filled leg when a sibling leg
|
|
242
308
|
in the same multi-leg entry failed, instead of leaving a naked position
|
|
@@ -278,7 +344,20 @@ def _unwind_filled_legs(ctx, filled_legs):
|
|
|
278
344
|
market_type=ctx["market_type"], exchange=leg["order_exchange"], params=leg.get("order_params"),
|
|
279
345
|
data_key=leg["data_key"], venue=ctx["venue"], group_id=leg.get("group_id"), leg_key=leg["leg_key"])
|
|
280
346
|
msg = f"Unwound {leg['leg_key']} (vt, MANUAL) after a sibling leg failed to enter.{price_note}"
|
|
281
|
-
|
|
347
|
+
elif ctx["mode"] == "bt":
|
|
348
|
+
candle = _bt_clock_candle(ctx)
|
|
349
|
+
ctx["orders_list"] = place_bt_leg_order(
|
|
350
|
+
redis_cursor=ctx["state_cursor"],
|
|
351
|
+
order_candle={"symbol": leg["tradingsymbol"], "timestamp": candle["timestamp"],
|
|
352
|
+
"close": candle["close"]},
|
|
353
|
+
quantity=leg["quantity_left"], quantity_left=0, position_type=leg["position_type"],
|
|
354
|
+
transaction_type=exit_transaction, order_type="MARKET", exit_type="MANUAL",
|
|
355
|
+
trade_action="EXIT", lot_size=leg["lot_size"], user_id=ctx["user_id"],
|
|
356
|
+
strategy_id=ctx["strategy_id"], request_id=ctx["request_id"], market=ctx["market"],
|
|
357
|
+
market_type=ctx["market_type"], exchange=leg["order_exchange"], params=leg.get("order_params"),
|
|
358
|
+
data_key=leg["data_key"], venue=ctx["venue"], group_id=leg.get("group_id"), leg_key=leg["leg_key"])
|
|
359
|
+
msg = f"Unwound {leg['leg_key']} (bt, MANUAL) after a sibling leg failed to enter."
|
|
360
|
+
elif ctx["mode"] == "lt":
|
|
282
361
|
unwind_status, unwind_response = place_lt_order(
|
|
283
362
|
symbol=leg["tradingsymbol"], exchange=leg["order_exchange"], quantity=leg["quantity_left"],
|
|
284
363
|
transaction_type=exit_transaction, order_type="MARKET", lot_size=leg["lot_size"],
|
|
@@ -317,6 +396,8 @@ def _unwind_filled_legs(ctx, filled_legs):
|
|
|
317
396
|
broker=unwind_response.get("broker"), broker_symbol=unwind_response.get("broker_symbol"))
|
|
318
397
|
ctx["orders_list"] = orders_list
|
|
319
398
|
msg = f"Unwound {leg['leg_key']} ({leg['tradingsymbol']}) (lt, MANUAL) after a sibling leg failed to enter."
|
|
399
|
+
else:
|
|
400
|
+
raise ValueError(f"unknown mode {ctx['mode']!r}: refusing to place an unwind order")
|
|
320
401
|
_alert(ctx, msg, "Leg Unwind")
|
|
321
402
|
finally:
|
|
322
403
|
release_exit_mutex(ctx["state_cursor"], ctx["request_id"], token)
|
|
@@ -479,8 +560,12 @@ def convert_leg_orders_to_trade(ctx, record_manual=False):
|
|
|
479
560
|
trade["combined_pnl"] = net
|
|
480
561
|
if ctx["mode"] == "vt":
|
|
481
562
|
save_vt_trade(app_db_cursor=ctx["app_db_cursor"], redis_cursor=ctx["state_cursor"], trade_dict=trade)
|
|
482
|
-
|
|
563
|
+
elif ctx["mode"] == "bt":
|
|
564
|
+
ctx.setdefault("bt_trades", []).append(trade) # in memory; the driver persists them once
|
|
565
|
+
elif ctx["mode"] == "lt":
|
|
483
566
|
save_lt_trade(app_db_cursor=ctx["app_db_cursor"], redis_cursor=ctx["state_cursor"], trade_dict=trade)
|
|
567
|
+
else:
|
|
568
|
+
raise ValueError(f"unknown mode {ctx['mode']!r}: refusing to record a trade")
|
|
484
569
|
|
|
485
570
|
|
|
486
571
|
def place_exit_order_for_leg(ctx, leg, exit_type, candle):
|
|
@@ -511,7 +596,19 @@ def place_exit_order_for_leg(ctx, leg, exit_type, candle):
|
|
|
511
596
|
market=ctx["market"], market_type=ctx["market_type"], exchange=leg["order_exchange"],
|
|
512
597
|
data_key=leg["data_key"], venue=ctx["venue"], group_id=leg.get("group_id"), leg_key=leg["leg_key"])
|
|
513
598
|
|
|
514
|
-
|
|
599
|
+
elif ctx["mode"] == "bt":
|
|
600
|
+
leg["quantity_left"] -= exit_quantity
|
|
601
|
+
ctx["orders_list"] = place_bt_leg_order(
|
|
602
|
+
redis_cursor=ctx["state_cursor"], order_candle={**candle, "symbol": leg["tradingsymbol"]},
|
|
603
|
+
position_type=leg["position_type"], quantity=exit_quantity, quantity_left=leg["quantity_left"],
|
|
604
|
+
transaction_type=exit_transaction, order_type=inputs.get("order_type", "MARKET"),
|
|
605
|
+
exit_type=exit_type, params=leg["order_params"], lot_size=leg["lot_size"], trade_action="EXIT",
|
|
606
|
+
trigger_price=_exit_trigger_price(ctx, leg, exit_type, candle),
|
|
607
|
+
user_id=ctx["user_id"], strategy_id=ctx["strategy_id"], request_id=ctx["request_id"],
|
|
608
|
+
market=ctx["market"], market_type=ctx["market_type"], exchange=leg["order_exchange"],
|
|
609
|
+
data_key=leg["data_key"], venue=ctx["venue"], group_id=leg.get("group_id"), leg_key=leg["leg_key"])
|
|
610
|
+
|
|
611
|
+
elif ctx["mode"] == "lt":
|
|
515
612
|
order_status, lt_response = place_lt_order(
|
|
516
613
|
symbol=leg["tradingsymbol"], exchange=leg["order_exchange"], quantity=exit_quantity,
|
|
517
614
|
transaction_type=exit_transaction, order_type="MARKET", lot_size=leg["lot_size"],
|
|
@@ -549,6 +646,9 @@ def place_exit_order_for_leg(ctx, leg, exit_type, candle):
|
|
|
549
646
|
broker=lt_response.get("broker"), broker_symbol=lt_response.get("broker_symbol"))
|
|
550
647
|
ctx["orders_list"] = orders_list
|
|
551
648
|
|
|
649
|
+
else:
|
|
650
|
+
raise ValueError(f"unknown mode {ctx['mode']!r}: refusing to place an exit order")
|
|
651
|
+
|
|
552
652
|
if all(l["quantity_left"] == 0 for l in ctx["legs"].values()):
|
|
553
653
|
convert_leg_orders_to_trade(ctx)
|
|
554
654
|
|
|
@@ -600,7 +700,11 @@ def manual_exit_legs(ctx, prices, leg_keys=None):
|
|
|
600
700
|
data_key=leg["data_key"], venue=ctx["venue"], group_id=leg.get("group_id"), leg_key=leg_key)
|
|
601
701
|
exited.append(leg_key)
|
|
602
702
|
|
|
603
|
-
|
|
703
|
+
elif ctx["mode"] == "bt":
|
|
704
|
+
refused[leg_key] = "manual exit is not available in a backtest"
|
|
705
|
+
continue
|
|
706
|
+
|
|
707
|
+
elif ctx["mode"] == "lt":
|
|
604
708
|
inputs = ctx["parameters"]
|
|
605
709
|
order_status, lt_response = place_lt_order(
|
|
606
710
|
symbol=leg["tradingsymbol"], exchange=leg["order_exchange"], quantity=exit_quantity,
|
|
@@ -642,6 +746,9 @@ def manual_exit_legs(ctx, prices, leg_keys=None):
|
|
|
642
746
|
ctx["orders_list"] = orders_list
|
|
643
747
|
exited.append(leg_key)
|
|
644
748
|
|
|
749
|
+
else:
|
|
750
|
+
raise ValueError(f"unknown mode {ctx['mode']!r}: refusing to place a manual exit")
|
|
751
|
+
|
|
645
752
|
if ctx["legs"] and all(l["quantity_left"] == 0 for l in ctx["legs"].values()):
|
|
646
753
|
convert_leg_orders_to_trade(ctx, record_manual=True)
|
|
647
754
|
|
|
@@ -10,8 +10,7 @@ live feed only ever holds ticks that have already arrived. """
|
|
|
10
10
|
def truncate_feeds_to_moment(feeds, moment):
|
|
11
11
|
""" Returns a new {name: rows} with every timestamped feed's rows
|
|
12
12
|
filtered to timestamp <= moment. A feed whose rows aren't timestamped
|
|
13
|
-
candles
|
|
14
|
-
object) passes through unchanged — truncation only applies where "point
|
|
13
|
+
candles passes through unchanged — truncation only applies where "point
|
|
15
14
|
in time" is a meaningful concept. """
|
|
16
15
|
truncated = {}
|
|
17
16
|
for name, rows in feeds.items():
|