openscript 0.4.0__tar.gz → 0.6.0__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.4.0 → openscript-0.6.0}/PKG-INFO +82 -82
- {openscript-0.4.0 → openscript-0.6.0}/openscript/__init__.py +3 -1
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/answers.py +87 -16
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/facts.py +47 -11
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/running.py +54 -29
- openscript-0.6.0/openscript/adapter/secondary.py +72 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/serving.py +72 -16
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/sessions.py +12 -8
- openscript-0.6.0/openscript/adapter/surface.py +54 -0
- openscript-0.6.0/openscript/arrays.py +283 -0
- openscript-0.6.0/openscript/buckets.py +152 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/budget.py +3 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/contracts.py +11 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/diagnostics.py +6 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/intervals.py +10 -22
- {openscript-0.4.0 → openscript-0.6.0}/openscript/machine.py +11 -25
- openscript-0.6.0/openscript/objects.py +297 -0
- openscript-0.6.0/openscript/reasons.py +49 -0
- openscript-0.6.0/openscript/request_body.py +229 -0
- openscript-0.6.0/openscript/request_plan.py +226 -0
- openscript-0.6.0/openscript/requests.py +284 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/run.py +125 -12
- {openscript-0.4.0 → openscript-0.6.0}/openscript/values.py +12 -7
- {openscript-0.4.0 → openscript-0.6.0}/openscript/verify.py +14 -8
- {openscript-0.4.0 → openscript-0.6.0}/openscript.egg-info/PKG-INFO +82 -82
- {openscript-0.4.0 → openscript-0.6.0}/openscript.egg-info/SOURCES.txt +15 -0
- {openscript-0.4.0 → openscript-0.6.0}/pyproject.toml +1 -1
- {openscript-0.4.0 → openscript-0.6.0}/setup.cfg +4 -4
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_adapter_cases.py +7 -5
- openscript-0.6.0/tests/test_adapter_identity.py +125 -0
- openscript-0.6.0/tests/test_arrays.py +144 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_diagnostics.py +12 -7
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_engine_facts.py +4 -1
- openscript-0.6.0/tests/test_hand_over.py +71 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_host_surface.py +441 -441
- openscript-0.6.0/tests/test_objects.py +173 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_programs.py +26 -12
- openscript-0.6.0/tests/test_request_host.py +273 -0
- openscript-0.6.0/tests/test_requests.py +160 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_values.py +11 -11
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_verify.py +4 -2
- {openscript-0.4.0 → openscript-0.6.0}/README.md +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/__main__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/__init__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/analysis.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/charges.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/equity.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/report.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/shapes.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/statistics.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/accounting/trades.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/__init__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/channels.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/expectations.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/matching.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/ordering.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/page.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/reading.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/reporting.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/adapter/spellings.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/bars.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/canonical.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/civil.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/dates.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/hours.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/inputs.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/__init__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/arithmetic.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/averages.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/bars.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/bookkeeping.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/code_points.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/colour.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/composites.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/counting.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/deviation.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/elementary.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/extremes.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/flows.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/momentum.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/number_text.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/prices.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/ranges.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/rounding.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/series.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/stateful.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/stateless.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/strength.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/strings.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/trend.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/library/values.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/logbook.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/memory.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/opcodes.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/program.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/__init__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/calls.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/closable.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/fills.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/holdings.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/intents.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/ledger.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/placing.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/positions.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/refusals.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/rows.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/sizing.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/strategy/statuses.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/__init__.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/bands.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/levels.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/marks.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/paints.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/plots.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/surface/published.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/verify_code.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/verify_requests.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/verify_shape.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/verify_tables.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/version.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript/zones.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript.egg-info/dependency_links.txt +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/openscript.egg-info/top_level.txt +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_adapter_matching.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_adapter_page.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_adapter_spellings.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_analysis_and_runup.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_bar_cycle.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_bars.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_calendar.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_calendar_reads.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_calls.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_canonical.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_charges.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_colour.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_cost_models.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_frame_fold.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_frame_instant.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_harness.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_ledger_orders.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_library_vectors.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_logbook.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_machine.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_maths_rules.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_memory.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_number_text.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_opcodes.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_order_page.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_order_refusals.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_series_rules.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_session_window.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_sizing_refusal.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_stateful_vectors.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_strategy_cases.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_strategy_profile.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_string_ceiling.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_strings.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_supplied_schedule.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_surface.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_surface_bands.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_surface_example.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_time_programs.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_trades_and_summary.py +0 -0
- {openscript-0.4.0 → openscript-0.6.0}/tests/test_version.py +0 -0
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: openscript
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: An open trading language: the engine that runs a compiled program.
|
|
5
|
-
License-Expression: Apache-2.0
|
|
6
|
-
Project-URL: Homepage, https://github.com/marketcalls/openscript#readme
|
|
7
|
-
Project-URL: Source, https://github.com/marketcalls/openscript
|
|
8
|
-
Keywords: trading,language,interpreter,indicators,backtesting
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
-
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
12
|
-
Requires-Python: >=3.12
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
|
-
# openscript
|
|
16
|
-
|
|
17
|
-
The engine that runs a compiled OpenScript program, in Python.
|
|
18
|
-
|
|
19
|
-
OpenScript is an open trading language. A script is compiled to a **compiled
|
|
20
|
-
program**: plain data, an instruction list, never generated code. This package
|
|
21
|
-
runs one.
|
|
22
|
-
|
|
23
|
-
It exists so that a platform can run a strategy where a JavaScript runtime is
|
|
24
|
-
not available, which for a production trading server is the ordinary case. The
|
|
25
|
-
compiler and the first engine are the `openalgo-script` package on npm; this is
|
|
26
|
-
the second engine, and the two are held to each other by a conformance suite
|
|
27
|
-
where any disagreement is a release blocker.
|
|
28
|
-
|
|
29
|
-
## What it is
|
|
30
|
-
|
|
31
|
-
- **No compiler.** This package is handed a compiled program and never a script.
|
|
32
|
-
The program arrives as canonical text, which is what a run's hash is taken
|
|
33
|
-
over, so an engine cannot quietly run something other than what was recorded.
|
|
34
|
-
- **Nothing builds code out of text.** No string evaluator, no statement
|
|
35
|
-
executor, no import driven by hand, no object graph loaded out of bytes. That
|
|
36
|
-
is what lets a platform run many people's scripts in one process, and it is
|
|
37
|
-
enforced by a check rather than intended.
|
|
38
|
-
- **Zero dependencies.** The standard library only, and not the parts of it that
|
|
39
|
-
stop a run being reproducible. Measured on every build against the module
|
|
40
|
-
names the running interpreter says are its own.
|
|
41
|
-
|
|
42
|
-
## Installing
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
pip install openscript
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Python 3.12 or newer. Nothing else.
|
|
49
|
-
|
|
50
|
-
## Using it
|
|
51
|
-
|
|
52
|
-
```python
|
|
53
|
-
from openscript.run import load_text
|
|
54
|
-
|
|
55
|
-
loaded = load_text(program_text, settings, library)
|
|
56
|
-
if loaded.ok:
|
|
57
|
-
result = loaded.run.execute_bar(0, bar)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
A host loads a program once and pushes bars at it one at a time, keeping a
|
|
61
|
-
checkpoint so a bar that is still moving can be executed again and rolled back.
|
|
62
|
-
The conformance adapter is the other way in:
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
python -m openscript --describe
|
|
66
|
-
python -m openscript <case-directory>
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Where the documentation is
|
|
70
|
-
|
|
71
|
-
The specification and the guides live in the repository:
|
|
72
|
-
|
|
73
|
-
- `docs/integrating/the-python-engine.md` for what is in this package and what a
|
|
74
|
-
host needs.
|
|
75
|
-
- `docs/integrating/running-a-strategy.md` for driving it bar by bar: the load,
|
|
76
|
-
the bar cycle, the rollback a moving bar rests on, and the order boundary.
|
|
77
|
-
- `spec/` for the language, the compiled program format, the standard library
|
|
78
|
-
and the conformance suite.
|
|
79
|
-
|
|
80
|
-
## Licence
|
|
81
|
-
|
|
82
|
-
Apache-2.0.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openscript
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: An open trading language: the engine that runs a compiled program.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/marketcalls/openscript#readme
|
|
7
|
+
Project-URL: Source, https://github.com/marketcalls/openscript
|
|
8
|
+
Keywords: trading,language,interpreter,indicators,backtesting
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# openscript
|
|
16
|
+
|
|
17
|
+
The engine that runs a compiled OpenScript program, in Python.
|
|
18
|
+
|
|
19
|
+
OpenScript is an open trading language. A script is compiled to a **compiled
|
|
20
|
+
program**: plain data, an instruction list, never generated code. This package
|
|
21
|
+
runs one.
|
|
22
|
+
|
|
23
|
+
It exists so that a platform can run a strategy where a JavaScript runtime is
|
|
24
|
+
not available, which for a production trading server is the ordinary case. The
|
|
25
|
+
compiler and the first engine are the `openalgo-script` package on npm; this is
|
|
26
|
+
the second engine, and the two are held to each other by a conformance suite
|
|
27
|
+
where any disagreement is a release blocker.
|
|
28
|
+
|
|
29
|
+
## What it is
|
|
30
|
+
|
|
31
|
+
- **No compiler.** This package is handed a compiled program and never a script.
|
|
32
|
+
The program arrives as canonical text, which is what a run's hash is taken
|
|
33
|
+
over, so an engine cannot quietly run something other than what was recorded.
|
|
34
|
+
- **Nothing builds code out of text.** No string evaluator, no statement
|
|
35
|
+
executor, no import driven by hand, no object graph loaded out of bytes. That
|
|
36
|
+
is what lets a platform run many people's scripts in one process, and it is
|
|
37
|
+
enforced by a check rather than intended.
|
|
38
|
+
- **Zero dependencies.** The standard library only, and not the parts of it that
|
|
39
|
+
stop a run being reproducible. Measured on every build against the module
|
|
40
|
+
names the running interpreter says are its own.
|
|
41
|
+
|
|
42
|
+
## Installing
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
pip install openscript
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Python 3.12 or newer. Nothing else.
|
|
49
|
+
|
|
50
|
+
## Using it
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from openscript.run import load_text
|
|
54
|
+
|
|
55
|
+
loaded = load_text(program_text, settings, library)
|
|
56
|
+
if loaded.ok:
|
|
57
|
+
result = loaded.run.execute_bar(0, bar)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A host loads a program once and pushes bars at it one at a time, keeping a
|
|
61
|
+
checkpoint so a bar that is still moving can be executed again and rolled back.
|
|
62
|
+
The conformance adapter is the other way in:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
python -m openscript --describe
|
|
66
|
+
python -m openscript <case-directory>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Where the documentation is
|
|
70
|
+
|
|
71
|
+
The specification and the guides live in the repository:
|
|
72
|
+
|
|
73
|
+
- `docs/integrating/the-python-engine.md` for what is in this package and what a
|
|
74
|
+
host needs.
|
|
75
|
+
- `docs/integrating/running-a-strategy.md` for driving it bar by bar: the load,
|
|
76
|
+
the bar cycle, the rollback a moving bar rests on, and the order boundary.
|
|
77
|
+
- `spec/` for the language, the compiled program format, the standard library
|
|
78
|
+
and the conformance suite.
|
|
79
|
+
|
|
80
|
+
## Licence
|
|
81
|
+
|
|
82
|
+
Apache-2.0.
|
|
@@ -16,7 +16,9 @@ What is where, for somebody reading this engine rather than running it:
|
|
|
16
16
|
memory regions and their lifetimes, the values, the bars,
|
|
17
17
|
the inputs, the verification done before the first bar,
|
|
18
18
|
the execution budget, the canonical encoding, the
|
|
19
|
-
diagnostics and the two version numbers
|
|
19
|
+
diagnostics and the two version numbers, and the reads
|
|
20
|
+
of section 2.16: a timeframe's buckets, a read's plan
|
|
21
|
+
and body, and the fold that puts its value on the chart.
|
|
20
22
|
- ``library/`` the functions of ``spec/stdlib.md``, each accumulating in
|
|
21
23
|
the order that page fixes, in the two halves that page
|
|
22
24
|
divides them into: the calls that remember nothing and
|
|
@@ -30,11 +30,12 @@ have it say. The stage's report records that as a defect of the page rather than
|
|
|
30
30
|
leaving it to be discovered.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
import email.parser
|
|
33
34
|
import json
|
|
34
35
|
import sys
|
|
35
36
|
import tomllib
|
|
36
37
|
from pathlib import Path
|
|
37
|
-
from typing import Any, Dict, Optional
|
|
38
|
+
from typing import Any, Callable, Dict, Mapping, Optional
|
|
38
39
|
|
|
39
40
|
from ..version import FORMAT, LANGUAGE_VERSIONS
|
|
40
41
|
from .expectations import expected_channels
|
|
@@ -47,6 +48,11 @@ from .spellings import Malformed
|
|
|
47
48
|
#: The distribution file beside the package, which states the version once.
|
|
48
49
|
_PROJECT = "pyproject.toml"
|
|
49
50
|
|
|
51
|
+
#: The distribution this engine is, as the build and the installer name it. It is
|
|
52
|
+
#: named for its top package, so the name is read from where this module sits
|
|
53
|
+
#: rather than written out a second time beside the one in pyproject.toml.
|
|
54
|
+
_NAME = __name__.split(".", 1)[0]
|
|
55
|
+
|
|
50
56
|
#: Section 8: what this implementation claims. The profile that covers the cases
|
|
51
57
|
#: it runs, which is the one a case it would report ``unsupported`` is not in.
|
|
52
58
|
#:
|
|
@@ -65,24 +71,89 @@ PROFILE = PROFILES[2]
|
|
|
65
71
|
|
|
66
72
|
|
|
67
73
|
def _distribution() -> Dict[str, Any]:
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
The version is a fact of the release
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
"""The engine's name and version, from wherever this copy of it was built.
|
|
75
|
+
|
|
76
|
+
The version is a fact of the release, stated once in the distribution file,
|
|
77
|
+
and ``scripts/check-python.mjs`` holds that file and the package manifest
|
|
78
|
+
equal, so nothing read here is a third copy.
|
|
79
|
+
"""
|
|
80
|
+
return _identity(Path(__file__).resolve().parent.parent.parent / _PROJECT, _installed)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _installed(name: str, site: Optional[Path] = None) -> Optional[Mapping[str, str]]:
|
|
84
|
+
"""The metadata an installer wrote beside this package, or ``None`` if none did.
|
|
85
|
+
|
|
86
|
+
Every installer records a distribution in ``<name>-<version>.dist-info``
|
|
87
|
+
beside the package it installed, and ``METADATA`` there holds the name and
|
|
88
|
+
version the build wrote. It is read directly, with the standard library's
|
|
89
|
+
own header parser, rather than through the import system's metadata finder:
|
|
90
|
+
this project refuses anything that loads modules by a name computed at run
|
|
91
|
+
time, and reading two headers out of one file does not need it.
|
|
92
|
+
|
|
93
|
+
Two records beside one package is a broken upgrade, and there is no telling
|
|
94
|
+
which of them describes the files actually installed, so that is refused
|
|
95
|
+
by name rather than resolved by picking one.
|
|
96
|
+
"""
|
|
97
|
+
if site is None:
|
|
98
|
+
site = Path(__file__).resolve().parent.parent.parent
|
|
99
|
+
records = sorted(site.glob(f"{name}-*.dist-info/METADATA"))
|
|
100
|
+
if not records:
|
|
101
|
+
return None
|
|
102
|
+
if len(records) > 1:
|
|
103
|
+
raise Malformed(
|
|
104
|
+
f"{len(records)} installed records for {name!r} sit beside this package "
|
|
105
|
+
f"({', '.join(record.parent.name for record in records)}), so its version cannot "
|
|
106
|
+
"be told. Reinstall it so that one remains."
|
|
107
|
+
)
|
|
108
|
+
with records[0].open(encoding="utf-8") as file:
|
|
109
|
+
return email.parser.Parser().parse(file, headersonly=True)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _identity(
|
|
113
|
+
project_file: Path, installed: Callable[[str], Optional[Mapping[str, str]]]
|
|
114
|
+
) -> Dict[str, Any]:
|
|
115
|
+
"""Read the identity from ``project_file``, else from the installed distribution.
|
|
116
|
+
|
|
117
|
+
**Two places, because a copy of this engine lives in one of two.** A checkout
|
|
118
|
+
has ``pyproject.toml`` beside the package, which is what the conformance suite
|
|
119
|
+
runs. An installed copy does not: the build reads that file and records what
|
|
120
|
+
it says in the distribution's own metadata, and the file itself is never
|
|
121
|
+
installed. This used to read only the first, so ``python -m openscript
|
|
122
|
+
--describe`` failed on every installed copy, 0.5.0 from the index included,
|
|
123
|
+
and no host could run the suite against the engine it had actually installed.
|
|
124
|
+
|
|
125
|
+
The installed metadata is not an invented version. It is the one the wheel
|
|
126
|
+
was built with, written by the same build from the same file, and it is what
|
|
127
|
+
the installer, the index and ``pip show`` all report for this copy.
|
|
128
|
+
|
|
129
|
+
**A file that names some other project is not this one's.** Beside an
|
|
130
|
+
installed package, the directory above is ``site-packages``, where a stray
|
|
131
|
+
``pyproject.toml`` from anything else would otherwise be read as this
|
|
132
|
+
engine's identity. The name is checked, and a mismatch falls through.
|
|
133
|
+
|
|
134
|
+
Neither available is still a refusal that names both, because an identity
|
|
135
|
+
with a guessed version in it is worse than an adapter saying what it could
|
|
136
|
+
not find.
|
|
76
137
|
"""
|
|
77
|
-
|
|
138
|
+
reasons = []
|
|
78
139
|
try:
|
|
79
|
-
with
|
|
80
|
-
|
|
140
|
+
with project_file.open("rb") as file:
|
|
141
|
+
project = tomllib.load(file)["project"]
|
|
142
|
+
if project.get("name") == _NAME:
|
|
143
|
+
return project
|
|
144
|
+
reasons.append(f"{project_file} names {project.get('name')!r}, not {_NAME!r}")
|
|
81
145
|
except (OSError, KeyError, ValueError) as reason:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
146
|
+
reasons.append(f"{project_file} could not be read: {reason}")
|
|
147
|
+
|
|
148
|
+
recorded = installed(_NAME)
|
|
149
|
+
if recorded is not None and recorded.get("Name") and recorded.get("Version"):
|
|
150
|
+
return {"name": recorded["Name"], "version": recorded["Version"]}
|
|
151
|
+
reasons.append(f"no installed distribution named {_NAME!r} was found")
|
|
152
|
+
|
|
153
|
+
raise Malformed(
|
|
154
|
+
"section 9 has an adapter answer with the engine's own name and version, and "
|
|
155
|
+
+ "; ".join(reasons)
|
|
156
|
+
)
|
|
86
157
|
|
|
87
158
|
|
|
88
159
|
def describe() -> Dict[str, Any]:
|
|
@@ -14,18 +14,20 @@ for every entry but one: ``chart.symbol`` is the empty string, because 3.4 says
|
|
|
14
14
|
so. A default anywhere else would make "the smallest increment is five paise"
|
|
15
15
|
and "nobody said" the same reading.
|
|
16
16
|
|
|
17
|
-
**The two derived facts are
|
|
18
|
-
``chart.isIntraday`` are
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
**The two derived facts are computed from the interval, never read.**
|
|
18
|
+
``chart.intervalMinutes`` and ``chart.isIntraday`` are derived from the one
|
|
19
|
+
interval string the host stated, so neither can disagree with it, by the rule the
|
|
20
|
+
first engine applies: a count, then an optional unit of seconds, minutes, hours,
|
|
21
|
+
days or weeks, where a count alone is minutes, and a month has no answer because
|
|
22
|
+
it is no fixed number of minutes. The planned entries of both tables are absent:
|
|
23
|
+
a name the language does not serve yet is a refusal naming it (OS6004).
|
|
23
24
|
"""
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
import re
|
|
27
|
+
from typing import Any, Dict, Mapping, Optional, Protocol, Tuple
|
|
26
28
|
|
|
27
29
|
from ..values import ABSENT
|
|
28
|
-
from .sessions import SESSION_FACTS
|
|
30
|
+
from .sessions import SESSION_FACT_OF, SESSION_FACTS
|
|
29
31
|
|
|
30
32
|
#: Section 3.4 against ``host-interface.md`` 4.1: the call, and the fact of the
|
|
31
33
|
#: instrument record it reads. Every one is arity 0, holds no state and has no
|
|
@@ -115,13 +117,47 @@ def position_value(name: str, book: Book) -> Any:
|
|
|
115
117
|
#: 0, hold no state and have no effect, which is what section 2.5's check holds a
|
|
116
118
|
#: program's own library table to. The session's own entry is beside them because
|
|
117
119
|
#: it is read the same way and answered from the bar rather than from a record.
|
|
118
|
-
|
|
120
|
+
DERIVED_FACTS: Tuple[str, ...] = ("chart.intervalMinutes", "chart.isIntraday")
|
|
121
|
+
|
|
122
|
+
FACT_NAMES: Tuple[str, ...] = (
|
|
123
|
+
*CHART_FACTS,
|
|
124
|
+
CHART_CLOCK,
|
|
125
|
+
*DERIVED_FACTS,
|
|
126
|
+
*POSITION_FACTS,
|
|
127
|
+
*SESSION_FACTS,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
#: A count, then an optional unit. A count alone is minutes.
|
|
131
|
+
_INTERVAL = re.compile(r"^(\d+)([smhDWM]?)$")
|
|
132
|
+
|
|
133
|
+
#: Minutes in one of each unit. A month is absent from it on purpose.
|
|
134
|
+
_MINUTES = {"s": 1 / 60, "": 1, "m": 1, "h": 60, "D": 60 * 24, "W": 60 * 24 * 7}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def interval_minutes(interval: Any) -> Any:
|
|
138
|
+
"""Minutes in an interval code, or absent for one that has no fixed length."""
|
|
139
|
+
if not isinstance(interval, str):
|
|
140
|
+
return ABSENT
|
|
141
|
+
found = _INTERVAL.match(interval)
|
|
142
|
+
if found is None or found.group(2) not in _MINUTES:
|
|
143
|
+
return ABSENT
|
|
144
|
+
return float(int(found.group(1)) * _MINUTES[found.group(2)])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def derived_value(name: str, instrument: Any) -> Any:
|
|
148
|
+
"""``chart.intervalMinutes`` or ``chart.isIntraday``, from the stated interval."""
|
|
149
|
+
minutes = interval_minutes(instrument.get("interval", ABSENT))
|
|
150
|
+
if name == "chart.intervalMinutes" or minutes is ABSENT:
|
|
151
|
+
return minutes
|
|
152
|
+
return minutes < 60 * 24
|
|
119
153
|
|
|
120
154
|
|
|
121
|
-
def fact_value(name: str, instrument: Any, now: Any, book: Book, bar: Any) -> Any:
|
|
155
|
+
def fact_value(name: str, instrument: Any, now: Any, book: Book, bar: Mapping[str, Any]) -> Any:
|
|
122
156
|
"""Whichever of the three namespaces this name belongs to, read once."""
|
|
123
157
|
if name in POSITION_FACTS:
|
|
124
158
|
return position_value(name, book)
|
|
125
159
|
if name in SESSION_FACTS:
|
|
126
|
-
return bar
|
|
160
|
+
return bar.get(SESSION_FACT_OF[name], ABSENT)
|
|
161
|
+
if name in DERIVED_FACTS:
|
|
162
|
+
return derived_value(name, instrument)
|
|
127
163
|
return chart_value(name, instrument, now)
|
|
@@ -25,7 +25,10 @@ case can assert them.
|
|
|
25
25
|
**The bars are confirmed and are not live.** A case is a run over a dataset, so
|
|
26
26
|
every execution is a new, confirmed, non-realtime bar with one update, and the
|
|
27
27
|
count supplied is the whole file: that is what makes ``bar.isLast`` true on the
|
|
28
|
-
last row and false everywhere else.
|
|
28
|
+
last row and false everywhere else. The whole file is also handed to the fold
|
|
29
|
+
before bar 0, as a history is, which a ``"lookahead"`` read is the one reading
|
|
30
|
+
to notice; a read of another instrument is answered from the case's own file
|
|
31
|
+
(``secondary.py``). An engine that only backtests is handed
|
|
29
32
|
``isRealtime`` false throughout, which is why no alert is raised here, and
|
|
30
33
|
``run.py`` reaches the same answer from the other side.
|
|
31
34
|
|
|
@@ -42,25 +45,32 @@ is here is the order the three are asked in.
|
|
|
42
45
|
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
43
46
|
|
|
44
47
|
from ..accounting import report_of
|
|
45
|
-
from ..
|
|
48
|
+
from ..buckets import is_intraday
|
|
49
|
+
from ..contracts import BarState
|
|
50
|
+
from ..dates import BAR_TIME, NAMES as DATE_NAMES
|
|
46
51
|
from ..inputs import utc_time
|
|
52
|
+
from ..logbook import Logbook
|
|
47
53
|
from ..run import load_text
|
|
48
54
|
from ..strategy import IntentBar
|
|
49
55
|
from ..values import ABSENT
|
|
50
56
|
from ..verify import capabilities
|
|
51
57
|
from .channels import orders_channel, performance_channel, trade_row
|
|
52
58
|
from .ordering import ORDER_ENTRIES, Desk, options_for, unfoldable
|
|
53
|
-
from .reading import
|
|
59
|
+
from .reading import Case
|
|
54
60
|
from .reporting import contract_for, marks_for, schedule_for, settings_problem
|
|
61
|
+
from .secondary import engine_bar, provider_for
|
|
55
62
|
from .serving import Serving, is_reference
|
|
56
63
|
from .sessions import (
|
|
57
64
|
READABLE_ZONE,
|
|
58
65
|
SESSION_FACTS,
|
|
59
66
|
SESSION_FIRST,
|
|
67
|
+
SESSION_LAST,
|
|
60
68
|
first_bars,
|
|
69
|
+
last_bars,
|
|
61
70
|
session_from,
|
|
62
71
|
)
|
|
63
72
|
from .spellings import Malformed, as_reported
|
|
73
|
+
from .surface import DRAWINGS, SURFACE_TAGS, TABLE, drawings_channel, table_channel
|
|
64
74
|
|
|
65
75
|
#: The codes a load raises when the program needs something this engine does not
|
|
66
76
|
#: have, each with the field naming what it was.
|
|
@@ -75,7 +85,7 @@ _UNSUPPORTED_CODES = {
|
|
|
75
85
|
#: assert is named ``unsupported`` on the case rather than answered emptily,
|
|
76
86
|
#: because an empty channel compares equal to an empty expectation and would be a
|
|
77
87
|
#: pass nobody earned.
|
|
78
|
-
ANSWERED = ("diagnostics", "values", "orders", "trades", "performance")
|
|
88
|
+
ANSWERED = ("diagnostics", "values", "orders", "trades", "performance", "log", DRAWINGS, TABLE)
|
|
79
89
|
|
|
80
90
|
#: ``compiled-program.md`` 2.2's tag for a program that places orders, and the
|
|
81
91
|
#: word the meta uses for a program that is one. This engine serves the tag
|
|
@@ -143,19 +153,6 @@ def _unsupported_from(diagnostic: Any) -> Optional[str]:
|
|
|
143
153
|
return sentence.format(value=value)
|
|
144
154
|
|
|
145
155
|
|
|
146
|
-
def _engine_bar(bar: Bar) -> EngineBar:
|
|
147
|
-
"""One row of the file as the engine's own bar. An absent field stays absent."""
|
|
148
|
-
return EngineBar(
|
|
149
|
-
time=float(bar.time),
|
|
150
|
-
open=bar.open,
|
|
151
|
-
high=bar.high,
|
|
152
|
-
low=bar.low,
|
|
153
|
-
close=bar.close,
|
|
154
|
-
volume=bar.volume,
|
|
155
|
-
oi=ABSENT,
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
|
|
159
156
|
def _plot_channels(program: Dict[str, Any]) -> Dict[str, int]:
|
|
160
157
|
"""Every plot's legend title and its stable key, against its channel.
|
|
161
158
|
|
|
@@ -203,21 +200,30 @@ def _values_channel(
|
|
|
203
200
|
return out
|
|
204
201
|
|
|
205
202
|
|
|
206
|
-
def _unreadable_zone(
|
|
203
|
+
def _unreadable_zone(run: Any, instrument: Dict[str, Any]) -> Optional[str]:
|
|
207
204
|
"""What this program reads in a calendar this engine cannot read, or nothing.
|
|
208
205
|
|
|
209
206
|
``stdlib.md`` section 12.1 reads a calendar field in the chart's timezone,
|
|
210
207
|
which the host states; this engine carries a reader for one zone and a host
|
|
211
208
|
with any other supplies its own. A case in another zone whose script takes a
|
|
212
|
-
written time,
|
|
213
|
-
|
|
209
|
+
written time, asks where a session begins or folds a read by the calendar
|
|
210
|
+
(``buckets.py``) is therefore a case this engine would answer under the wrong
|
|
211
|
+
calendar, and it says so instead.
|
|
214
212
|
"""
|
|
215
213
|
if instrument.get("timezone") == READABLE_ZONE:
|
|
216
214
|
return None
|
|
215
|
+
program = run.program.raw
|
|
216
|
+
# A day, week or month read is dated in the zone as well. With no zone at
|
|
217
|
+
# all it is the read neither engine can date, which both answer alike.
|
|
218
|
+
zoned = instrument.get("timezone") is not None
|
|
219
|
+
if zoned and any(not is_intraday(one.timeframe) for one in run.plans):
|
|
220
|
+
return "a day, week or month read"
|
|
217
221
|
if any(one["kind"] == "time" for one in program["inputs"]):
|
|
218
222
|
return "a time input"
|
|
219
223
|
if any(one["name"] in SESSION_FACTS for one in program["lib"]["functions"]):
|
|
220
224
|
return "a session boundary"
|
|
225
|
+
if any(one["name"] in DATE_NAMES for one in program["lib"]["functions"]):
|
|
226
|
+
return "a calendar call"
|
|
221
227
|
return None
|
|
222
228
|
|
|
223
229
|
|
|
@@ -238,8 +244,6 @@ def run_case(case: Case, program_text: str) -> Answer:
|
|
|
238
244
|
"how a tick row becomes the newest bar's four prices is not written down anywhere, so "
|
|
239
245
|
"a replay here would be this adapter's invention"
|
|
240
246
|
)
|
|
241
|
-
for name in case.secondary:
|
|
242
|
-
answer.cannot(f"{name} (section 3): this engine is handed one series and reads no other")
|
|
243
247
|
if case.frames is not None and case.bars is not None:
|
|
244
248
|
beyond = unfoldable(case.frames, len(case.bars))
|
|
245
249
|
if beyond:
|
|
@@ -264,8 +268,10 @@ def run_case(case: Case, program_text: str) -> Answer:
|
|
|
264
268
|
program_text,
|
|
265
269
|
dict(case.settings),
|
|
266
270
|
serving,
|
|
267
|
-
capabilities=capabilities(ORDERS),
|
|
271
|
+
capabilities=capabilities(ORDERS, *SURFACE_TAGS),
|
|
268
272
|
read_time=utc_time,
|
|
273
|
+
instrument=case.instrument,
|
|
274
|
+
provider=provider_for(case),
|
|
269
275
|
)
|
|
270
276
|
if loaded.diagnostic is not None:
|
|
271
277
|
feature = _unsupported_from(loaded.diagnostic)
|
|
@@ -280,7 +286,7 @@ def run_case(case: Case, program_text: str) -> Answer:
|
|
|
280
286
|
|
|
281
287
|
run = loaded.run
|
|
282
288
|
raw = run.program.raw
|
|
283
|
-
unreadable = _unreadable_zone(
|
|
289
|
+
unreadable = _unreadable_zone(run, case.instrument)
|
|
284
290
|
if unreadable is not None:
|
|
285
291
|
answer.cannot(
|
|
286
292
|
f"{unreadable} under the timezone {case.instrument.get('timezone')}: this engine reads "
|
|
@@ -324,7 +330,8 @@ def run_case(case: Case, program_text: str) -> Answer:
|
|
|
324
330
|
answer.channels = _with_empty(case, {"diagnostics": [_refusal_row(refused)]})
|
|
325
331
|
return answer
|
|
326
332
|
|
|
327
|
-
|
|
333
|
+
logbook = Logbook()
|
|
334
|
+
rows, diagnostics = _every_bar(run, serving, desk, case, logbook)
|
|
328
335
|
answered: Dict[str, Any] = {"diagnostics": diagnostics}
|
|
329
336
|
if "values" in case.asserts:
|
|
330
337
|
if not case.expected_columns:
|
|
@@ -335,6 +342,14 @@ def run_case(case: Case, program_text: str) -> Answer:
|
|
|
335
342
|
answered["values"] = _values_channel(case.expected_columns, raw, rows, answer)
|
|
336
343
|
if "orders" in case.asserts:
|
|
337
344
|
answered["orders"] = orders_channel(desk.rows(), desk.intents)
|
|
345
|
+
if "log" in case.asserts:
|
|
346
|
+
# conformance.md section 4: the bar, its time, and the value spelled as a
|
|
347
|
+
# cell of the values channel is, absence included.
|
|
348
|
+
answered["log"] = [dict(one, value=as_reported(one["value"])) for one in logbook.rows()]
|
|
349
|
+
if DRAWINGS in case.asserts:
|
|
350
|
+
answered[DRAWINGS] = drawings_channel(run.objects)
|
|
351
|
+
if TABLE in case.asserts:
|
|
352
|
+
answered[TABLE] = table_channel(run.objects)
|
|
338
353
|
if "trades" in case.asserts or "performance" in case.asserts:
|
|
339
354
|
report = report_of(
|
|
340
355
|
desk.fills.settled(),
|
|
@@ -394,7 +409,7 @@ def _declaration(run: Any) -> Dict[str, Any]:
|
|
|
394
409
|
|
|
395
410
|
|
|
396
411
|
def _every_bar(
|
|
397
|
-
run: Any, serving: Serving, desk: Desk, case: Case
|
|
412
|
+
run: Any, serving: Serving, desk: Desk, case: Case, logbook: Logbook
|
|
398
413
|
) -> Tuple[List[List[Any]], List[Dict[str, Any]]]:
|
|
399
414
|
"""Every bar of the file, in order, stopping at the first that fails.
|
|
400
415
|
|
|
@@ -414,8 +429,12 @@ def _every_bar(
|
|
|
414
429
|
bars = case.bars or []
|
|
415
430
|
supplied = len(bars)
|
|
416
431
|
when = case.declared.get("now", ABSENT)
|
|
417
|
-
|
|
432
|
+
times = [bar.time for bar in bars]
|
|
433
|
+
session = session_from(case.instrument)
|
|
434
|
+
opens = first_bars(times, session)
|
|
435
|
+
closes = last_bars(times, session, case.instrument.get("interval"))
|
|
418
436
|
rows: List[List[Any]] = []
|
|
437
|
+
run.history([engine_bar(one) for one in bars])
|
|
419
438
|
for index, bar in enumerate(bars):
|
|
420
439
|
desk.fold(index, float(bar.time))
|
|
421
440
|
previous = bars[index - 1].close if index > 0 else ABSENT
|
|
@@ -427,12 +446,14 @@ def _every_bar(
|
|
|
427
446
|
"previousClose": previous,
|
|
428
447
|
"volume": bar.volume,
|
|
429
448
|
SESSION_FIRST: opens[index],
|
|
449
|
+
SESSION_LAST: closes[index],
|
|
450
|
+
BAR_TIME: bar.time,
|
|
430
451
|
},
|
|
431
452
|
index == 0,
|
|
432
453
|
)
|
|
433
454
|
result = run.execute_bar(
|
|
434
455
|
index,
|
|
435
|
-
|
|
456
|
+
engine_bar(bar),
|
|
436
457
|
BarState(is_new=True, is_confirmed=True, is_realtime=False, updates=1.0),
|
|
437
458
|
supplied=supplied,
|
|
438
459
|
instrument=case.instrument,
|
|
@@ -441,7 +462,11 @@ def _every_bar(
|
|
|
441
462
|
if result.diagnostic is not None:
|
|
442
463
|
found = result.diagnostic
|
|
443
464
|
return rows, [_diagnostic_row(found.code, found.line, found.column, index)]
|
|
444
|
-
|
|
465
|
+
# Step 9's records, each to the side of the run that owns it: an order
|
|
466
|
+
# to the desk, a log line to the book. Nothing else carries an effect.
|
|
467
|
+
logbook.write(result.applied, index, bar.time)
|
|
468
|
+
orders = [one for one in result.applied if one.effect == "order"]
|
|
469
|
+
sent = desk.apply(orders, IntentBar(index=index, time=float(bar.time)))
|
|
445
470
|
if sent is not None:
|
|
446
471
|
return rows, [_diagnostic_row(sent.code, sent.line, sent.column, index)]
|
|
447
472
|
rows.append(list(result.columns))
|