trigerr 0.7.4__tar.gz → 0.7.6__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 (243) hide show
  1. {trigerr-0.7.4/trigerr.egg-info → trigerr-0.7.6}/PKG-INFO +1 -1
  2. trigerr-0.7.6/tests/test_capital_used.py +94 -0
  3. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_exit_mutex.py +2 -1
  4. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_live_orders_correlation.py +1 -0
  5. trigerr-0.7.6/tests/test_live_orders_time_limits.py +196 -0
  6. trigerr-0.7.6/tests/test_live_record_saves.py +105 -0
  7. trigerr-0.7.6/tests/test_positions_with_oms.py +337 -0
  8. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/__init__.py +1 -1
  9. trigerr-0.7.6/trigerr/orders/__init__.py +6 -0
  10. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/orders/live.py +178 -40
  11. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/orders/orders_utils.py +56 -10
  12. trigerr-0.7.6/trigerr/orders/positions.py +370 -0
  13. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/utils.py +1 -1
  14. {trigerr-0.7.4 → trigerr-0.7.6/trigerr.egg-info}/PKG-INFO +1 -1
  15. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr.egg-info/SOURCES.txt +5 -0
  16. trigerr-0.7.4/trigerr/orders/__init__.py +0 -4
  17. {trigerr-0.7.4 → trigerr-0.7.6}/LICENSE +0 -0
  18. {trigerr-0.7.4 → trigerr-0.7.6}/README.md +0 -0
  19. {trigerr-0.7.4 → trigerr-0.7.6}/pyproject.toml +0 -0
  20. {trigerr-0.7.4 → trigerr-0.7.6}/setup.cfg +0 -0
  21. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_brokerage.py +0 -0
  22. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_bt_orders.py +0 -0
  23. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_crypto_utils.py +0 -0
  24. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_data_market.py +0 -0
  25. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_historical_client_config.py +0 -0
  26. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_ledger_contract.py +0 -0
  27. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_logging_utils.py +0 -0
  28. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_orders_schema.py +0 -0
  29. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_package.py +0 -0
  30. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_reference_symbol_details.py +0 -0
  31. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_schema_parity.py +0 -0
  32. {trigerr-0.7.4 → trigerr-0.7.6}/tests/test_symbols.py +0 -0
  33. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/config.py +0 -0
  34. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/crypto_utils.py +0 -0
  35. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/custom_indicators.py +0 -0
  36. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/data/__init__.py +0 -0
  37. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/data/historical.py +0 -0
  38. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/data/market.py +0 -0
  39. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/data/reference.py +0 -0
  40. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/__init__.py +0 -0
  41. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/bt_state.py +0 -0
  42. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/candle_transforms.py +0 -0
  43. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/catalog.py +0 -0
  44. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/clock.py +0 -0
  45. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/compiler.py +0 -0
  46. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/data_feeds.py +0 -0
  47. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/execution_core.py +0 -0
  48. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/exit_engine.py +0 -0
  49. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/expressions.py +0 -0
  50. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/instrument.py +0 -0
  51. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/point_in_time.py +0 -0
  52. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/position_sizing.py +0 -0
  53. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/framework/tick_sources.py +0 -0
  54. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/jobs.py +0 -0
  55. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/orders/backtest.py +0 -0
  56. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/orders/virtual.py +0 -0
  57. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/symbols.py +0 -0
  58. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/__init__.py +0 -0
  59. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/__main__.py +0 -0
  60. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/_typing.py +0 -0
  61. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/__init__.py +0 -0
  62. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/cdl_doji.py +0 -0
  63. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/cdl_inside.py +0 -0
  64. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/cdl_pattern.py +0 -0
  65. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/cdl_z.py +0 -0
  66. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/candle/ha.py +0 -0
  67. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/core.py +0 -0
  68. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/custom.py +0 -0
  69. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/cycle/__init__.py +0 -0
  70. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/cycle/ebsw.py +0 -0
  71. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/cycle/reflex.py +0 -0
  72. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/ma.py +0 -0
  73. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/maps.py +0 -0
  74. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/__init__.py +0 -0
  75. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/ao.py +0 -0
  76. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/apo.py +0 -0
  77. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/bias.py +0 -0
  78. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/bop.py +0 -0
  79. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/brar.py +0 -0
  80. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/cci.py +0 -0
  81. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/cfo.py +0 -0
  82. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/cg.py +0 -0
  83. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/cmo.py +0 -0
  84. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/coppock.py +0 -0
  85. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/crsi.py +0 -0
  86. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/cti.py +0 -0
  87. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/dm.py +0 -0
  88. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/er.py +0 -0
  89. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/eri.py +0 -0
  90. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/exhc.py +0 -0
  91. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/fisher.py +0 -0
  92. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/inertia.py +0 -0
  93. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/kdj.py +0 -0
  94. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/kst.py +0 -0
  95. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/macd.py +0 -0
  96. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/mom.py +0 -0
  97. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/pgo.py +0 -0
  98. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/ppo.py +0 -0
  99. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/psl.py +0 -0
  100. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/qqe.py +0 -0
  101. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/roc.py +0 -0
  102. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/rsi.py +0 -0
  103. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/rsx.py +0 -0
  104. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/rvgi.py +0 -0
  105. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/slope.py +0 -0
  106. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/smc.py +0 -0
  107. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/smi.py +0 -0
  108. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/squeeze.py +0 -0
  109. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/squeeze_pro.py +0 -0
  110. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/stc.py +0 -0
  111. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/stoch.py +0 -0
  112. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/stochf.py +0 -0
  113. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/stochrsi.py +0 -0
  114. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/tmo.py +0 -0
  115. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/trix.py +0 -0
  116. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/tsi.py +0 -0
  117. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/uo.py +0 -0
  118. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/momentum/willr.py +0 -0
  119. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/__init__.py +0 -0
  120. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/alligator.py +0 -0
  121. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/alma.py +0 -0
  122. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/dema.py +0 -0
  123. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/ema.py +0 -0
  124. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/fwma.py +0 -0
  125. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/hilo.py +0 -0
  126. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/hl2.py +0 -0
  127. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/hlc3.py +0 -0
  128. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/hma.py +0 -0
  129. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/hwma.py +0 -0
  130. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/ichimoku.py +0 -0
  131. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/jma.py +0 -0
  132. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/kama.py +0 -0
  133. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/linreg.py +0 -0
  134. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/mama.py +0 -0
  135. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/mcgd.py +0 -0
  136. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/midpoint.py +0 -0
  137. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/midprice.py +0 -0
  138. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/ohlc4.py +0 -0
  139. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/pivots.py +0 -0
  140. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/pwma.py +0 -0
  141. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/rma.py +0 -0
  142. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/sinwma.py +0 -0
  143. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/sma.py +0 -0
  144. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/smma.py +0 -0
  145. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/ssf.py +0 -0
  146. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/ssf3.py +0 -0
  147. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/supertrend.py +0 -0
  148. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/swma.py +0 -0
  149. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/t3.py +0 -0
  150. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/tema.py +0 -0
  151. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/trima.py +0 -0
  152. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/vidya.py +0 -0
  153. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/wcp.py +0 -0
  154. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/wma.py +0 -0
  155. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/overlap/zlma.py +0 -0
  156. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/performance/__init__.py +0 -0
  157. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/performance/drawdown.py +0 -0
  158. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/performance/log_return.py +0 -0
  159. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/performance/percent_return.py +0 -0
  160. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/py.typed +0 -0
  161. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/__init__.py +0 -0
  162. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/entropy.py +0 -0
  163. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/kurtosis.py +0 -0
  164. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/mad.py +0 -0
  165. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/median.py +0 -0
  166. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/quantile.py +0 -0
  167. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/skew.py +0 -0
  168. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/stdev.py +0 -0
  169. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/tos_stdevall.py +0 -0
  170. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/variance.py +0 -0
  171. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/statistics/zscore.py +0 -0
  172. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/__init__.py +0 -0
  173. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/adx.py +0 -0
  174. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/alphatrend.py +0 -0
  175. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/amat.py +0 -0
  176. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/aroon.py +0 -0
  177. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/chop.py +0 -0
  178. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/cksp.py +0 -0
  179. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/decay.py +0 -0
  180. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/decreasing.py +0 -0
  181. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/dpo.py +0 -0
  182. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/ht_trendline.py +0 -0
  183. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/increasing.py +0 -0
  184. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/long_run.py +0 -0
  185. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/psar.py +0 -0
  186. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/qstick.py +0 -0
  187. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/rwi.py +0 -0
  188. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/short_run.py +0 -0
  189. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/trendflex.py +0 -0
  190. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/ttm_trend.py +0 -0
  191. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/vhf.py +0 -0
  192. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/vortex.py +0 -0
  193. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/trend/zigzag.py +0 -0
  194. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/__init__.py +0 -0
  195. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_candles.py +0 -0
  196. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_core.py +0 -0
  197. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_math.py +0 -0
  198. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_numba.py +0 -0
  199. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_signals.py +0 -0
  200. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_study.py +0 -0
  201. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_time.py +0 -0
  202. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/utils/_validate.py +0 -0
  203. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/__init__.py +0 -0
  204. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/aberration.py +0 -0
  205. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/accbands.py +0 -0
  206. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/atr.py +0 -0
  207. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/atrts.py +0 -0
  208. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/bbands.py +0 -0
  209. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/chandelier_exit.py +0 -0
  210. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/donchian.py +0 -0
  211. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/hwc.py +0 -0
  212. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/kc.py +0 -0
  213. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/massi.py +0 -0
  214. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/natr.py +0 -0
  215. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/pdist.py +0 -0
  216. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/rvi.py +0 -0
  217. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/thermo.py +0 -0
  218. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/true_range.py +0 -0
  219. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volatility/ui.py +0 -0
  220. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/__init__.py +0 -0
  221. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/ad.py +0 -0
  222. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/adosc.py +0 -0
  223. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/aobv.py +0 -0
  224. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/cmf.py +0 -0
  225. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/efi.py +0 -0
  226. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/eom.py +0 -0
  227. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/kvo.py +0 -0
  228. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/mfi.py +0 -0
  229. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/nvi.py +0 -0
  230. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/obv.py +0 -0
  231. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/pvi.py +0 -0
  232. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/pvo.py +0 -0
  233. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/pvol.py +0 -0
  234. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/pvr.py +0 -0
  235. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/pvt.py +0 -0
  236. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/tsv.py +0 -0
  237. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/vhm.py +0 -0
  238. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/vp.py +0 -0
  239. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/vwap.py +0 -0
  240. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr/ta/volume/vwma.py +0 -0
  241. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr.egg-info/dependency_links.txt +0 -0
  242. {trigerr-0.7.4 → trigerr-0.7.6}/trigerr.egg-info/requires.txt +0 -0
  243. {trigerr-0.7.4 → trigerr-0.7.6}/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.4
3
+ Version: 0.7.6
4
4
  Summary: Python tools for quantitative trading research and analysis.
5
5
  Author: Anurag Singh Kushwah
6
6
  License-Expression: MIT
@@ -0,0 +1,94 @@
1
+ """
2
+ Every trade carries two amounts: `investment`, the capital set aside for it, and `capital_used`, what its entry really
3
+ put to work. `quantity` counts units for spot and futures and lots for everything else (the rule the P&L is priced
4
+ with), and an order carries the `leverage` it was sized with, so the used amount is the position value over it.
5
+
6
+ Offline: no Mongo, no Redis, no network.
7
+ """
8
+ import datetime
9
+
10
+ import pytest
11
+
12
+ from trigerr.orders import orders_utils
13
+
14
+
15
+ def entry_order(**extra):
16
+ order = {
17
+ "symbol": "NIFTY_22600_PE", "data_key": "UPSTOX:XNSE:NIFTY_22600_PE", "group_id": "r1:1",
18
+ "trade_action": "ENTRY", "date": datetime.datetime(2026, 10, 22), "day": "Thursday",
19
+ "order_timestamp": "2026-10-22 13:38:00", "trigger_price": 136.9, "quantity": 11,
20
+ "lot_size": 65, "investment": 100000, "position_type": "LONG", "exchange": "NFO",
21
+ "market": "IN", "order_type": "MARKET", "quantity_left": 0,
22
+ "user_id": "507f1f77bcf86cd799439011", "strategy_id": "507f1f77bcf86cd799439012",
23
+ "request_id": "507f1f77bcf86cd799439013",
24
+ }
25
+ order.update(extra)
26
+ return order
27
+
28
+
29
+ def exit_order(**extra):
30
+ order = {
31
+ "symbol": "NIFTY_22600_PE", "trade_action": "EXIT", "exit_type": "T1", "quantity_left": 0,
32
+ "order_timestamp": "2026-10-22 13:50:00", "trigger_price": 150.0, "quantity": 11,
33
+ "position_type": "LONG", "order_type": "MARKET",
34
+ }
35
+ order.update(extra)
36
+ return order
37
+
38
+
39
+ def converted(market_type="options", entry=None, exit=None):
40
+ trades = orders_utils.convert_to_trades(
41
+ orders_list=[entry_order(**(entry or {})), exit_order(**(exit or {}))],
42
+ market_type=market_type, order_exit_levels=["T1"], mode="vt", broker="zerodha")
43
+ assert trades is not None and len(trades) == 1
44
+ return trades[0]
45
+
46
+
47
+ def test_options_count_lots_times_lot_size():
48
+ trade = converted()
49
+ assert trade["capital_used"] == round(136.9 * 11 * 65, 2) == 97883.5
50
+
51
+
52
+ def test_the_capital_set_aside_is_kept_and_is_a_little_more_than_was_used():
53
+ trade = converted()
54
+ assert trade["investment"] == 100000
55
+ assert trade["capital_used"] < trade["investment"]
56
+
57
+
58
+ def test_futures_and_spot_count_units_not_lots():
59
+ futures = converted(market_type="futures", entry={"quantity": 3, "lot_size": 50, "trigger_price": 100}, exit={"quantity": 3})
60
+ assert futures["capital_used"] == 300
61
+ spot = converted(market_type="spot", entry={"quantity": 10, "lot_size": 1, "trigger_price": 250}, exit={"quantity": 10})
62
+ assert spot["capital_used"] == 2500
63
+
64
+
65
+ def test_leverage_divides_the_position_value():
66
+ trade = converted(entry={"quantity": 10, "lot_size": 25, "trigger_price": 100, "leverage": 5}, exit={"quantity": 10})
67
+ assert trade["capital_used"] == 5000
68
+ futures = converted(market_type="futures", entry={"quantity": 3, "trigger_price": 100, "leverage": 10}, exit={"quantity": 3})
69
+ assert futures["capital_used"] == 30
70
+
71
+
72
+ @pytest.mark.parametrize("leverage", [None, 0, 1])
73
+ def test_no_leverage_counts_as_one(leverage):
74
+ extra = {} if leverage is None else {"leverage": leverage}
75
+ assert converted(entry=extra)["capital_used"] == 97883.5
76
+
77
+
78
+ def test_a_fractional_crypto_position_keeps_its_cents():
79
+ order = entry_order(market="CRYPTO", quantity=0.5, trigger_price=60000.25, lot_size=1, leverage=10)
80
+ assert orders_utils.capital_used(order, "futures") == 3000.01
81
+
82
+
83
+ def test_every_trade_of_a_run_carries_it():
84
+ orders = [entry_order(group_id="r1:1"), exit_order(), entry_order(group_id="r1:2", trigger_price=140.0), exit_order(trigger_price=120.0)]
85
+ trades = orders_utils.convert_to_trades(orders_list=orders, market_type="options", order_exit_levels=["T1"], mode="vt", broker="zerodha")
86
+ assert [trade["capital_used"] for trade in trades] == [97883.5, round(140.0 * 11 * 65, 2)]
87
+
88
+
89
+ def test_leverage_changes_what_was_used_not_the_profit_or_the_charges():
90
+ plain = converted()
91
+ leveraged = converted(entry={"leverage": 5})
92
+ assert leveraged["capital_used"] == plain["capital_used"] / 5
93
+ for field in ("pnl", "net_pnl", "brokerage", "pnl_percent"):
94
+ assert leveraged[field] == plain[field]
@@ -159,8 +159,9 @@ def test_an_exit_poll_longer_than_the_ttl_keeps_the_mutex_held(monkeypatch):
159
159
  open_polls = {"n": 300}
160
160
  seen_free = []
161
161
 
162
- def fake_post(url, json, headers):
162
+ def fake_post(url, json, headers, timeout):
163
163
  response = type("R", (), {})()
164
+ response.status_code = 200
164
165
  status = "OPEN" if open_polls["n"] > 0 else "COMPLETED"
165
166
  open_polls["n"] -= 1
166
167
  response.json = lambda: {"status": status, "timestamp": "2026-09-28 10:00:00", "average_price": 100.0}
@@ -6,6 +6,7 @@ from trigerr.orders import live
6
6
 
7
7
  def _mock_response(payload):
8
8
  response = MagicMock()
9
+ response.status_code = 200
9
10
  response.json.return_value = payload
10
11
  return response
11
12
 
@@ -0,0 +1,196 @@
1
+ """ Calls to the OMS carry a time limit, a placement with no answer is resent under its own key until the OMS has
2
+ a result for it, and a fill wait that gets no answer keeps waiting: none of these says anything about the order. """
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+
7
+ from trigerr import utils
8
+ from trigerr.orders import live
9
+
10
+ IN_PROGRESS = {"status": "ERROR", "message": "Duplicate request: an order with this idempotency_key is still in progress."}
11
+ DONE = {"status": "COMPLETED", "timestamp": "2026-11-11 10:00:00", "filled_quantity": 75, "order_quantity": 75}
12
+
13
+
14
+ def reply(payload, status_code=200):
15
+ response = MagicMock()
16
+ response.status_code = status_code
17
+ response.json.return_value = payload
18
+ return response
19
+
20
+
21
+ def setup_function(function):
22
+ live.orders_url = "http://testserver/"
23
+
24
+
25
+ @pytest.fixture
26
+ def post():
27
+ with patch("trigerr.orders.live.requests.post") as mock_post, patch("trigerr.orders.live.time.sleep"):
28
+ yield mock_post
29
+
30
+
31
+ def test_every_order_call_has_a_time_limit(post):
32
+ post.return_value = reply(dict(DONE, status="SUCCESS"))
33
+ live.place_live_order("cred-1", {"symbol": "INFY"})
34
+ live.modify_live_order("cred-1", {"order_id": "o1"})
35
+ live.check_order_status("cred-1", "o1", "NFO")
36
+ live.validate_credential("cred-1")
37
+ post.return_value = reply(DONE)
38
+ live.poll_order_status("cred-1", "o1", "NFO")
39
+ limits = [call.kwargs.get("timeout") for call in post.call_args_list]
40
+ assert limits == [live.PLACE_TIMEOUT, live.CALL_TIMEOUT, live.CALL_TIMEOUT, live.CALL_TIMEOUT, live.CALL_TIMEOUT]
41
+ assert all(limit and all(limit) for limit in limits)
42
+
43
+
44
+ def test_the_funds_read_has_a_time_limit():
45
+ with patch("trigerr.utils.requests.post") as mock_post:
46
+ mock_post.return_value = reply({"status": "SUCCESS", "message": "Fund Found", "data": {"net_balance": 5.0}})
47
+ assert utils.fetch_available_funds("cred-1") == 5.0
48
+ assert mock_post.call_args.kwargs["timeout"]
49
+
50
+
51
+ @pytest.mark.parametrize("no_answer", [live.requests.Timeout("slow"), reply("<html>bad gateway</html>", 502), reply({}, 504)])
52
+ def test_a_placement_with_no_answer_is_resent_under_the_same_key(post, no_answer):
53
+ post.side_effect = [no_answer, reply({"status": "SUCCESS", "order_id": "OID1"})]
54
+ assert live.place_live_order("cred-1", {"symbol": "INFY"})["order_id"] == "OID1"
55
+ keys = [call.kwargs["json"]["idempotency_key"] for call in post.call_args_list]
56
+ assert len(keys) == 2 and keys[0] == keys[1]
57
+
58
+
59
+ def test_a_resent_placement_waits_for_the_first_send_to_finish(post):
60
+ post.side_effect = [live.requests.Timeout("slow"), reply(IN_PROGRESS), reply(IN_PROGRESS),
61
+ reply({"status": "SUCCESS", "order_id": "OID1"})]
62
+ assert live.place_live_order("cred-1", {"symbol": "INFY"})["order_id"] == "OID1"
63
+ keys = {call.kwargs["json"]["idempotency_key"] for call in post.call_args_list}
64
+ assert post.call_count == 4 and len(keys) == 1
65
+
66
+
67
+ def test_the_wait_for_the_first_send_is_bounded(post):
68
+ post.side_effect = [live.requests.Timeout("slow")] + [reply(IN_PROGRESS)] * 50
69
+ clock = iter(range(0, 10000, 10))
70
+ with patch("trigerr.orders.live.time.monotonic", side_effect=lambda: next(clock)):
71
+ answer = live.place_live_order("cred-1", {"symbol": "INFY"})
72
+ assert answer == IN_PROGRESS
73
+ assert post.call_count < 50
74
+
75
+
76
+ def test_a_plain_refusal_is_not_resent(post):
77
+ post.return_value = reply({"status": "ERROR", "message": "Insufficient funds"})
78
+ assert live.place_live_order("cred-1", {"symbol": "INFY"})["message"] == "Insufficient funds"
79
+ assert post.call_count == 1
80
+
81
+
82
+ @pytest.mark.parametrize("no_answer", [live.requests.ConnectionError("reset"), live.requests.Timeout("slow"),
83
+ reply("<html>bad gateway</html>", 502)])
84
+ def test_a_fill_wait_with_no_answer_keeps_waiting(post, no_answer):
85
+ post.side_effect = [reply({"status": "OPEN"}), no_answer, no_answer, reply(DONE)]
86
+ waits = []
87
+ status, order = live.poll_order_status("cred-1", "OID1", "NFO", on_wait=lambda: waits.append(1))
88
+ assert status == "success" and order["filled_quantity"] == 75
89
+ assert len(waits) == 3
90
+
91
+
92
+ def test_a_fill_wait_with_no_answer_still_ends_at_its_time_limit(post):
93
+ post.side_effect = live.requests.ConnectionError("down")
94
+ clock = iter(range(0, 100000, 100))
95
+ with patch("trigerr.orders.live.time.time", side_effect=lambda: next(clock)):
96
+ status, order = live.poll_order_status("cred-1", "OID1", "NFO", max_wait_seconds=1000)
97
+ assert status == "timeout" and order is None
98
+
99
+
100
+ OPEN = {"status": "OPEN", "timestamp": "2026-11-11 10:00:00", "filled_quantity": 0, "order_quantity": 75}
101
+
102
+
103
+ def ended(status, filled):
104
+ return dict(DONE, status=status, filled_quantity=filled)
105
+
106
+
107
+ def paths(post):
108
+ return [call.kwargs["url"].rsplit("/", 1)[1] for call in post.call_args_list]
109
+
110
+
111
+ def ticking(step):
112
+ """ A clock that moves `step` seconds on every read. """
113
+ clock = iter(range(0, 1000000, step))
114
+ return patch("trigerr.orders.live.time.time", side_effect=lambda: next(clock))
115
+
116
+
117
+ def test_an_order_that_fills_settles_with_no_cancel(post):
118
+ post.side_effect = [reply(OPEN), reply(DONE)]
119
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO")
120
+ assert settled and live.filled_lots(order, 75) == 1
121
+ assert "cancel_order" not in paths(post)
122
+
123
+
124
+ @pytest.mark.parametrize("status,filled,lots", [("PARTIAL", 150, 2), ("REJECTED", 0, 0), ("CANCELLED", 0, 0)])
125
+ def test_an_order_that_ends_short_settles_with_what_filled(post, status, filled, lots):
126
+ post.return_value = reply(ended(status, filled))
127
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO")
128
+ assert settled and live.filled_lots(order, 75) == lots
129
+ assert "cancel_order" not in paths(post)
130
+
131
+
132
+ def open_until_cancelled(post, then):
133
+ """ The OMS says OPEN until a cancel is asked for, and `then` after it. """
134
+ def answer(url, **kwargs):
135
+ if url.endswith("cancel_order"):
136
+ return reply({"status": "SUCCESS"})
137
+ return reply(then if "cancel_order" in paths(post)[:-1] else OPEN)
138
+ post.side_effect = answer
139
+
140
+
141
+ def test_an_order_still_open_after_the_wait_is_cancelled_then_settled(post):
142
+ open_until_cancelled(post, then=ended("PARTIAL", 75))
143
+ with ticking(10):
144
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO", wait_s=60)
145
+ assert paths(post).count("cancel_order") == 1 and paths(post)[-1] == "check_order_status"
146
+ assert settled and live.filled_lots(order, 75) == 1
147
+
148
+
149
+ def test_an_order_still_open_after_the_cancel_is_not_settled(post):
150
+ open_until_cancelled(post, then=OPEN)
151
+ with ticking(10):
152
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO", wait_s=60)
153
+ assert not settled and order["status"] == "OPEN"
154
+ assert paths(post).count("cancel_order") == 1
155
+
156
+
157
+ @pytest.mark.parametrize("unclear", [{"status": "ERROR", "message": "Order Details could not be found"}, {"status": "WHAT"}])
158
+ def test_only_unclear_answers_is_not_settled(post, unclear):
159
+ post.return_value = reply(dict(unclear, timestamp="2026-11-11 10:00:00"))
160
+ with ticking(10):
161
+ settled, _ = live.settle_live_order("cred-1", "OID1", "NFO", wait_s=60)
162
+ assert not settled
163
+ assert paths(post).count("cancel_order") == 1 and paths(post).count("check_order_status") > 2
164
+
165
+
166
+ def test_an_unclear_answer_does_not_end_the_wait(post):
167
+ unclear = {"status": "ERROR", "message": "Exception in Check Order Status", "timestamp": "2026-11-11 10:00:00"}
168
+ post.side_effect = [reply(unclear), reply(OPEN), reply(DONE)]
169
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO")
170
+ assert settled and order["status"] == "COMPLETED"
171
+ assert paths(post) == ["check_order_status"] * 3
172
+
173
+
174
+ def test_no_answer_at_all_is_not_settled(post):
175
+ post.side_effect = live.requests.ConnectionError("down")
176
+ with ticking(40):
177
+ settled, order = live.settle_live_order("cred-1", "OID1", "NFO", wait_s=60)
178
+ assert not settled and order is None
179
+
180
+
181
+ @pytest.mark.parametrize("order_response,expected", [
182
+ ({"status": "SUCCESS", "order_id": "o1", "sub_orders": [{"status": "OPEN"}]}, "o1"),
183
+ ({"status": "ERROR", "order_id": "o1", "sub_orders": [{"status": "OPEN"}, {"status": "REJECTED"}]}, "o1"),
184
+ ({"status": "ERROR", "order_id": "o1", "sub_orders": [{"status": "REJECTED"}]}, None),
185
+ ({"status": "ERROR", "message": "placement UNCONFIRMED"}, None),
186
+ (None, None),
187
+ ])
188
+ def test_placed_order_id_is_there_only_when_a_piece_is_at_the_broker(order_response, expected):
189
+ assert live.placed_order_id(order_response) == expected
190
+
191
+
192
+ def test_filled_lots():
193
+ assert live.filled_lots({"filled_quantity": 150}, 75) == 2
194
+ assert live.filled_lots({"filled_quantity": None}, 75) == 0
195
+ assert live.filled_lots(None, 75) == 0
196
+ assert live.filled_lots({"filled_quantity": 1.5}) == 1.5
@@ -0,0 +1,105 @@
1
+ """ Saving a live order or trade: a few tries, and an answer that says where the record really is. """
2
+ import datetime
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+ from pymongo.errors import AutoReconnect, DuplicateKeyError
7
+
8
+ from trigerr.orders import live, orders_utils
9
+ from test_orders_schema import FakeDB, FakeRedis
10
+
11
+
12
+ class FlakyCollection:
13
+ """ insert_one fails `failures` times, then saves. `lands` True: a failed try did reach the database. """
14
+ def __init__(self, failures, lands=False):
15
+ self.failures, self.lands, self.docs = failures, lands, []
16
+
17
+ def insert_one(self, doc):
18
+ doc.setdefault("_id", "id0")
19
+ if any(saved["_id"] == doc["_id"] for saved in self.docs):
20
+ raise DuplicateKeyError("duplicate")
21
+ if self.failures:
22
+ self.failures -= 1
23
+ if self.lands:
24
+ self.docs.append(dict(doc))
25
+ raise AutoReconnect("connection lost")
26
+ self.docs.append(dict(doc))
27
+ return type("Result", (), {"inserted_id": doc["_id"]})()
28
+
29
+
30
+ class FlakyRedis(FakeRedis):
31
+ """ rpush fails `failures` times, then works. `lands` True: a failed try did reach Redis. """
32
+ def __init__(self, failures, lands=False):
33
+ super().__init__()
34
+ self.failures, self.lands = failures, lands
35
+
36
+ def rpush(self, key, value):
37
+ if self.failures:
38
+ self.failures -= 1
39
+ if self.lands:
40
+ super().rpush(key, value)
41
+ raise ConnectionError("socket closed")
42
+ return super().rpush(key, value)
43
+
44
+
45
+ @pytest.fixture(autouse=True)
46
+ def quiet():
47
+ with patch("trigerr.orders.orders_utils.time.sleep"), patch("trigerr.orders.live.send_order_alert"):
48
+ yield
49
+
50
+
51
+ def save_order(db, redis_cursor):
52
+ return live.save_lt_order(app_db_cursor=db, redis_cursor=redis_cursor, orders_list=[], symbol="NIFTY_23450_CE", quantity=2,
53
+ user_id="u1", strategy_id="s1", request_id="r1", exchange="NFO", order_id="OID1",
54
+ exchange_timestamp=datetime.datetime(2026, 1, 1, 9, 20))
55
+
56
+
57
+ def db_with(collection):
58
+ db = FakeDB()
59
+ db.cols["lt_orders"] = db.cols["lt_trades"] = collection
60
+ return db
61
+
62
+
63
+ def test_a_save_that_fails_twice_then_works_is_a_success():
64
+ collection, redis_cursor = FlakyCollection(failures=2), FlakyRedis(failures=2)
65
+ status, orders = save_order(db_with(collection), redis_cursor)
66
+ assert status == "success" and len(collection.docs) == 1 and len(orders) == 1
67
+
68
+
69
+ @pytest.mark.parametrize("failures", [1, 2])
70
+ def test_a_try_that_did_land_is_not_saved_twice(failures):
71
+ collection, redis_cursor = FlakyCollection(failures, lands=True), FlakyRedis(failures, lands=True)
72
+ status, orders = save_order(db_with(collection), redis_cursor)
73
+ assert status == "success" and len(collection.docs) == 1 and len(orders) == 1
74
+
75
+
76
+ def test_mongo_down_and_redis_up_is_db_failed_and_the_order_is_in_the_list():
77
+ status, orders = save_order(db_with(FlakyCollection(failures=99)), FakeRedis())
78
+ assert status == "db_failed"
79
+ assert len(orders) == 1 and orders[0]["order_id"] == "OID1" and "db_order_id" not in orders[0]
80
+
81
+
82
+ def test_redis_down_is_failed_whatever_mongo_did():
83
+ collection = FlakyCollection(failures=0)
84
+ status, orders = save_order(db_with(collection), FlakyRedis(failures=99))
85
+ assert status == "failed" and orders == [] and len(collection.docs) == 1
86
+
87
+
88
+ def test_a_failed_order_alert_does_not_fail_the_save():
89
+ with patch("trigerr.orders.live.send_order_alert", side_effect=RuntimeError("alert service down")):
90
+ status, orders = save_order(FakeDB(), FakeRedis())
91
+ assert status == "success" and len(orders) == 1
92
+
93
+
94
+ def test_save_lt_trade_says_whether_mongo_took_it():
95
+ trade = {"request_id": "r1", "user_id": "u1", "pnl": 10}
96
+ assert live.save_lt_trade(FakeDB(), FakeRedis(), dict(trade)) is True
97
+ redis_cursor = FakeRedis()
98
+ assert live.save_lt_trade(db_with(FlakyCollection(failures=99)), redis_cursor, dict(trade)) is False
99
+ assert len(redis_cursor.lists["r1_trades"]) == 1
100
+
101
+
102
+ def test_add_order_to_redis_answers():
103
+ order = {"user_id": "u1", "symbol": "X"}
104
+ assert orders_utils.add_order_to_redis(FakeRedis(), "r1", order, "vt") is True
105
+ assert orders_utils.add_order_to_redis(FlakyRedis(failures=99), "r1", order, "vt") is False