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.
Files changed (238) hide show
  1. {trigerr-0.7.1/trigerr.egg-info → trigerr-0.7.2}/PKG-INFO +17 -13
  2. trigerr-0.7.2/README.md +58 -0
  3. trigerr-0.7.2/tests/test_bt_orders.py +109 -0
  4. trigerr-0.7.2/tests/test_ledger_contract.py +33 -0
  5. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/__init__.py +1 -1
  6. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/__init__.py +1 -0
  7. trigerr-0.7.2/trigerr/framework/bt_state.py +44 -0
  8. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/compiler.py +8 -0
  9. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/data_feeds.py +7 -23
  10. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/execution_core.py +114 -7
  11. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/point_in_time.py +1 -2
  12. trigerr-0.7.2/trigerr/orders/backtest.py +115 -0
  13. {trigerr-0.7.1 → trigerr-0.7.2/trigerr.egg-info}/PKG-INFO +17 -13
  14. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/SOURCES.txt +3 -0
  15. trigerr-0.7.1/README.md +0 -54
  16. trigerr-0.7.1/trigerr/orders/backtest.py +0 -56
  17. {trigerr-0.7.1 → trigerr-0.7.2}/LICENSE +0 -0
  18. {trigerr-0.7.1 → trigerr-0.7.2}/pyproject.toml +0 -0
  19. {trigerr-0.7.1 → trigerr-0.7.2}/setup.cfg +0 -0
  20. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_crypto_utils.py +0 -0
  21. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_data_market.py +0 -0
  22. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_exit_mutex.py +0 -0
  23. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_historical_client_config.py +0 -0
  24. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_live_orders_correlation.py +0 -0
  25. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_logging_utils.py +0 -0
  26. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_orders_schema.py +0 -0
  27. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_package.py +0 -0
  28. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_reference_symbol_details.py +0 -0
  29. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_schema_parity.py +0 -0
  30. {trigerr-0.7.1 → trigerr-0.7.2}/tests/test_symbols.py +0 -0
  31. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/config.py +0 -0
  32. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/crypto_utils.py +0 -0
  33. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/custom_indicators.py +0 -0
  34. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/__init__.py +0 -0
  35. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/historical.py +0 -0
  36. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/market.py +0 -0
  37. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/data/reference.py +0 -0
  38. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/candle_transforms.py +0 -0
  39. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/catalog.py +0 -0
  40. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/clock.py +0 -0
  41. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/exit_engine.py +0 -0
  42. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/expressions.py +0 -0
  43. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/instrument.py +0 -0
  44. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/position_sizing.py +0 -0
  45. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/framework/tick_sources.py +0 -0
  46. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/jobs.py +0 -0
  47. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/__init__.py +0 -0
  48. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/live.py +0 -0
  49. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/orders_utils.py +0 -0
  50. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/orders/virtual.py +0 -0
  51. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/symbols.py +0 -0
  52. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/__init__.py +0 -0
  53. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/__main__.py +0 -0
  54. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/_typing.py +0 -0
  55. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/__init__.py +0 -0
  56. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_doji.py +0 -0
  57. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_inside.py +0 -0
  58. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_pattern.py +0 -0
  59. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/cdl_z.py +0 -0
  60. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/candle/ha.py +0 -0
  61. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/core.py +0 -0
  62. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/custom.py +0 -0
  63. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/__init__.py +0 -0
  64. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/ebsw.py +0 -0
  65. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/cycle/reflex.py +0 -0
  66. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/ma.py +0 -0
  67. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/maps.py +0 -0
  68. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/__init__.py +0 -0
  69. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/ao.py +0 -0
  70. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/apo.py +0 -0
  71. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/bias.py +0 -0
  72. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/bop.py +0 -0
  73. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/brar.py +0 -0
  74. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cci.py +0 -0
  75. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cfo.py +0 -0
  76. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cg.py +0 -0
  77. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cmo.py +0 -0
  78. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/coppock.py +0 -0
  79. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/crsi.py +0 -0
  80. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/cti.py +0 -0
  81. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/dm.py +0 -0
  82. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/er.py +0 -0
  83. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/eri.py +0 -0
  84. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/exhc.py +0 -0
  85. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/fisher.py +0 -0
  86. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/inertia.py +0 -0
  87. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/kdj.py +0 -0
  88. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/kst.py +0 -0
  89. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/macd.py +0 -0
  90. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/mom.py +0 -0
  91. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/pgo.py +0 -0
  92. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/ppo.py +0 -0
  93. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/psl.py +0 -0
  94. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/qqe.py +0 -0
  95. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/roc.py +0 -0
  96. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rsi.py +0 -0
  97. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rsx.py +0 -0
  98. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/rvgi.py +0 -0
  99. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/slope.py +0 -0
  100. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/smc.py +0 -0
  101. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/smi.py +0 -0
  102. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze.py +0 -0
  103. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze_pro.py +0 -0
  104. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stc.py +0 -0
  105. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stoch.py +0 -0
  106. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stochf.py +0 -0
  107. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/stochrsi.py +0 -0
  108. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/tmo.py +0 -0
  109. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/trix.py +0 -0
  110. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/tsi.py +0 -0
  111. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/uo.py +0 -0
  112. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/momentum/willr.py +0 -0
  113. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/__init__.py +0 -0
  114. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/alligator.py +0 -0
  115. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/alma.py +0 -0
  116. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/dema.py +0 -0
  117. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ema.py +0 -0
  118. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/fwma.py +0 -0
  119. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hilo.py +0 -0
  120. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hl2.py +0 -0
  121. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hlc3.py +0 -0
  122. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hma.py +0 -0
  123. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/hwma.py +0 -0
  124. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ichimoku.py +0 -0
  125. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/jma.py +0 -0
  126. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/kama.py +0 -0
  127. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/linreg.py +0 -0
  128. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/mama.py +0 -0
  129. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/mcgd.py +0 -0
  130. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/midpoint.py +0 -0
  131. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/midprice.py +0 -0
  132. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ohlc4.py +0 -0
  133. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/pivots.py +0 -0
  134. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/pwma.py +0 -0
  135. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/rma.py +0 -0
  136. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/sinwma.py +0 -0
  137. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/sma.py +0 -0
  138. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/smma.py +0 -0
  139. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ssf.py +0 -0
  140. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/ssf3.py +0 -0
  141. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/supertrend.py +0 -0
  142. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/swma.py +0 -0
  143. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/t3.py +0 -0
  144. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/tema.py +0 -0
  145. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/trima.py +0 -0
  146. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/vidya.py +0 -0
  147. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/wcp.py +0 -0
  148. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/wma.py +0 -0
  149. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/overlap/zlma.py +0 -0
  150. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/__init__.py +0 -0
  151. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/drawdown.py +0 -0
  152. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/log_return.py +0 -0
  153. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/performance/percent_return.py +0 -0
  154. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/py.typed +0 -0
  155. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/__init__.py +0 -0
  156. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/entropy.py +0 -0
  157. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/kurtosis.py +0 -0
  158. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/mad.py +0 -0
  159. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/median.py +0 -0
  160. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/quantile.py +0 -0
  161. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/skew.py +0 -0
  162. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/stdev.py +0 -0
  163. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/tos_stdevall.py +0 -0
  164. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/variance.py +0 -0
  165. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/statistics/zscore.py +0 -0
  166. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/__init__.py +0 -0
  167. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/adx.py +0 -0
  168. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/alphatrend.py +0 -0
  169. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/amat.py +0 -0
  170. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/aroon.py +0 -0
  171. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/chop.py +0 -0
  172. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/cksp.py +0 -0
  173. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/decay.py +0 -0
  174. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/decreasing.py +0 -0
  175. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/dpo.py +0 -0
  176. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/ht_trendline.py +0 -0
  177. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/increasing.py +0 -0
  178. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/long_run.py +0 -0
  179. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/psar.py +0 -0
  180. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/qstick.py +0 -0
  181. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/rwi.py +0 -0
  182. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/short_run.py +0 -0
  183. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/trendflex.py +0 -0
  184. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/ttm_trend.py +0 -0
  185. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/vhf.py +0 -0
  186. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/vortex.py +0 -0
  187. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/trend/zigzag.py +0 -0
  188. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/__init__.py +0 -0
  189. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_candles.py +0 -0
  190. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_core.py +0 -0
  191. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_math.py +0 -0
  192. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_numba.py +0 -0
  193. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_signals.py +0 -0
  194. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_study.py +0 -0
  195. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_time.py +0 -0
  196. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/utils/_validate.py +0 -0
  197. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/__init__.py +0 -0
  198. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/aberration.py +0 -0
  199. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/accbands.py +0 -0
  200. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/atr.py +0 -0
  201. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/atrts.py +0 -0
  202. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/bbands.py +0 -0
  203. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/chandelier_exit.py +0 -0
  204. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/donchian.py +0 -0
  205. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/hwc.py +0 -0
  206. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/kc.py +0 -0
  207. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/massi.py +0 -0
  208. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/natr.py +0 -0
  209. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/pdist.py +0 -0
  210. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/rvi.py +0 -0
  211. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/thermo.py +0 -0
  212. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/true_range.py +0 -0
  213. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volatility/ui.py +0 -0
  214. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/__init__.py +0 -0
  215. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/ad.py +0 -0
  216. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/adosc.py +0 -0
  217. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/aobv.py +0 -0
  218. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/cmf.py +0 -0
  219. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/efi.py +0 -0
  220. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/eom.py +0 -0
  221. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/kvo.py +0 -0
  222. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/mfi.py +0 -0
  223. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/nvi.py +0 -0
  224. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/obv.py +0 -0
  225. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvi.py +0 -0
  226. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvo.py +0 -0
  227. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvol.py +0 -0
  228. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvr.py +0 -0
  229. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/pvt.py +0 -0
  230. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/tsv.py +0 -0
  231. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vhm.py +0 -0
  232. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vp.py +0 -0
  233. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vwap.py +0 -0
  234. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/ta/volume/vwma.py +0 -0
  235. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr/utils.py +0 -0
  236. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/dependency_links.txt +0 -0
  237. {trigerr-0.7.1 → trigerr-0.7.2}/trigerr.egg-info/requires.txt +0 -0
  238. {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.1
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 quantitative trading research and analysis.
35
- It is being developed as an open-source successor to an earlier internal
36
- package, with an initial focus on dependable offline tools: technical
37
- indicators, OHLCV utilities, market-structure analysis, P&L calculations,
38
- reports, and in-memory backtesting.
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 in its initial migration phase. The import namespace and packaging
43
- are ready; the public API is not yet stable. Network-connected data and order
44
- integrations will be redesigned as optional adapters rather than treated as
45
- part of the core package.
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
- The near-term focus is stabilizing the offline analysis core (indicators,
80
- utilities, backtesting) and reshaping the network-connected data and order
81
- integrations into optional adapters.
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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  from .config import config
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.7.2"
4
4
 
5
5
 
6
6
  def set_api_key(key):
@@ -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 for market-data kinds, or the raw stored value for
4
- collection/model kinds.
3
+ — a list of dict candles.
5
4
 
6
- Candle-kind resolvers call the SDK's own HTTP-based historical fetchers
7
- directly — pure, no Redis/Mongo. `mongo_collection`/`pickled_model` read
8
- whatever the runtime already placed on ctx (`ctx["collections"]`/
9
- `ctx["models"]`) instead of importing pymongo/pickle here — the same pattern
10
- `instrument.py` already uses for broker/symbol metadata, and what keeps this
11
- package infra-free (Decision #8: the SDK is the pure core; live/DB access is
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
- else:
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
- else:
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
- else:
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
- else:
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 (a mongo_collection without a timestamp column, a pickled model
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():