openscript 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 (189) hide show
  1. {openscript-0.7.0 → openscript-0.7.2}/PKG-INFO +1 -1
  2. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/flows.py +38 -5
  3. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/trend.py +2 -2
  4. {openscript-0.7.0 → openscript-0.7.2}/openscript.egg-info/PKG-INFO +1 -1
  5. {openscript-0.7.0 → openscript-0.7.2}/openscript.egg-info/SOURCES.txt +2 -0
  6. {openscript-0.7.0 → openscript-0.7.2}/pyproject.toml +1 -1
  7. openscript-0.7.2/tests/test_anchored_total_overflow.py +294 -0
  8. openscript-0.7.2/tests/test_directional_recovery.py +180 -0
  9. {openscript-0.7.0 → openscript-0.7.2}/README.md +0 -0
  10. {openscript-0.7.0 → openscript-0.7.2}/openscript/__init__.py +0 -0
  11. {openscript-0.7.0 → openscript-0.7.2}/openscript/__main__.py +0 -0
  12. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/__init__.py +0 -0
  13. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/analysis.py +0 -0
  14. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/charges.py +0 -0
  15. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/equity.py +0 -0
  16. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/report.py +0 -0
  17. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/shapes.py +0 -0
  18. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/statistics.py +0 -0
  19. {openscript-0.7.0 → openscript-0.7.2}/openscript/accounting/trades.py +0 -0
  20. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/__init__.py +0 -0
  21. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/answers.py +0 -0
  22. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/channels.py +0 -0
  23. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/expectations.py +0 -0
  24. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/facts.py +0 -0
  25. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/matching.py +0 -0
  26. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/ordering.py +0 -0
  27. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/page.py +0 -0
  28. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/reading.py +0 -0
  29. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/reporting.py +0 -0
  30. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/running.py +0 -0
  31. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/secondary.py +0 -0
  32. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/serving.py +0 -0
  33. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/sessions.py +0 -0
  34. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/spellings.py +0 -0
  35. {openscript-0.7.0 → openscript-0.7.2}/openscript/adapter/surface.py +0 -0
  36. {openscript-0.7.0 → openscript-0.7.2}/openscript/arrays.py +0 -0
  37. {openscript-0.7.0 → openscript-0.7.2}/openscript/bars.py +0 -0
  38. {openscript-0.7.0 → openscript-0.7.2}/openscript/buckets.py +0 -0
  39. {openscript-0.7.0 → openscript-0.7.2}/openscript/budget.py +0 -0
  40. {openscript-0.7.0 → openscript-0.7.2}/openscript/canonical.py +0 -0
  41. {openscript-0.7.0 → openscript-0.7.2}/openscript/civil.py +0 -0
  42. {openscript-0.7.0 → openscript-0.7.2}/openscript/contracts.py +0 -0
  43. {openscript-0.7.0 → openscript-0.7.2}/openscript/dates.py +0 -0
  44. {openscript-0.7.0 → openscript-0.7.2}/openscript/diagnostics.py +0 -0
  45. {openscript-0.7.0 → openscript-0.7.2}/openscript/hours.py +0 -0
  46. {openscript-0.7.0 → openscript-0.7.2}/openscript/inputs.py +0 -0
  47. {openscript-0.7.0 → openscript-0.7.2}/openscript/intervals.py +0 -0
  48. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/__init__.py +0 -0
  49. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/arithmetic.py +0 -0
  50. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/averages.py +0 -0
  51. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/bars.py +0 -0
  52. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/bookkeeping.py +0 -0
  53. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/code_points.py +0 -0
  54. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/colour.py +0 -0
  55. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/composites.py +0 -0
  56. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/counting.py +0 -0
  57. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/deviation.py +0 -0
  58. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/elementary.py +0 -0
  59. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/extremes.py +0 -0
  60. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/gaussian.py +0 -0
  61. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/history.py +0 -0
  62. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/hypotenuse.py +0 -0
  63. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/momentum.py +0 -0
  64. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/number_text.py +0 -0
  65. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/power.py +0 -0
  66. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/prices.py +0 -0
  67. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/ranges.py +0 -0
  68. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/rounding.py +0 -0
  69. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/series.py +0 -0
  70. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/stateful.py +0 -0
  71. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/stateless.py +0 -0
  72. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/strength.py +0 -0
  73. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/strings.py +0 -0
  74. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/transcendental.py +0 -0
  75. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/trigonometric.py +0 -0
  76. {openscript-0.7.0 → openscript-0.7.2}/openscript/library/values.py +0 -0
  77. {openscript-0.7.0 → openscript-0.7.2}/openscript/logbook.py +0 -0
  78. {openscript-0.7.0 → openscript-0.7.2}/openscript/machine.py +0 -0
  79. {openscript-0.7.0 → openscript-0.7.2}/openscript/memory.py +0 -0
  80. {openscript-0.7.0 → openscript-0.7.2}/openscript/objects.py +0 -0
  81. {openscript-0.7.0 → openscript-0.7.2}/openscript/opcodes.py +0 -0
  82. {openscript-0.7.0 → openscript-0.7.2}/openscript/program.py +0 -0
  83. {openscript-0.7.0 → openscript-0.7.2}/openscript/reasons.py +0 -0
  84. {openscript-0.7.0 → openscript-0.7.2}/openscript/request_body.py +0 -0
  85. {openscript-0.7.0 → openscript-0.7.2}/openscript/request_plan.py +0 -0
  86. {openscript-0.7.0 → openscript-0.7.2}/openscript/requests.py +0 -0
  87. {openscript-0.7.0 → openscript-0.7.2}/openscript/run.py +0 -0
  88. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/__init__.py +0 -0
  89. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/calls.py +0 -0
  90. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/closable.py +0 -0
  91. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/fills.py +0 -0
  92. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/holdings.py +0 -0
  93. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/intents.py +0 -0
  94. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/ledger.py +0 -0
  95. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/placing.py +0 -0
  96. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/positions.py +0 -0
  97. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/refusals.py +0 -0
  98. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/rows.py +0 -0
  99. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/sizing.py +0 -0
  100. {openscript-0.7.0 → openscript-0.7.2}/openscript/strategy/statuses.py +0 -0
  101. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/__init__.py +0 -0
  102. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/bands.py +0 -0
  103. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/levels.py +0 -0
  104. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/marks.py +0 -0
  105. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/paints.py +0 -0
  106. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/plots.py +0 -0
  107. {openscript-0.7.0 → openscript-0.7.2}/openscript/surface/published.py +0 -0
  108. {openscript-0.7.0 → openscript-0.7.2}/openscript/values.py +0 -0
  109. {openscript-0.7.0 → openscript-0.7.2}/openscript/verify.py +0 -0
  110. {openscript-0.7.0 → openscript-0.7.2}/openscript/verify_code.py +0 -0
  111. {openscript-0.7.0 → openscript-0.7.2}/openscript/verify_requests.py +0 -0
  112. {openscript-0.7.0 → openscript-0.7.2}/openscript/verify_shape.py +0 -0
  113. {openscript-0.7.0 → openscript-0.7.2}/openscript/verify_tables.py +0 -0
  114. {openscript-0.7.0 → openscript-0.7.2}/openscript/version.py +0 -0
  115. {openscript-0.7.0 → openscript-0.7.2}/openscript/zones.py +0 -0
  116. {openscript-0.7.0 → openscript-0.7.2}/openscript.egg-info/dependency_links.txt +0 -0
  117. {openscript-0.7.0 → openscript-0.7.2}/openscript.egg-info/top_level.txt +0 -0
  118. {openscript-0.7.0 → openscript-0.7.2}/setup.cfg +0 -0
  119. {openscript-0.7.0 → openscript-0.7.2}/tests/test_adapter_cases.py +0 -0
  120. {openscript-0.7.0 → openscript-0.7.2}/tests/test_adapter_identity.py +0 -0
  121. {openscript-0.7.0 → openscript-0.7.2}/tests/test_adapter_matching.py +0 -0
  122. {openscript-0.7.0 → openscript-0.7.2}/tests/test_adapter_page.py +0 -0
  123. {openscript-0.7.0 → openscript-0.7.2}/tests/test_adapter_spellings.py +0 -0
  124. {openscript-0.7.0 → openscript-0.7.2}/tests/test_analysis_and_runup.py +0 -0
  125. {openscript-0.7.0 → openscript-0.7.2}/tests/test_arrays.py +0 -0
  126. {openscript-0.7.0 → openscript-0.7.2}/tests/test_bar_cycle.py +0 -0
  127. {openscript-0.7.0 → openscript-0.7.2}/tests/test_bars.py +0 -0
  128. {openscript-0.7.0 → openscript-0.7.2}/tests/test_calendar.py +0 -0
  129. {openscript-0.7.0 → openscript-0.7.2}/tests/test_calendar_reads.py +0 -0
  130. {openscript-0.7.0 → openscript-0.7.2}/tests/test_calls.py +0 -0
  131. {openscript-0.7.0 → openscript-0.7.2}/tests/test_canonical.py +0 -0
  132. {openscript-0.7.0 → openscript-0.7.2}/tests/test_charges.py +0 -0
  133. {openscript-0.7.0 → openscript-0.7.2}/tests/test_colour.py +0 -0
  134. {openscript-0.7.0 → openscript-0.7.2}/tests/test_cost_models.py +0 -0
  135. {openscript-0.7.0 → openscript-0.7.2}/tests/test_diagnostics.py +0 -0
  136. {openscript-0.7.0 → openscript-0.7.2}/tests/test_engine_facts.py +0 -0
  137. {openscript-0.7.0 → openscript-0.7.2}/tests/test_frame_fold.py +0 -0
  138. {openscript-0.7.0 → openscript-0.7.2}/tests/test_frame_instant.py +0 -0
  139. {openscript-0.7.0 → openscript-0.7.2}/tests/test_hand_over.py +0 -0
  140. {openscript-0.7.0 → openscript-0.7.2}/tests/test_harness.py +0 -0
  141. {openscript-0.7.0 → openscript-0.7.2}/tests/test_host_surface.py +0 -0
  142. {openscript-0.7.0 → openscript-0.7.2}/tests/test_hypot.py +0 -0
  143. {openscript-0.7.0 → openscript-0.7.2}/tests/test_ledger_orders.py +0 -0
  144. {openscript-0.7.0 → openscript-0.7.2}/tests/test_library_vectors.py +0 -0
  145. {openscript-0.7.0 → openscript-0.7.2}/tests/test_logbook.py +0 -0
  146. {openscript-0.7.0 → openscript-0.7.2}/tests/test_machine.py +0 -0
  147. {openscript-0.7.0 → openscript-0.7.2}/tests/test_maths_rules.py +0 -0
  148. {openscript-0.7.0 → openscript-0.7.2}/tests/test_memory.py +0 -0
  149. {openscript-0.7.0 → openscript-0.7.2}/tests/test_mfi_numeric_edges.py +0 -0
  150. {openscript-0.7.0 → openscript-0.7.2}/tests/test_momentum_overflow.py +0 -0
  151. {openscript-0.7.0 → openscript-0.7.2}/tests/test_number_text.py +0 -0
  152. {openscript-0.7.0 → openscript-0.7.2}/tests/test_objects.py +0 -0
  153. {openscript-0.7.0 → openscript-0.7.2}/tests/test_opcodes.py +0 -0
  154. {openscript-0.7.0 → openscript-0.7.2}/tests/test_order_page.py +0 -0
  155. {openscript-0.7.0 → openscript-0.7.2}/tests/test_order_refusals.py +0 -0
  156. {openscript-0.7.0 → openscript-0.7.2}/tests/test_power.py +0 -0
  157. {openscript-0.7.0 → openscript-0.7.2}/tests/test_programs.py +0 -0
  158. {openscript-0.7.0 → openscript-0.7.2}/tests/test_range_numeric_edges.py +0 -0
  159. {openscript-0.7.0 → openscript-0.7.2}/tests/test_recursive_overflow.py +0 -0
  160. {openscript-0.7.0 → openscript-0.7.2}/tests/test_request_host.py +0 -0
  161. {openscript-0.7.0 → openscript-0.7.2}/tests/test_requests.py +0 -0
  162. {openscript-0.7.0 → openscript-0.7.2}/tests/test_series_rules.py +0 -0
  163. {openscript-0.7.0 → openscript-0.7.2}/tests/test_session_window.py +0 -0
  164. {openscript-0.7.0 → openscript-0.7.2}/tests/test_sizing_refusal.py +0 -0
  165. {openscript-0.7.0 → openscript-0.7.2}/tests/test_stateful_numeric_edges.py +0 -0
  166. {openscript-0.7.0 → openscript-0.7.2}/tests/test_stateful_vectors.py +0 -0
  167. {openscript-0.7.0 → openscript-0.7.2}/tests/test_statistics_overflow.py +0 -0
  168. {openscript-0.7.0 → openscript-0.7.2}/tests/test_strategy_cases.py +0 -0
  169. {openscript-0.7.0 → openscript-0.7.2}/tests/test_strategy_profile.py +0 -0
  170. {openscript-0.7.0 → openscript-0.7.2}/tests/test_string_ceiling.py +0 -0
  171. {openscript-0.7.0 → openscript-0.7.2}/tests/test_strings.py +0 -0
  172. {openscript-0.7.0 → openscript-0.7.2}/tests/test_supplied_schedule.py +0 -0
  173. {openscript-0.7.0 → openscript-0.7.2}/tests/test_surface.py +0 -0
  174. {openscript-0.7.0 → openscript-0.7.2}/tests/test_surface_bands.py +0 -0
  175. {openscript-0.7.0 → openscript-0.7.2}/tests/test_surface_example.py +0 -0
  176. {openscript-0.7.0 → openscript-0.7.2}/tests/test_time_programs.py +0 -0
  177. {openscript-0.7.0 → openscript-0.7.2}/tests/test_trades_and_summary.py +0 -0
  178. {openscript-0.7.0 → openscript-0.7.2}/tests/test_transcendental.py +0 -0
  179. {openscript-0.7.0 → openscript-0.7.2}/tests/test_trend_numeric_edges.py +0 -0
  180. {openscript-0.7.0 → openscript-0.7.2}/tests/test_trigonometric.py +0 -0
  181. {openscript-0.7.0 → openscript-0.7.2}/tests/test_values.py +0 -0
  182. {openscript-0.7.0 → openscript-0.7.2}/tests/test_varying_distance.py +0 -0
  183. {openscript-0.7.0 → openscript-0.7.2}/tests/test_varying_lookback.py +0 -0
  184. {openscript-0.7.0 → openscript-0.7.2}/tests/test_varying_occurrence.py +0 -0
  185. {openscript-0.7.0 → openscript-0.7.2}/tests/test_varying_recursive.py +0 -0
  186. {openscript-0.7.0 → openscript-0.7.2}/tests/test_verify.py +0 -0
  187. {openscript-0.7.0 → openscript-0.7.2}/tests/test_version.py +0 -0
  188. {openscript-0.7.0 → openscript-0.7.2}/tests/test_volume_numeric_edges.py +0 -0
  189. {openscript-0.7.0 → openscript-0.7.2}/tests/test_zero_boundaries.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscript
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: An open trading language: the engine that runs a compiled program.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/marketcalls/openscript#readme
@@ -52,13 +52,22 @@ def _anchored(state: Region, ctx, value: Value, anchor: Value) -> Value:
52
52
  return ABSENT
53
53
  if price is None or traded is None:
54
54
  return ABSENT
55
- flow = held.get("flow", 0.0) + price * traded
55
+ # An overflowing product is an absent term under compiled-program.md 3.1:
56
+ # the bar is absent and neither total moves.
57
+ term = result(price * traded)
58
+ if term is None:
59
+ return ABSENT
60
+ flow = held.get("flow", 0.0) + term
56
61
  volume = held.get("traded", 0.0) + traded
57
62
  held["flow"] = flow
58
63
  held["traded"] = volume
59
- if volume == 0:
64
+ # An overflowed total is kept and is absent, so the average divided by it is
65
+ # too, rather than the exact zero a finite flow over an infinity would give.
66
+ numerator = result(flow)
67
+ divisor = result(volume)
68
+ if numerator is None or divisor is None or divisor == 0:
60
69
  return ABSENT
61
- return result(flow / volume)
70
+ return result(numerator / divisor)
62
71
 
63
72
 
64
73
  def balance(state: Region, ctx) -> Value:
@@ -93,6 +102,11 @@ def position_flow(ctx) -> Value:
93
102
  The two bracketed differences are formed first and subtracted, then divided by
94
103
  the span, then multiplied by the volume. **A bar whose span is not above zero
95
104
  contributes an exact 0** rather than ending the total.
105
+
106
+ A span that overflows is absent under `compiled-program.md` 3.1, and the term
107
+ with it: dividing a finite numerator by the raw infinity would give an exact
108
+ zero for a bar whose position was never computed. Any other step that
109
+ overflows stays non-finite to the end, where ``result`` catches it.
96
110
  """
97
111
  high = number(ctx.bar("high"))
98
112
  low = number(ctx.bar("low"))
@@ -100,7 +114,9 @@ def position_flow(ctx) -> Value:
100
114
  traded = number(ctx.bar("volume"))
101
115
  if high is None or low is None or close is None or traded is None:
102
116
  return ABSENT
103
- span = high - low
117
+ span = result(high - low)
118
+ if span is None:
119
+ return ABSENT
104
120
  if span <= 0:
105
121
  return 0.0
106
122
  return result((((close - low) - (high - close)) / span) * traded)
@@ -149,7 +165,24 @@ def price_trend(state: Region, ctx) -> Value:
149
165
  before = back(contributed(state, "close", close, 2), 1)
150
166
  if close is None or traded is None or not isinstance(before, float) or before == 0:
151
167
  return ABSENT
152
- return running_total(state, "run", ((close - before) / before) * traded)
168
+ return running_total(state, "run", _trend_term(close, before, traded))
169
+
170
+
171
+ def _trend_term(close: float, before: float, traded: float) -> Value:
172
+ """The term ``pvt`` adds, checked after each operation as section 3.1 states.
173
+
174
+ A change that overflows is absent although the true proportion may be
175
+ finite, and an absent term leaves the running total where it was, so the
176
+ bar costs its own reading and nothing after it. Adding the unchecked
177
+ infinity instead would store it and end the reading for good.
178
+ """
179
+ change = result(close - before)
180
+ if change is None:
181
+ return ABSENT
182
+ proportion = result(change / before)
183
+ if proportion is None:
184
+ return ABSENT
185
+ return result(proportion * traded)
153
186
 
154
187
 
155
188
  def money_flow(state: Region, ctx, length: Optional[int]) -> Value:
@@ -179,8 +179,8 @@ def directional(state: Region, ctx, di_length: Optional[int], adx_length: Option
179
179
  if isinstance(before_high, float) and isinstance(before_low, float):
180
180
  up = high - before_high
181
181
  down = before_low - low
182
- up_move = up if up > down and up > 0 else 0.0
183
- down_move = down if down > up and down > 0 else 0.0
182
+ up_move = result(up) if up > down and up > 0 else 0.0
183
+ down_move = result(down) if down > up and down > 0 else 0.0
184
184
  smoothed_up = averages.smoothed(region(state, "up"), up_move, di_length)
185
185
  smoothed_down = averages.smoothed(region(state, "down"), down_move, di_length)
186
186
  smoothed_range = averages.smoothed(region(state, "range"), gap_range(ctx), di_length)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscript
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: An open trading language: the engine that runs a compiled program.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/marketcalls/openscript#readme
@@ -118,6 +118,7 @@ tests/test_adapter_matching.py
118
118
  tests/test_adapter_page.py
119
119
  tests/test_adapter_spellings.py
120
120
  tests/test_analysis_and_runup.py
121
+ tests/test_anchored_total_overflow.py
121
122
  tests/test_arrays.py
122
123
  tests/test_bar_cycle.py
123
124
  tests/test_bars.py
@@ -129,6 +130,7 @@ tests/test_charges.py
129
130
  tests/test_colour.py
130
131
  tests/test_cost_models.py
131
132
  tests/test_diagnostics.py
133
+ tests/test_directional_recovery.py
132
134
  tests/test_engine_facts.py
133
135
  tests/test_frame_fold.py
134
136
  tests/test_frame_instant.py
@@ -13,7 +13,7 @@
13
13
 
14
14
  [project]
15
15
  name = "openscript"
16
- version = "0.7.0"
16
+ version = "0.7.2"
17
17
  description = "An open trading language: the engine that runs a compiled program."
18
18
  readme = "README.md"
19
19
  requires-python = ">=3.12"
@@ -0,0 +1,294 @@
1
+ """An overflowing term is an absent observation, and the anchored total keeps its value.
2
+
3
+ `compiled-program.md` section 3.1 makes every non-finite arithmetic result
4
+ absent, checked after each individual operation. `stdlib.md` section 20.6 says
5
+ an absent term produces an absent reading and leaves the running total where it
6
+ was. Read together, a term whose change, proportion or product overflows costs
7
+ the reading on its own bar and nothing after it. The wrong implementation these
8
+ fixtures catch adds the unchecked term, stores the infinity it produced, and is
9
+ absent on every later bar.
10
+
11
+ A total that overflows although its term was finite is a different case: the
12
+ total keeps what the arithmetic produced and every later reading is absent until
13
+ the total is started again, which is what the sibling totals already do.
14
+
15
+ The term ``ad`` and ``cmf`` share has one step whose overflow a later step could
16
+ hide: a finite numerator divided by an overflowed span is an exact zero. That
17
+ span is absent under section 3.1, so the term is absent rather than zero.
18
+ """
19
+
20
+ import copy
21
+ import json
22
+ import math
23
+ import struct
24
+ import sys
25
+ import unittest
26
+
27
+ from openscript.library import stateful_table
28
+ from tests.test_momentum_overflow import finite, operation
29
+
30
+
31
+ def trend_expected(rows):
32
+ """Batch reading of ``pvt``, one rounded operation at a time from exact operands."""
33
+ output, total, before, started = [], 0.0, None, False
34
+ for row in rows:
35
+ close, volume = row['close'], row['volume']
36
+ previous, seen = before, started
37
+ before, started = close, True
38
+ reading = None
39
+ if seen and previous is not None and previous != 0 and close is not None and volume is not None:
40
+ change = finite(operation(close, previous, '-'))
41
+ proportion = None if change is None else finite(operation(change, previous, '/'))
42
+ term = None if proportion is None else finite(operation(proportion, volume, '*'))
43
+ if term is not None:
44
+ total = operation(total, term, '+')
45
+ reading = finite(total)
46
+ output.append(reading)
47
+ return output
48
+
49
+
50
+ def anchored_expected(rows, anchors):
51
+ """Batch reading of ``vwapAnchor``: both totals reset first, then one term each."""
52
+ output, flow, traded, anchored = [], 0.0, 0.0, False
53
+ for row, anchor in zip(rows, anchors):
54
+ if anchor:
55
+ flow, traded, anchored = 0.0, 0.0, True
56
+ reading = None
57
+ price, volume = row['close'], row['volume']
58
+ if anchored and price is not None and volume is not None:
59
+ term = finite(operation(price, volume, '*'))
60
+ if term is not None:
61
+ flow = operation(flow, term, '+')
62
+ traded = operation(traded, volume, '+')
63
+ numerator, divisor = finite(flow), finite(traded)
64
+ if numerator is not None and divisor is not None and divisor != 0:
65
+ reading = finite(operation(numerator, divisor, '/'))
66
+ output.append(reading)
67
+ return output
68
+
69
+
70
+ def flow_term(row):
71
+ """The term ``ad`` and ``cmf`` share, every stated operation checked as it rounds."""
72
+ high, low, close, volume = (row[key] for key in ['high', 'low', 'close', 'volume'])
73
+ if high is None or low is None or close is None or volume is None:
74
+ return None
75
+ span = finite(operation(high, low, '-'))
76
+ if span is None:
77
+ return None
78
+ if span <= 0:
79
+ return 0.0
80
+ upper = finite(operation(close, low, '-'))
81
+ lower = finite(operation(high, close, '-'))
82
+ offset = None if upper is None or lower is None else finite(operation(upper, lower, '-'))
83
+ share = None if offset is None else finite(operation(offset, span, '/'))
84
+ return None if share is None else finite(operation(share, volume, '*'))
85
+
86
+
87
+ def accumulation_expected(rows):
88
+ """Batch reading of ``ad``: an absent term is an absent bar and the total stays."""
89
+ output, total = [], 0.0
90
+ for row in rows:
91
+ term = flow_term(row)
92
+ if term is not None:
93
+ total = operation(total, term, '+')
94
+ output.append(None if term is None else finite(total))
95
+ return output
96
+
97
+
98
+ def fraction_expected(rows, length):
99
+ """Batch reading of ``cmf``: fresh window sums, oldest first, then one division."""
100
+ terms = [flow_term(row) for row in rows]
101
+ output = []
102
+ for index in range(len(rows)):
103
+ start = index + 1 - length
104
+ flows = terms[start:index + 1] if start >= 0 else []
105
+ volumes = [row['volume'] for row in rows[start:index + 1]] if start >= 0 else []
106
+ reading = None
107
+ if flows and None not in flows and None not in volumes:
108
+ flow = traded = 0.0
109
+ for term, volume in zip(flows, volumes):
110
+ flow = operation(flow, term, '+')
111
+ traded = operation(traded, volume, '+')
112
+ flow, traded = finite(flow), finite(traded)
113
+ if flow is not None and traded is not None and traded > 0:
114
+ reading = finite(operation(flow, traded, '/'))
115
+ output.append(reading)
116
+ return output
117
+
118
+
119
+ def flow_case(name, label, triples, volume=2.0):
120
+ rows = [dict(time=1700000000000 + index * 60000, open=close, high=high, low=low,
121
+ close=close, volume=volume) for index, (high, low, close) in enumerate(triples)]
122
+ expected = accumulation_expected(rows) if name == 'ad' else fraction_expected(rows, 2)
123
+ return dict(id=f'{name}-{label}', name=name, bars=rows, anchors=[False] * len(rows), expected=expected)
124
+
125
+
126
+ def bars(closes, volumes, anchors=None):
127
+ """Host bars; ``open`` marks an anchor so a compiled script can read it."""
128
+ anchors = anchors or [False] * len(closes)
129
+ return [dict(time=1700000000000 + index * 60000, open=1.0 if anchor else 0.0,
130
+ high=close, low=close, close=close, volume=volume)
131
+ for index, (close, volume, anchor) in enumerate(zip(closes, volumes, anchors))]
132
+
133
+
134
+ def trend_case(label, closes, volumes):
135
+ rows = bars(closes, volumes)
136
+ return dict(id=f'pvt-{label}', name='pvt', bars=rows, anchors=[False] * len(rows),
137
+ expected=trend_expected(rows))
138
+
139
+
140
+ def anchored_case(label, closes, volumes, anchors):
141
+ rows = bars(closes, volumes, anchors)
142
+ return dict(id=f'vwapAnchor-{label}', name='vwapAnchor', bars=rows, anchors=list(anchors),
143
+ expected=anchored_expected(rows, anchors))
144
+
145
+
146
+ def signed(values, sign):
147
+ return [None if value is None else sign * value for value in values]
148
+
149
+
150
+ def cases():
151
+ rows = []
152
+ ordinary = [40.0 + index % 7 for index in range(40)]
153
+ for sign in [1.0, -1.0]:
154
+ tag = 'up' if sign > 0 else 'down'
155
+
156
+ def flip(values):
157
+ return signed(values, sign)
158
+
159
+ rows.append(trend_case(f'change-overflow-{tag}', flip([1.0, 1e308, -1e308, 2.0, 3.0]), [1.0] * 5))
160
+ rows.append(trend_case(f'proportion-overflow-{tag}',
161
+ flip([1.0, 2.0, 1e-300, 1e10, 2e10, 3e10]), [1.0] * 6))
162
+ rows.append(trend_case(f'product-overflow-{tag}', flip([1.0, 2.0, 6.0, 7.0, 8.0]),
163
+ [1.0, 1.0, 1e308, 1.0, 1.0]))
164
+ rows.append(trend_case(f'total-overflow-{tag}', flip([1.0, 2.0, 4.0, 8.0, 9.0]),
165
+ [1.0, 1e308, 1e308, 1.0, 1.0]))
166
+ rows.append(trend_case(f'recovery-{tag}', flip(ordinary + [-1e308, 1e308] + ordinary),
167
+ [1.0 + index % 3 for index in range(82)]))
168
+ rows.append(trend_case('holes', [10.0, 11.0, None, 12.0, 13.0, 14.0, 15.0],
169
+ [5.0, 5.0, 5.0, 5.0, None, 5.0, 5.0]))
170
+ rows.append(trend_case('zero-previous', [0.0, 1e308, 1.0, 2.0], [1.0] * 4))
171
+ rows.append(trend_case('unchanged-negative', [-5.0, -5.0, -4.0], [3.0] * 3))
172
+ for sign in [1.0, -1.0]:
173
+ tag = 'up' if sign > 0 else 'down'
174
+ rows.append(anchored_case(f'product-overflow-{tag}', [10.0 * sign, 1e308 * sign, 20.0 * sign, 30.0 * sign],
175
+ [2.0, 1e10, 6.0, 2.0], [True, False, False, False]))
176
+ rows.append(anchored_case(f'flow-overflow-{tag}', [1e308 * sign, 1e308 * sign, 5.0 * sign, 4.0 * sign],
177
+ [1.0, 1.0, 1.0, 2.0], [True, False, False, True]))
178
+ rows.append(anchored_case('traded-overflow', [1e-10, 1e-10, 5.0, 4.0, 6.0], [1e308, 1e308, 1.0, 2.0, 2.0],
179
+ [True, False, False, True, False]))
180
+ rows.append(anchored_case('overflow-before-anchor', [1e308, 10.0, 1e308, 20.0], [1e10, 2.0, 1e10, 2.0],
181
+ [False, True, False, False]))
182
+ # The span overflows on the second bar although the true term there is 1.
183
+ span = [(2.0, 0.0, 1.5), (1e308, -1e308, 5e307), (4.0, 2.0, 4.0), (5.0, 1.0, 2.0), (6.0, 2.0, 5.0)]
184
+ for sign in [1.0, -1.0]:
185
+ tag = 'up' if sign > 0 else 'down'
186
+ triples = span if sign > 0 else [(-low, -high, -close) for high, low, close in span]
187
+ rows.append(flow_case('ad', f'span-overflow-{tag}', triples))
188
+ rows.append(flow_case('cmf', f'span-overflow-{tag}', triples))
189
+ rows.append(flow_case('ad', 'outside-range', [(2.0, 0.0, 1.0), (1e308, 0.0, -1e308), (4.0, 2.0, 3.0)]))
190
+ rows.append(flow_case('ad', 'zero-span', [(2.0, 0.0, 1.5), (3.0, 3.0, 3.0), (4.0, 2.0, 4.0)]))
191
+ return rows
192
+
193
+
194
+ class Observation:
195
+ """What the stateful table reads from a bar, with the session flag for ``vwap``."""
196
+
197
+ def __init__(self, row, anchor):
198
+ self.values = {**row, 'isSessionFirst': anchor}
199
+
200
+ def bar(self, key):
201
+ return self.values.get(key)
202
+
203
+ def first_bar(self):
204
+ return False
205
+
206
+ def make_array(self, values):
207
+ return list(values)
208
+
209
+
210
+ def call(name, row, anchor, state):
211
+ if name == 'pvt':
212
+ return stateful_table()[('pvt', 0)].call(Observation(row, anchor), [], state)
213
+ if name == 'ad':
214
+ return stateful_table()[('ad', 0)].call(Observation(row, anchor), [], state)
215
+ if name == 'cmf':
216
+ return stateful_table()[('cmf', 1)].call(Observation(row, anchor), [2.0], state)
217
+ if name == 'vwap':
218
+ return stateful_table()[('vwap', 1)].call(Observation(row, anchor), [row['close']], state)
219
+ return stateful_table()[('vwapAnchor', 2)].call(Observation(row, anchor), [row['close'], anchor], state)
220
+
221
+
222
+ def encoded(values):
223
+ return [None if value is None else struct.pack('>d', value).hex() for value in values]
224
+
225
+
226
+ def readings(row, name=None, forming=False):
227
+ name = name or row['name']
228
+ state, output = {}, []
229
+ for index, (bar, anchor) in enumerate(zip(row['bars'], row['anchors'])):
230
+ if forming:
231
+ saved = copy.deepcopy(state)
232
+ draft = {**bar, 'close': 1e308 if index % 2 else -1e308, 'volume': sys.float_info.max}
233
+ call(name, draft, anchor, state)
234
+ state = copy.deepcopy(saved)
235
+ output.append(call(name, bar, anchor, state))
236
+ return output
237
+
238
+
239
+ def named(prefix):
240
+ return next(row for row in cases() if row['id'] == prefix)
241
+
242
+
243
+ class AnchoredTotalOverflow(unittest.TestCase):
244
+ def test_the_specified_trend_values_are_hand_computed(self):
245
+ # Bar 2's change overflows, so its term is absent and the total stays at
246
+ # 1e308. Bar 3 adds ((2 + 1e308) / -1e308) * 1 = -1, and 1e308 - 1
247
+ # rounds back to 1e308, as does adding bar 4's 0.5.
248
+ self.assertEqual(named('pvt-change-overflow-up')['expected'], [None, 1e308, None, 1e308, 1e308])
249
+ self.assertEqual(named('pvt-change-overflow-down')['expected'], [None, 1e308, None, 1e308, 1e308])
250
+ self.assertEqual(named('pvt-proportion-overflow-up')['expected'], [None, 1.0, 0.0, None, 1.0, 1.5])
251
+ self.assertEqual(named('pvt-product-overflow-up')['expected'],
252
+ [None, 1.0, None, 1.0 + 1.0 / 6.0, 1.0 + 1.0 / 6.0 + 1.0 / 7.0])
253
+ self.assertEqual(named('pvt-total-overflow-up')['expected'], [None, 1e308, None, None, None])
254
+ self.assertEqual(named('vwapAnchor-product-overflow-up')['expected'], [10.0, None, 17.5, 20.0])
255
+ self.assertEqual(named('vwapAnchor-traded-overflow')['expected'], [1e-10, None, None, 4.0, 5.0])
256
+ # A finite numerator over an overflowed span is not a zero term: the span
257
+ # is absent, so the bar is, and the total of 1 carries on to 3 and 2.
258
+ self.assertEqual(named('ad-span-overflow-up')['expected'], [1.0, None, 3.0, 2.0, 3.0])
259
+ self.assertEqual(named('cmf-span-overflow-up')['expected'], [None, None, None, 0.25, 0.0])
260
+ self.assertEqual(named('ad-zero-span')['expected'], [1.0, 1.0, 3.0])
261
+
262
+ def test_an_overflowing_term_leaves_the_total_where_it_was(self):
263
+ for row in cases():
264
+ with self.subTest(case=row['id']):
265
+ actual = readings(row)
266
+ self.assertEqual(encoded(actual), encoded(row['expected']))
267
+ self.assertTrue(all(value is None or math.isfinite(value) for value in actual))
268
+ if 'recovery' in row['id'] or 'product-overflow' in row['id']:
269
+ self.assertIsNotNone(actual[-1])
270
+
271
+ def test_each_prefix_keeps_the_same_availability_and_bits(self):
272
+ for row in cases():
273
+ for end in range(len(row['bars']) + 1):
274
+ with self.subTest(case=row['id'], prefix=end):
275
+ part = {**row, 'bars': row['bars'][:end], 'anchors': row['anchors'][:end]}
276
+ self.assertEqual(encoded(readings(part)), encoded(row['expected'][:end]))
277
+
278
+ def test_forming_checkpoint_revisions_do_not_contaminate_history(self):
279
+ for row in cases():
280
+ with self.subTest(case=row['id']):
281
+ self.assertEqual(encoded(readings(row, forming=True)), encoded(row['expected']))
282
+
283
+ def test_the_session_average_is_the_same_calculation(self):
284
+ for row in cases():
285
+ if row['name'] == 'vwapAnchor':
286
+ with self.subTest(case=row['id']):
287
+ self.assertEqual(encoded(readings(row, name='vwap')), encoded(row['expected']))
288
+
289
+
290
+ if __name__ == '__main__':
291
+ if sys.argv[1:] == ['--cases']:
292
+ json.dump(cases(), sys.stdout)
293
+ else:
294
+ unittest.main()
@@ -0,0 +1,180 @@
1
+ """Selected directional overflow is an absent observation before smoothing."""
2
+
3
+ import copy
4
+ import json
5
+ import math
6
+ import struct
7
+ import sys
8
+ import unittest
9
+
10
+ from openscript.library import stateful_table
11
+ from tests.test_momentum_overflow import average, finite, operation
12
+
13
+
14
+ def expected(rows, di_length, adx_length):
15
+ """Batch operation oracle, independent of directional state and dispatch."""
16
+ rises, falls, ranges = [], [], []
17
+ for index, bar in enumerate(rows):
18
+ before = rows[index - 1] if index else {}
19
+ high, low = bar['high'], bar['low']
20
+ old_high, old_low, old_close = (before.get(key) for key in ['high', 'low', 'close'])
21
+ up = down = width = None
22
+ if all(value is not None for value in [high, low, old_high, old_low]):
23
+ rise = operation(high, old_high, '-')
24
+ fall = operation(old_low, low, '-')
25
+ up = finite(rise) if rise > fall and rise > 0 else 0.0
26
+ down = finite(fall) if fall > rise and fall > 0 else 0.0
27
+ if all(value is not None for value in [high, low, old_close]):
28
+ width = finite(max(operation(high, low, '-'), abs(operation(high, old_close, '-')),
29
+ abs(operation(low, old_close, '-'))))
30
+ rises.append(up)
31
+ falls.append(down)
32
+ ranges.append(width)
33
+ rises = average(rises, di_length, True)
34
+ falls = average(falls, di_length, True)
35
+ ranges = average(ranges, di_length, True)
36
+ plus, minus, spreads = [], [], []
37
+ for up, down, width in zip(rises, falls, ranges):
38
+ plus.append(None if up is None or width in [None, 0] else
39
+ finite(operation(operation(up, width, '/'), 100.0, '*')))
40
+ minus.append(None if down is None or width in [None, 0] else
41
+ finite(operation(operation(down, width, '/'), 100.0, '*')))
42
+ a, b = plus[-1], minus[-1]
43
+ if a is None or b is None:
44
+ spreads.append(None)
45
+ else:
46
+ total = operation(a, b, '+')
47
+ spreads.append(0.0 if total == 0 else finite(operation(
48
+ operation(abs(operation(a, b, '-')), total, '/'), 100.0, '*')))
49
+ return [list(row) for row in zip(average(spreads, adx_length, True), plus, minus)]
50
+
51
+
52
+ def bars(rows):
53
+ return [dict(time=1700000000000 + index * 60000, open=close, high=high,
54
+ low=low, close=close, volume=100.0)
55
+ for index, (high, low, close) in enumerate(rows)]
56
+
57
+
58
+ def cases():
59
+ minimal = [(-1e308, -1e308, -1e308)] * 2 + [(1e308, 1e308, 1e308),
60
+ (1e308, 5e307, 1e308), (1e308, 5e307, 1e308)]
61
+ long = [minimal[0]] * 40 + minimal[2:4] + [(41.0 + i, 39.0 + i, 40.0 + i) for i in range(40)]
62
+ rows = []
63
+ for sign in [1, -1]:
64
+ for label, source, length in [('overflow', minimal, 1), ('seeded-overflow', long, 14)]:
65
+ source = source if sign == 1 else [(-low, -high, -close) for high, low, close in source]
66
+ rows.append(dict(id=f'{label}-{sign}', bars=bars(source), lengths=[length, length]))
67
+ competing = [(-1e308, -1.5e308, -1e308)] * 3 + [(1e308, -1.6e308, 0.),
68
+ (1e308, 5e307, 1e308), (1e308, 5e307, 1e308)]
69
+ for sign in [1, -1]:
70
+ for label, source, length in [('leading-overflow', minimal[1:], 1), ('competing-overflow', competing, 2)]:
71
+ source = source if sign == 1 else [(-low, -high, -close) for high, low, close in source]
72
+ rows.append(dict(id=f'{label}-{sign}', bars=bars(source), lengths=[length, length]))
73
+ rows.append(dict(id='positive-movement-tie', bars=bars([(10., 8., 9.), (11., 7., 9.),
74
+ (12., 6., 9.), (14., 7., 12.), (13., 5., 8.)]), lengths=[1, 2]))
75
+ ramp = [(41.0 + i, 39.0 + i, 40.0 + i) for i in range(9)]
76
+ for field in ['high', 'low', 'close', 'open']:
77
+ source = bars(ramp)
78
+ source[6][field] = None
79
+ rows.append(dict(id=f'missing-{field}', bars=source, lengths=[3, 3]))
80
+ rows.append(dict(id='zero-range', bars=bars([(10., 0., 5.), (11., 1., 6.), (10., 2., 6.),
81
+ (6., 6., 6.), (12., 6., 9.), (11., 5., 8.)]), lengths=[1, 2]))
82
+ rows.append(dict(id='reversal', bars=bars([(10., 8., 9.), (12., 9., 11.), (11., 7., 8.),
83
+ (13., 9., 12.), (12., 10., 11.)]), lengths=[2, 2]))
84
+ rows.append(dict(id='flat', bars=bars([(10., 10., 10.)] * 8), lengths=[2, 2]))
85
+ for row in rows:
86
+ row['expected'] = expected(row['bars'], *row['lengths'])
87
+ return rows
88
+
89
+
90
+ class Observation:
91
+ def __init__(self, row, previous, index):
92
+ self.values = {**row, 'previousClose': previous}
93
+ self.index = index
94
+
95
+ def bar(self, key):
96
+ return self.values.get(key)
97
+
98
+ def first_bar(self):
99
+ return self.index == 0
100
+
101
+ def make_array(self, values):
102
+ return list(values)
103
+
104
+
105
+ def encoded(rows):
106
+ return [[None if value is None else struct.pack('>d', value).hex() for value in row] for row in rows]
107
+
108
+
109
+ def readings(row, forming=False):
110
+ entry = stateful_table()[('adx', 2)]
111
+ state, output = {}, []
112
+ for index, bar in enumerate(row['bars']):
113
+ previous = row['bars'][index - 1]['close'] if index else None
114
+ ctx = Observation(bar, previous, index)
115
+ if forming:
116
+ saved = copy.deepcopy(state)
117
+ draft = {**bar, 'high': 1e308, 'low': -1e308, 'close': 0.0}
118
+ entry.call(Observation(draft, previous, index), row['lengths'], state)
119
+ state = copy.deepcopy(saved)
120
+ output.append(entry.call(ctx, row['lengths'], state))
121
+ return output
122
+
123
+
124
+ class DirectionalRecovery(unittest.TestCase):
125
+ def test_minimal_oracle_has_hand_computed_recovery(self):
126
+ upward = cases()[0]
127
+ self.assertEqual(upward['expected'], [[None] * 3] * 3 + [[100., 0., 100.], [0., 0., 0.]])
128
+ downward = cases()[2]
129
+ self.assertEqual(downward['expected'], [[None] * 3] * 3 + [[100., 100., 0.], [0., 0., 0.]])
130
+
131
+ def test_selected_overflow_never_poisoned_the_smoothed_state(self):
132
+ for row in cases():
133
+ with self.subTest(case=row['id']):
134
+ self.assertEqual(encoded(readings(row)), encoded(row['expected']))
135
+ if row['id'].startswith('seeded-overflow'):
136
+ self.assertTrue(all(value is not None and math.isfinite(value) for value in row['expected'][-1]))
137
+
138
+ def test_each_prefix_keeps_the_same_availability_and_bits(self):
139
+ for row in cases():
140
+ for end in range(len(row['bars']) + 1):
141
+ with self.subTest(case=row['id'], prefix=end):
142
+ self.assertEqual(encoded(readings({**row, 'bars': row['bars'][:end]})), encoded(row['expected'][:end]))
143
+
144
+ def test_forming_checkpoint_revisions_do_not_contaminate_history(self):
145
+ for row in cases():
146
+ with self.subTest(case=row['id']):
147
+ self.assertEqual(encoded(readings(row, forming=True)), encoded(row['expected']))
148
+
149
+
150
+ def compiled():
151
+ from openscript.adapter.serving import Serving
152
+ from openscript.contracts import Bar, BarState
153
+ from openscript.run import load
154
+ from openscript.values import ABSENT
155
+ payload = json.load(sys.stdin)
156
+ library = Serving()
157
+ loaded = load(payload['program'], {}, library)
158
+ if not loaded.ok:
159
+ raise AssertionError(loaded.diagnostic)
160
+ accepted, output = {}, []
161
+ for delivery in payload['deliveries']:
162
+ index = delivery['index']
163
+ raw = {key: None if value is None else float(value) for key, value in delivery['bar'].items()}
164
+ library.at_bar({**raw, 'previousClose': accepted.get(index - 1, {}).get('close')}, index == 0)
165
+ actual = loaded.run.execute_bar(index, Bar(**raw), BarState())
166
+ if not actual.ok:
167
+ raise AssertionError(actual.diagnostic)
168
+ values = [actual.columns[plot['channel']] for plot in payload['program']['outputs']['plots']]
169
+ output.append([None if value is ABSENT else struct.pack('>d', value).hex() for value in values])
170
+ accepted[index] = raw
171
+ json.dump(output, sys.stdout)
172
+
173
+
174
+ if __name__ == '__main__':
175
+ if sys.argv[1:] == ['--cases']:
176
+ json.dump(cases(), sys.stdout)
177
+ elif sys.argv[1:] == ['--compiled']:
178
+ compiled()
179
+ else:
180
+ unittest.main()
File without changes