trigerr 0.7.0__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 (239) hide show
  1. {trigerr-0.7.0/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_exit_mutex.py +183 -0
  5. trigerr-0.7.2/tests/test_ledger_contract.py +33 -0
  6. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_live_orders_correlation.py +56 -0
  7. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/__init__.py +1 -1
  8. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/__init__.py +1 -0
  9. trigerr-0.7.2/trigerr/framework/bt_state.py +44 -0
  10. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/compiler.py +8 -0
  11. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/data_feeds.py +7 -23
  12. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/execution_core.py +203 -39
  13. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/point_in_time.py +1 -2
  14. trigerr-0.7.2/trigerr/orders/backtest.py +115 -0
  15. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/orders/live.py +35 -10
  16. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/orders/orders_utils.py +17 -0
  17. {trigerr-0.7.0 → trigerr-0.7.2/trigerr.egg-info}/PKG-INFO +17 -13
  18. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr.egg-info/SOURCES.txt +3 -0
  19. trigerr-0.7.0/README.md +0 -54
  20. trigerr-0.7.0/tests/test_exit_mutex.py +0 -100
  21. trigerr-0.7.0/trigerr/orders/backtest.py +0 -56
  22. {trigerr-0.7.0 → trigerr-0.7.2}/LICENSE +0 -0
  23. {trigerr-0.7.0 → trigerr-0.7.2}/pyproject.toml +0 -0
  24. {trigerr-0.7.0 → trigerr-0.7.2}/setup.cfg +0 -0
  25. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_crypto_utils.py +0 -0
  26. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_data_market.py +0 -0
  27. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_historical_client_config.py +0 -0
  28. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_logging_utils.py +0 -0
  29. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_orders_schema.py +0 -0
  30. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_package.py +0 -0
  31. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_reference_symbol_details.py +0 -0
  32. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_schema_parity.py +0 -0
  33. {trigerr-0.7.0 → trigerr-0.7.2}/tests/test_symbols.py +0 -0
  34. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/config.py +0 -0
  35. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/crypto_utils.py +0 -0
  36. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/custom_indicators.py +0 -0
  37. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/data/__init__.py +0 -0
  38. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/data/historical.py +0 -0
  39. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/data/market.py +0 -0
  40. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/data/reference.py +0 -0
  41. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/candle_transforms.py +0 -0
  42. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/catalog.py +0 -0
  43. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/clock.py +0 -0
  44. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/exit_engine.py +0 -0
  45. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/expressions.py +0 -0
  46. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/instrument.py +0 -0
  47. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/position_sizing.py +0 -0
  48. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/framework/tick_sources.py +0 -0
  49. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/jobs.py +0 -0
  50. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/orders/__init__.py +0 -0
  51. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/orders/virtual.py +0 -0
  52. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/symbols.py +0 -0
  53. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/__init__.py +0 -0
  54. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/__main__.py +0 -0
  55. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/_typing.py +0 -0
  56. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/__init__.py +0 -0
  57. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/cdl_doji.py +0 -0
  58. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/cdl_inside.py +0 -0
  59. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/cdl_pattern.py +0 -0
  60. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/cdl_z.py +0 -0
  61. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/candle/ha.py +0 -0
  62. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/core.py +0 -0
  63. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/custom.py +0 -0
  64. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/cycle/__init__.py +0 -0
  65. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/cycle/ebsw.py +0 -0
  66. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/cycle/reflex.py +0 -0
  67. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/ma.py +0 -0
  68. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/maps.py +0 -0
  69. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/__init__.py +0 -0
  70. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/ao.py +0 -0
  71. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/apo.py +0 -0
  72. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/bias.py +0 -0
  73. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/bop.py +0 -0
  74. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/brar.py +0 -0
  75. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/cci.py +0 -0
  76. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/cfo.py +0 -0
  77. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/cg.py +0 -0
  78. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/cmo.py +0 -0
  79. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/coppock.py +0 -0
  80. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/crsi.py +0 -0
  81. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/cti.py +0 -0
  82. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/dm.py +0 -0
  83. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/er.py +0 -0
  84. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/eri.py +0 -0
  85. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/exhc.py +0 -0
  86. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/fisher.py +0 -0
  87. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/inertia.py +0 -0
  88. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/kdj.py +0 -0
  89. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/kst.py +0 -0
  90. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/macd.py +0 -0
  91. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/mom.py +0 -0
  92. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/pgo.py +0 -0
  93. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/ppo.py +0 -0
  94. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/psl.py +0 -0
  95. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/qqe.py +0 -0
  96. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/roc.py +0 -0
  97. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/rsi.py +0 -0
  98. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/rsx.py +0 -0
  99. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/rvgi.py +0 -0
  100. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/slope.py +0 -0
  101. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/smc.py +0 -0
  102. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/smi.py +0 -0
  103. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze.py +0 -0
  104. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/squeeze_pro.py +0 -0
  105. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/stc.py +0 -0
  106. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/stoch.py +0 -0
  107. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/stochf.py +0 -0
  108. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/stochrsi.py +0 -0
  109. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/tmo.py +0 -0
  110. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/trix.py +0 -0
  111. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/tsi.py +0 -0
  112. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/uo.py +0 -0
  113. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/momentum/willr.py +0 -0
  114. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/__init__.py +0 -0
  115. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/alligator.py +0 -0
  116. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/alma.py +0 -0
  117. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/dema.py +0 -0
  118. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/ema.py +0 -0
  119. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/fwma.py +0 -0
  120. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/hilo.py +0 -0
  121. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/hl2.py +0 -0
  122. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/hlc3.py +0 -0
  123. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/hma.py +0 -0
  124. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/hwma.py +0 -0
  125. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/ichimoku.py +0 -0
  126. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/jma.py +0 -0
  127. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/kama.py +0 -0
  128. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/linreg.py +0 -0
  129. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/mama.py +0 -0
  130. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/mcgd.py +0 -0
  131. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/midpoint.py +0 -0
  132. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/midprice.py +0 -0
  133. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/ohlc4.py +0 -0
  134. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/pivots.py +0 -0
  135. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/pwma.py +0 -0
  136. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/rma.py +0 -0
  137. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/sinwma.py +0 -0
  138. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/sma.py +0 -0
  139. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/smma.py +0 -0
  140. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/ssf.py +0 -0
  141. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/ssf3.py +0 -0
  142. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/supertrend.py +0 -0
  143. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/swma.py +0 -0
  144. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/t3.py +0 -0
  145. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/tema.py +0 -0
  146. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/trima.py +0 -0
  147. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/vidya.py +0 -0
  148. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/wcp.py +0 -0
  149. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/wma.py +0 -0
  150. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/overlap/zlma.py +0 -0
  151. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/performance/__init__.py +0 -0
  152. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/performance/drawdown.py +0 -0
  153. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/performance/log_return.py +0 -0
  154. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/performance/percent_return.py +0 -0
  155. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/py.typed +0 -0
  156. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/__init__.py +0 -0
  157. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/entropy.py +0 -0
  158. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/kurtosis.py +0 -0
  159. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/mad.py +0 -0
  160. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/median.py +0 -0
  161. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/quantile.py +0 -0
  162. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/skew.py +0 -0
  163. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/stdev.py +0 -0
  164. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/tos_stdevall.py +0 -0
  165. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/variance.py +0 -0
  166. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/statistics/zscore.py +0 -0
  167. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/__init__.py +0 -0
  168. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/adx.py +0 -0
  169. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/alphatrend.py +0 -0
  170. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/amat.py +0 -0
  171. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/aroon.py +0 -0
  172. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/chop.py +0 -0
  173. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/cksp.py +0 -0
  174. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/decay.py +0 -0
  175. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/decreasing.py +0 -0
  176. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/dpo.py +0 -0
  177. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/ht_trendline.py +0 -0
  178. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/increasing.py +0 -0
  179. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/long_run.py +0 -0
  180. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/psar.py +0 -0
  181. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/qstick.py +0 -0
  182. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/rwi.py +0 -0
  183. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/short_run.py +0 -0
  184. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/trendflex.py +0 -0
  185. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/ttm_trend.py +0 -0
  186. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/vhf.py +0 -0
  187. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/vortex.py +0 -0
  188. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/trend/zigzag.py +0 -0
  189. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/__init__.py +0 -0
  190. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_candles.py +0 -0
  191. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_core.py +0 -0
  192. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_math.py +0 -0
  193. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_numba.py +0 -0
  194. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_signals.py +0 -0
  195. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_study.py +0 -0
  196. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_time.py +0 -0
  197. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/utils/_validate.py +0 -0
  198. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/__init__.py +0 -0
  199. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/aberration.py +0 -0
  200. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/accbands.py +0 -0
  201. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/atr.py +0 -0
  202. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/atrts.py +0 -0
  203. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/bbands.py +0 -0
  204. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/chandelier_exit.py +0 -0
  205. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/donchian.py +0 -0
  206. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/hwc.py +0 -0
  207. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/kc.py +0 -0
  208. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/massi.py +0 -0
  209. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/natr.py +0 -0
  210. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/pdist.py +0 -0
  211. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/rvi.py +0 -0
  212. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/thermo.py +0 -0
  213. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/true_range.py +0 -0
  214. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volatility/ui.py +0 -0
  215. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/__init__.py +0 -0
  216. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/ad.py +0 -0
  217. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/adosc.py +0 -0
  218. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/aobv.py +0 -0
  219. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/cmf.py +0 -0
  220. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/efi.py +0 -0
  221. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/eom.py +0 -0
  222. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/kvo.py +0 -0
  223. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/mfi.py +0 -0
  224. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/nvi.py +0 -0
  225. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/obv.py +0 -0
  226. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/pvi.py +0 -0
  227. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/pvo.py +0 -0
  228. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/pvol.py +0 -0
  229. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/pvr.py +0 -0
  230. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/pvt.py +0 -0
  231. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/tsv.py +0 -0
  232. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/vhm.py +0 -0
  233. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/vp.py +0 -0
  234. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/vwap.py +0 -0
  235. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/ta/volume/vwma.py +0 -0
  236. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr/utils.py +0 -0
  237. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr.egg-info/dependency_links.txt +0 -0
  238. {trigerr-0.7.0 → trigerr-0.7.2}/trigerr.egg-info/requires.txt +0 -0
  239. {trigerr-0.7.0 → 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.0
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,183 @@
1
+ """ Task B3 (spec §8.3 manual exits): the per-request exit mutex that
2
+ serializes everyone who places exit orders for a request — a manual exit
3
+ (OMS) and a running strategy's own exits — so a leg is never exited twice.
4
+
5
+ Offline: a tiny in-memory fake Redis (SET NX EX / GET / DELETE), no network.
6
+ """
7
+ import time
8
+
9
+ from trigerr.orders import live
10
+ from trigerr.orders.orders_utils import (acquire_exit_mutex, release_exit_mutex, renew_exit_mutex,
11
+ EXIT_MUTEX_TTL_S)
12
+
13
+
14
+ class FakeRedis:
15
+ """ SET NX EX / GET / DELETE / EXPIRE with real expiry against a settable
16
+ clock (self.now), so a test can let time pass past the mutex TTL. """
17
+ def __init__(self):
18
+ self._kv = {}
19
+ self._expires = {}
20
+ self.now = 0.0
21
+
22
+ def _alive(self, key):
23
+ if key in self._expires and self.now >= self._expires[key]:
24
+ self._kv.pop(key, None)
25
+ self._expires.pop(key, None)
26
+ return key in self._kv
27
+
28
+ def set(self, key, value, nx=False, ex=None):
29
+ if nx and self._alive(key):
30
+ return None
31
+ self._kv[key] = value
32
+ if ex is not None:
33
+ self._expires[key] = self.now + ex
34
+ return True
35
+
36
+ def get(self, key):
37
+ return self._kv.get(key) if self._alive(key) else None
38
+
39
+ def delete(self, key):
40
+ self._expires.pop(key, None)
41
+ return self._kv.pop(key, None) is not None
42
+
43
+ def expire(self, key, seconds):
44
+ if not self._alive(key):
45
+ return False
46
+ self._expires[key] = self.now + seconds
47
+ return True
48
+
49
+
50
+ def test_acquire_returns_a_token_when_free():
51
+ redis_cursor = FakeRedis()
52
+ token = acquire_exit_mutex(redis_cursor, "r1")
53
+ assert token is not None
54
+ assert redis_cursor.get("r1:exit_mutex") == token
55
+
56
+
57
+ def test_acquire_waits_then_succeeds_once_the_holder_releases(monkeypatch):
58
+ redis_cursor = FakeRedis()
59
+ sleeps = []
60
+ monkeypatch.setattr(time, "sleep", lambda s: sleeps.append(s))
61
+
62
+ first_token = acquire_exit_mutex(redis_cursor, "r1")
63
+
64
+ # The real SET NX fails while the first holder has it; make the first
65
+ # retry fail, then release before the second retry succeeds.
66
+ attempts = {"n": 0}
67
+ real_set = redis_cursor.set
68
+
69
+ def flaky_set(key, value, nx=False, ex=None):
70
+ attempts["n"] += 1
71
+ if attempts["n"] == 1:
72
+ return None # still held on the first retry
73
+ redis_cursor.delete("r1:exit_mutex") # the first holder released it
74
+ return real_set(key, value, nx=nx, ex=ex)
75
+
76
+ monkeypatch.setattr(redis_cursor, "set", flaky_set)
77
+ second_token = acquire_exit_mutex(redis_cursor, "r1", wait_s=5)
78
+ assert second_token is not None
79
+ assert second_token != first_token
80
+ assert sleeps, "acquire_exit_mutex must sleep between retries"
81
+
82
+
83
+ def test_acquire_times_out_when_never_released(monkeypatch):
84
+ redis_cursor = FakeRedis()
85
+ acquire_exit_mutex(redis_cursor, "r1") # holds it forever
86
+
87
+ monkeypatch.setattr(time, "sleep", lambda s: None)
88
+ clock = {"t": 0.0}
89
+ monkeypatch.setattr(time, "monotonic", lambda: clock["t"])
90
+
91
+ # First monotonic() call sets the deadline; make time "pass" fully on the
92
+ # very first is-it-past-the-deadline check so the loop returns None
93
+ # immediately instead of actually looping wait_s/0.2 times.
94
+ real_monotonic = time.monotonic
95
+ calls = {"n": 0}
96
+
97
+ def fake_monotonic():
98
+ calls["n"] += 1
99
+ if calls["n"] == 1:
100
+ return 0.0 # deadline = 0.0 + wait_s
101
+ return 10 ** 6 # every later check is long past it
102
+ monkeypatch.setattr(time, "monotonic", fake_monotonic)
103
+
104
+ token = acquire_exit_mutex(redis_cursor, "r1", wait_s=1)
105
+ assert token is None
106
+
107
+
108
+ def test_release_deletes_only_when_token_still_matches():
109
+ redis_cursor = FakeRedis()
110
+ token = acquire_exit_mutex(redis_cursor, "r1")
111
+ release_exit_mutex(redis_cursor, "r1", "not-the-token")
112
+ assert redis_cursor.get("r1:exit_mutex") == token # untouched
113
+
114
+ release_exit_mutex(redis_cursor, "r1", token)
115
+ assert redis_cursor.get("r1:exit_mutex") is None
116
+
117
+
118
+ def test_release_is_a_noop_when_key_already_expired_or_gone():
119
+ redis_cursor = FakeRedis()
120
+ release_exit_mutex(redis_cursor, "r1", "whatever") # must not raise
121
+ assert redis_cursor.get("r1:exit_mutex") is None
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # S5: renewal while an exit order waits for its fill
126
+ # ---------------------------------------------------------------------------
127
+
128
+ def test_renew_extends_the_ttl_for_its_own_token():
129
+ redis_cursor = FakeRedis()
130
+ token = acquire_exit_mutex(redis_cursor, "r1")
131
+ redis_cursor.now = EXIT_MUTEX_TTL_S - 1
132
+ renew_exit_mutex(redis_cursor, "r1", token)
133
+ redis_cursor.now = EXIT_MUTEX_TTL_S + 60 # past the original expiry
134
+ assert redis_cursor.get("r1:exit_mutex") == token
135
+
136
+
137
+ def test_renew_never_touches_someone_elses_mutex():
138
+ redis_cursor = FakeRedis()
139
+ other = acquire_exit_mutex(redis_cursor, "r1")
140
+ redis_cursor.now = EXIT_MUTEX_TTL_S - 1
141
+ renew_exit_mutex(redis_cursor, "r1", "stale-token")
142
+ redis_cursor.now = EXIT_MUTEX_TTL_S
143
+ assert redis_cursor.get("r1:exit_mutex") is None, f"{other} must expire on its own schedule"
144
+
145
+
146
+ def test_renew_does_not_resurrect_an_expired_mutex():
147
+ redis_cursor = FakeRedis()
148
+ token = acquire_exit_mutex(redis_cursor, "r1")
149
+ redis_cursor.now = EXIT_MUTEX_TTL_S + 1
150
+ renew_exit_mutex(redis_cursor, "r1", token)
151
+ assert redis_cursor.get("r1:exit_mutex") is None
152
+
153
+
154
+ def test_an_exit_poll_longer_than_the_ttl_keeps_the_mutex_held(monkeypatch):
155
+ """ A MARKET exit that sits OPEN for 5 minutes (> the 120 s TTL): with
156
+ on_wait renewing, nobody else can take the mutex until the fill. """
157
+ redis_cursor = FakeRedis()
158
+ token = acquire_exit_mutex(redis_cursor, "r1")
159
+ open_polls = {"n": 300}
160
+ seen_free = []
161
+
162
+ def fake_post(url, json, headers):
163
+ response = type("R", (), {})()
164
+ status = "OPEN" if open_polls["n"] > 0 else "COMPLETED"
165
+ open_polls["n"] -= 1
166
+ response.json = lambda: {"status": status, "timestamp": "2026-09-28 10:00:00", "average_price": 100.0}
167
+ return response
168
+
169
+ def fake_sleep(seconds):
170
+ redis_cursor.now += seconds
171
+ seen_free.append(acquire_exit_mutex(redis_cursor, "r1", wait_s=0) is not None)
172
+
173
+ monkeypatch.setattr(live.requests, "post", fake_post)
174
+ monkeypatch.setattr(live.time, "sleep", fake_sleep)
175
+ monkeypatch.setattr(live.time, "time", lambda: redis_cursor.now)
176
+ live.orders_url = "http://testserver/"
177
+
178
+ status, _ = live.poll_order_status("c1", "OID1", "NFO",
179
+ on_wait=lambda: renew_exit_mutex(redis_cursor, "r1", token))
180
+ assert status == "success"
181
+ assert redis_cursor.now >= 300
182
+ assert not any(seen_free), "the mutex must never be free while the exit is still OPEN"
183
+ assert redis_cursor.get("r1:exit_mutex") == token
@@ -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
@@ -127,3 +127,59 @@ def test_validate_credential_never_sends_credential_values():
127
127
 
128
128
  sent_body = mock_post.call_args.kwargs["json"]
129
129
  assert set(sent_body.keys()) == {"credential_id"}
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # S6: idempotency key -- one per call, reused only for transport retries
134
+ # ---------------------------------------------------------------------------
135
+
136
+ def test_place_live_order_sends_idempotency_key_at_top_level():
137
+ with patch("trigerr.orders.live.requests.post") as mock_post:
138
+ mock_post.return_value = _mock_response({"status": "SUCCESS"})
139
+ live.place_live_order(credential_id="cred-1", order_details={"symbol": "INFY"})
140
+
141
+ sent_body = mock_post.call_args.kwargs["json"]
142
+ assert sent_body["idempotency_key"]
143
+ assert "idempotency_key" not in sent_body["order_params"]
144
+
145
+
146
+ def test_transport_retry_resends_the_same_key():
147
+ """ The OMS may have placed the order before the connection dropped: the
148
+ retry must carry the same key so the OMS replays instead of re-placing. """
149
+ with patch("trigerr.orders.live.requests.post") as mock_post, patch("trigerr.orders.live.time.sleep"):
150
+ mock_post.side_effect = [live.requests.ConnectionError("reset"),
151
+ _mock_response({"status": "SUCCESS", "order_id": "OID1"})]
152
+ response = live.place_live_order(credential_id="cred-1", order_details={"symbol": "INFY"})
153
+
154
+ assert response["order_id"] == "OID1"
155
+ keys = [c.kwargs["json"]["idempotency_key"] for c in mock_post.call_args_list]
156
+ assert len(keys) == 2 and keys[0] == keys[1]
157
+
158
+
159
+ def test_each_call_gets_a_fresh_key():
160
+ """ The OMS keeps an ERROR result under its key for 24 h: a later retry of
161
+ a rejected order must not reuse the key, or it can never be placed. """
162
+ with patch("trigerr.orders.live.requests.post") as mock_post:
163
+ mock_post.return_value = _mock_response({"status": "ERROR"})
164
+ live.place_live_order(credential_id="cred-1", order_details={"symbol": "INFY"})
165
+ live.place_live_order(credential_id="cred-1", order_details={"symbol": "INFY"})
166
+
167
+ keys = [c.kwargs["json"]["idempotency_key"] for c in mock_post.call_args_list]
168
+ assert keys[0] != keys[1]
169
+
170
+
171
+ def test_transport_retries_are_bounded_then_return_none():
172
+ with patch("trigerr.orders.live.requests.post") as mock_post, patch("trigerr.orders.live.time.sleep"):
173
+ mock_post.side_effect = live.requests.ConnectionError("down")
174
+ assert live.place_live_order(credential_id="cred-1", order_details={"symbol": "INFY"}) is None
175
+ assert mock_post.call_count == 3
176
+
177
+
178
+ def test_poll_order_status_calls_on_wait_once_per_open_poll():
179
+ responses = [_mock_response({"status": "OPEN"}), _mock_response({"status": "OPEN"}),
180
+ _mock_response({"status": "COMPLETED", "timestamp": "2026-09-28 10:00:00"})]
181
+ waits = []
182
+ with patch("trigerr.orders.live.requests.post", side_effect=responses), patch("trigerr.orders.live.time.sleep"):
183
+ status, _ = live.poll_order_status("cred-1", "OID1", "NFO", on_wait=lambda: waits.append(1))
184
+ assert status == "success"
185
+ assert len(waits) == 2
@@ -1,6 +1,6 @@
1
1
  from .config import config
2
2
 
3
- __version__ = "0.7.0"
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