openscript 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscript-0.7.1 → openscript-0.7.2}/PKG-INFO +1 -1
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/flows.py +38 -5
- {openscript-0.7.1 → openscript-0.7.2}/openscript.egg-info/PKG-INFO +1 -1
- {openscript-0.7.1 → openscript-0.7.2}/openscript.egg-info/SOURCES.txt +1 -0
- {openscript-0.7.1 → openscript-0.7.2}/pyproject.toml +1 -1
- openscript-0.7.2/tests/test_anchored_total_overflow.py +294 -0
- {openscript-0.7.1 → openscript-0.7.2}/README.md +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/__main__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/analysis.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/charges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/equity.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/report.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/shapes.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/statistics.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/accounting/trades.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/answers.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/channels.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/expectations.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/facts.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/matching.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/ordering.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/page.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/reading.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/reporting.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/running.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/secondary.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/serving.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/sessions.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/spellings.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/adapter/surface.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/arrays.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/bars.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/buckets.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/budget.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/canonical.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/civil.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/contracts.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/dates.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/diagnostics.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/hours.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/inputs.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/intervals.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/arithmetic.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/averages.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/bars.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/bookkeeping.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/code_points.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/colour.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/composites.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/counting.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/deviation.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/elementary.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/extremes.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/gaussian.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/history.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/hypotenuse.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/momentum.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/number_text.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/power.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/prices.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/ranges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/rounding.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/series.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/stateful.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/stateless.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/strength.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/strings.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/transcendental.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/trend.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/trigonometric.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/library/values.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/logbook.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/machine.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/memory.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/objects.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/opcodes.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/program.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/reasons.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/request_body.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/request_plan.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/requests.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/run.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/calls.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/closable.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/fills.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/holdings.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/intents.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/ledger.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/placing.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/positions.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/refusals.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/rows.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/sizing.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/strategy/statuses.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/__init__.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/bands.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/levels.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/marks.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/paints.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/plots.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/surface/published.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/values.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/verify.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/verify_code.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/verify_requests.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/verify_shape.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/verify_tables.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/version.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript/zones.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript.egg-info/dependency_links.txt +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/openscript.egg-info/top_level.txt +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/setup.cfg +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_adapter_cases.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_adapter_identity.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_adapter_matching.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_adapter_page.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_adapter_spellings.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_analysis_and_runup.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_arrays.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_bar_cycle.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_bars.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_calendar.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_calendar_reads.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_calls.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_canonical.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_charges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_colour.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_cost_models.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_diagnostics.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_directional_recovery.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_engine_facts.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_frame_fold.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_frame_instant.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_hand_over.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_harness.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_host_surface.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_hypot.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_ledger_orders.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_library_vectors.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_logbook.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_machine.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_maths_rules.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_memory.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_mfi_numeric_edges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_momentum_overflow.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_number_text.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_objects.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_opcodes.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_order_page.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_order_refusals.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_power.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_programs.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_range_numeric_edges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_recursive_overflow.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_request_host.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_requests.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_series_rules.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_session_window.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_sizing_refusal.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_stateful_numeric_edges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_stateful_vectors.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_statistics_overflow.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_strategy_cases.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_strategy_profile.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_string_ceiling.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_strings.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_supplied_schedule.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_surface.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_surface_bands.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_surface_example.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_time_programs.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_trades_and_summary.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_transcendental.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_trend_numeric_edges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_trigonometric.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_values.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_varying_distance.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_varying_lookback.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_varying_occurrence.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_varying_recursive.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_verify.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_version.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_volume_numeric_edges.py +0 -0
- {openscript-0.7.1 → openscript-0.7.2}/tests/test_zero_boundaries.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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", (
|
|
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:
|
|
@@ -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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|