openscript 0.4.0__py3-none-any.whl
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/__init__.py +40 -0
- openscript/__main__.py +62 -0
- openscript/accounting/__init__.py +74 -0
- openscript/accounting/analysis.py +174 -0
- openscript/accounting/charges.py +397 -0
- openscript/accounting/equity.py +234 -0
- openscript/accounting/report.py +82 -0
- openscript/accounting/shapes.py +74 -0
- openscript/accounting/statistics.py +300 -0
- openscript/accounting/trades.py +294 -0
- openscript/adapter/__init__.py +32 -0
- openscript/adapter/answers.py +215 -0
- openscript/adapter/channels.py +137 -0
- openscript/adapter/expectations.py +67 -0
- openscript/adapter/facts.py +127 -0
- openscript/adapter/matching.py +257 -0
- openscript/adapter/ordering.py +187 -0
- openscript/adapter/page.py +130 -0
- openscript/adapter/reading.py +357 -0
- openscript/adapter/reporting.py +244 -0
- openscript/adapter/running.py +449 -0
- openscript/adapter/serving.py +229 -0
- openscript/adapter/sessions.py +168 -0
- openscript/adapter/spellings.py +184 -0
- openscript/bars.py +157 -0
- openscript/budget.py +342 -0
- openscript/canonical.py +192 -0
- openscript/civil.py +196 -0
- openscript/contracts.py +165 -0
- openscript/dates.py +302 -0
- openscript/diagnostics.py +104 -0
- openscript/hours.py +165 -0
- openscript/inputs.py +239 -0
- openscript/intervals.py +60 -0
- openscript/library/__init__.py +76 -0
- openscript/library/arithmetic.py +128 -0
- openscript/library/averages.py +133 -0
- openscript/library/bars.py +60 -0
- openscript/library/bookkeeping.py +166 -0
- openscript/library/code_points.py +85 -0
- openscript/library/colour.py +202 -0
- openscript/library/composites.py +208 -0
- openscript/library/counting.py +218 -0
- openscript/library/deviation.py +155 -0
- openscript/library/elementary.py +206 -0
- openscript/library/extremes.py +122 -0
- openscript/library/flows.py +220 -0
- openscript/library/momentum.py +203 -0
- openscript/library/number_text.py +223 -0
- openscript/library/prices.py +36 -0
- openscript/library/ranges.py +105 -0
- openscript/library/rounding.py +123 -0
- openscript/library/series.py +213 -0
- openscript/library/stateful.py +442 -0
- openscript/library/stateless.py +261 -0
- openscript/library/strength.py +180 -0
- openscript/library/strings.py +228 -0
- openscript/library/trend.py +260 -0
- openscript/library/values.py +91 -0
- openscript/logbook.py +119 -0
- openscript/machine.py +499 -0
- openscript/memory.py +204 -0
- openscript/opcodes.py +166 -0
- openscript/program.py +146 -0
- openscript/run.py +368 -0
- openscript/strategy/__init__.py +78 -0
- openscript/strategy/calls.py +201 -0
- openscript/strategy/closable.py +182 -0
- openscript/strategy/fills.py +131 -0
- openscript/strategy/holdings.py +277 -0
- openscript/strategy/intents.py +162 -0
- openscript/strategy/ledger.py +270 -0
- openscript/strategy/placing.py +206 -0
- openscript/strategy/positions.py +124 -0
- openscript/strategy/refusals.py +293 -0
- openscript/strategy/rows.py +219 -0
- openscript/strategy/sizing.py +229 -0
- openscript/strategy/statuses.py +65 -0
- openscript/surface/__init__.py +115 -0
- openscript/surface/bands.py +103 -0
- openscript/surface/levels.py +44 -0
- openscript/surface/marks.py +52 -0
- openscript/surface/paints.py +58 -0
- openscript/surface/plots.py +44 -0
- openscript/surface/published.py +119 -0
- openscript/values.py +210 -0
- openscript/verify.py +301 -0
- openscript/verify_code.py +290 -0
- openscript/verify_requests.py +271 -0
- openscript/verify_shape.py +162 -0
- openscript/verify_tables.py +256 -0
- openscript/version.py +39 -0
- openscript/zones.py +118 -0
- openscript-0.4.0.dist-info/METADATA +82 -0
- openscript-0.4.0.dist-info/RECORD +97 -0
- openscript-0.4.0.dist-info/WHEEL +5 -0
- openscript-0.4.0.dist-info/top_level.txt +1 -0
openscript/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The second engine: a compiled program, run in Python.
|
|
2
|
+
|
|
3
|
+
The package is importable as one name, ``openscript``, and this module is its
|
|
4
|
+
door. Nothing is exported through it: every caller, inside this package and
|
|
5
|
+
outside it, names the module it wants, so the import that reaches a rule says
|
|
6
|
+
where that rule lives.
|
|
7
|
+
|
|
8
|
+
What is where, for somebody reading this engine rather than running it:
|
|
9
|
+
|
|
10
|
+
- ``__main__.py`` the entry point the conformance adapter starts, as
|
|
11
|
+
``python -m openscript``, which is the command line half
|
|
12
|
+
of the three invocations ``spec/conformance.md`` section
|
|
13
|
+
9 gives an adapter.
|
|
14
|
+
- the modules beside the machine of ``spec/compiled-program.md`` sections 2 to
|
|
15
|
+
this one 11: the program read as data, the instruction set, the
|
|
16
|
+
memory regions and their lifetimes, the values, the bars,
|
|
17
|
+
the inputs, the verification done before the first bar,
|
|
18
|
+
the execution budget, the canonical encoding, the
|
|
19
|
+
diagnostics and the two version numbers.
|
|
20
|
+
- ``library/`` the functions of ``spec/stdlib.md``, each accumulating in
|
|
21
|
+
the order that page fixes, in the two halves that page
|
|
22
|
+
divides them into: the calls that remember nothing and
|
|
23
|
+
the calls that carry state from bar to bar.
|
|
24
|
+
- ``strategy/`` what a strategy decided and what came back: the order
|
|
25
|
+
calls, the intents a bar leaves behind, the frames a host
|
|
26
|
+
folds in and the ledger of ``stdlib.md`` section 17.
|
|
27
|
+
- ``accounting/`` what those fills came to: the charges, the trades, the
|
|
28
|
+
equity a report is marked on, and the summary.
|
|
29
|
+
- ``adapter/`` one conformance case, read from its files, run, and
|
|
30
|
+
answered channel by channel.
|
|
31
|
+
|
|
32
|
+
Two rules that are not this file's to relax, both from ``CLAUDE.md``:
|
|
33
|
+
|
|
34
|
+
Nothing here builds code out of text. No string evaluator, no statement
|
|
35
|
+
executor, no compiler, no import by a name computed at run time, no object
|
|
36
|
+
graph loaded out of bytes. The compiled program is data, and an engine that
|
|
37
|
+
reads data is why a host can run many people's scripts in one process.
|
|
38
|
+
|
|
39
|
+
Nothing here imports anything but the standard library.
|
|
40
|
+
"""
|
openscript/__main__.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""The entry point the conformance adapter starts: ``python -m openscript``.
|
|
2
|
+
|
|
3
|
+
``spec/conformance.md`` section 9 gives an adapter three invocations, each
|
|
4
|
+
writing one JSON object to standard output and exiting 0, and this module is the
|
|
5
|
+
command line half of all three. Everything about what the engine answers is next
|
|
6
|
+
door in ``adapter/``.
|
|
7
|
+
|
|
8
|
+
python -m openscript --describe
|
|
9
|
+
python -m openscript <case-directory>
|
|
10
|
+
python -m openscript --actual <case-directory>
|
|
11
|
+
|
|
12
|
+
**The compiled program arrives on standard input**, as one JSON object holding
|
|
13
|
+
the canonical text of the program under ``program``, or holding ``diagnostics``
|
|
14
|
+
when the case's script did not compile. This engine implements no compiler, which
|
|
15
|
+
section 1 provides for: an implementation that only has an engine reads compiled
|
|
16
|
+
programs produced elsewhere, runs the engine half and says so. The text goes to
|
|
17
|
+
``load_text`` rather than to a parsed object, because canonical bytes are what a
|
|
18
|
+
host sends in production and the hash it records a run against is taken over
|
|
19
|
+
them.
|
|
20
|
+
|
|
21
|
+
**Why it exits 0 on a failing case.** Section 9 puts the outcome inside the
|
|
22
|
+
object, and the runner holds the clock and the child process, so a non-zero exit
|
|
23
|
+
is the one thing left to mean a crash. An invocation that is not one of the three
|
|
24
|
+
is refused with a non-zero exit, because nothing it could write would be a case
|
|
25
|
+
result.
|
|
26
|
+
|
|
27
|
+
**The output is the canonical encoding**, written by the same writer the compiled
|
|
28
|
+
program's text boundary uses. One writer means a number in a report is spelled
|
|
29
|
+
the way a number in a program is, which is ``language.md`` section 5.5 in both
|
|
30
|
+
places rather than the interpreter's own rendering in one of them.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import sys
|
|
34
|
+
|
|
35
|
+
from .adapter.answers import invoke
|
|
36
|
+
from .adapter.spellings import Malformed
|
|
37
|
+
from .canonical import canonicalise
|
|
38
|
+
|
|
39
|
+
_USAGE = (
|
|
40
|
+
"Usage: python -m openscript --describe | <case-directory> | --actual <case-directory>\n"
|
|
41
|
+
"conformance.md section 9 gives an adapter these three invocations and no other.\n"
|
|
42
|
+
"A case invocation is handed the compiled program on standard input, as one JSON\n"
|
|
43
|
+
"object holding the canonical program text under program, because this engine\n"
|
|
44
|
+
"implements no compiler and reads programs produced elsewhere (section 1)."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(arguments) -> int:
|
|
49
|
+
try:
|
|
50
|
+
answered = invoke(arguments)
|
|
51
|
+
except Malformed as reason:
|
|
52
|
+
sys.stderr.write(f"{reason}\n")
|
|
53
|
+
return 1
|
|
54
|
+
if answered is None:
|
|
55
|
+
sys.stderr.write(f"{_USAGE}\n")
|
|
56
|
+
return 1
|
|
57
|
+
sys.stdout.write(f"{canonicalise(answered)}\n")
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""The money: what a run made, what it cost, and what that is worth knowing.
|
|
2
|
+
|
|
3
|
+
**This package imports no interpreter and no ledger, and it never will.** It is
|
|
4
|
+
arithmetic over portable data: a list of settled fills, a list of bar closes, a
|
|
5
|
+
charge schedule and the contract the run was carried out under. Two things follow
|
|
6
|
+
from that and both of them are the reason for it.
|
|
7
|
+
|
|
8
|
+
A stored record can be reported again with no engine present, which is what makes
|
|
9
|
+
a run record a conformance case rather than a souvenir. And the engine can call
|
|
10
|
+
this, when the day comes that a script may read its own equity, without a cycle
|
|
11
|
+
and without a second implementation of any of these formulas sitting inside the
|
|
12
|
+
execution path disagreeing with this one.
|
|
13
|
+
|
|
14
|
+
What is here:
|
|
15
|
+
|
|
16
|
+
- ``shapes`` the atoms: a fill, a contract and a bar's close
|
|
17
|
+
- ``charges`` what one fill cost, in the order the lines are declared
|
|
18
|
+
- ``trades`` the round trips, one per position reference
|
|
19
|
+
- ``equity`` one point per report bar, marked to the close
|
|
20
|
+
- ``statistics`` the summary, and which half of the trade list each figure counts
|
|
21
|
+
- ``report`` the one pass, in the one order, that is the result
|
|
22
|
+
|
|
23
|
+
**What none of these is, is specified.** ``conformance.md`` section 4 makes
|
|
24
|
+
``performance`` a channel a case asserts and ``stdlib.md`` 17.4 leaves every
|
|
25
|
+
figure in it planned, so the formulas below are read from the first engine rather
|
|
26
|
+
than from a page. That is a defect of the specification and not of either engine,
|
|
27
|
+
and it is recorded where a reader of this package will meet it rather than only in
|
|
28
|
+
a report nobody keeps.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from .analysis import SideAnalysis, TradeAnalysis, analysis_of
|
|
32
|
+
from .charges import (
|
|
33
|
+
ChargeBreakdown,
|
|
34
|
+
ChargeLine,
|
|
35
|
+
ChargeSchedule,
|
|
36
|
+
charge_for,
|
|
37
|
+
round_money,
|
|
38
|
+
schedule_from_declaration,
|
|
39
|
+
schedule_problem,
|
|
40
|
+
)
|
|
41
|
+
from .equity import EquityPoint, bars_in_market_over, equity_over, open_on_bar, ratio_of
|
|
42
|
+
from .report import Report, report_of
|
|
43
|
+
from .shapes import BarMark, Contract, RecordedFill
|
|
44
|
+
from .statistics import Summary, summary_of
|
|
45
|
+
from .trades import Trade, closed_by, opened_by, trades_of
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"BarMark",
|
|
49
|
+
"ChargeBreakdown",
|
|
50
|
+
"ChargeLine",
|
|
51
|
+
"ChargeSchedule",
|
|
52
|
+
"Contract",
|
|
53
|
+
"EquityPoint",
|
|
54
|
+
"RecordedFill",
|
|
55
|
+
"Report",
|
|
56
|
+
"SideAnalysis",
|
|
57
|
+
"Summary",
|
|
58
|
+
"Trade",
|
|
59
|
+
"TradeAnalysis",
|
|
60
|
+
"analysis_of",
|
|
61
|
+
"bars_in_market_over",
|
|
62
|
+
"charge_for",
|
|
63
|
+
"closed_by",
|
|
64
|
+
"equity_over",
|
|
65
|
+
"open_on_bar",
|
|
66
|
+
"opened_by",
|
|
67
|
+
"ratio_of",
|
|
68
|
+
"report_of",
|
|
69
|
+
"round_money",
|
|
70
|
+
"schedule_from_declaration",
|
|
71
|
+
"schedule_problem",
|
|
72
|
+
"summary_of",
|
|
73
|
+
"trades_of",
|
|
74
|
+
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""The trades taken apart: by direction, by extreme, and by run.
|
|
2
|
+
|
|
3
|
+
The summary answers what the whole run did. Three questions it cannot answer are
|
|
4
|
+
asked here, and each of them is a question about whether the summary means what
|
|
5
|
+
it looks like it means.
|
|
6
|
+
|
|
7
|
+
**Which side made the money.** A run whose long trades paid for its short ones
|
|
8
|
+
reports a healthy net and is two strategies, one of which is losing. The summary
|
|
9
|
+
cannot show that, because every figure in it is folded over both sides at once.
|
|
10
|
+
Splitting it is not a refinement of the headline number, it is the first thing
|
|
11
|
+
that can contradict it.
|
|
12
|
+
|
|
13
|
+
**Whether one trade is the result.** A hundred trades and a profit factor of two
|
|
14
|
+
reads as an edge until the largest win is the whole of the net. The expectancy's
|
|
15
|
+
standard error already says how wide the spread is; the largest win and the
|
|
16
|
+
largest loss say where the width came from, which is the part a reader can act
|
|
17
|
+
on.
|
|
18
|
+
|
|
19
|
+
**What the run of losses was.** The deepest drawdown is a money figure and the
|
|
20
|
+
longest one is a bar count, and neither is the number that actually stops
|
|
21
|
+
somebody trading a strategy. That number is how many times in a row it was
|
|
22
|
+
wrong, and it is not derivable from anything in the summary: the same win rate
|
|
23
|
+
over the same trades gives a streak of two or a streak of eleven depending on an
|
|
24
|
+
ordering the summary folds away.
|
|
25
|
+
|
|
26
|
+
**Every figure here is over closed trades, and that is the whole rule.** An open
|
|
27
|
+
trade has no net to win or lose by, so it is in none of these counts, in no
|
|
28
|
+
streak and in no extreme. The consequence worth stating: ``long.count`` plus
|
|
29
|
+
``short.count`` is the summary's ``trade_count`` and not the length of the list
|
|
30
|
+
folded, and the summary's ``open_trade_count`` is where the difference goes.
|
|
31
|
+
|
|
32
|
+
This is the first engine's ``src/core/accounting/analysis.ts`` in this language's
|
|
33
|
+
spellings. The two are one algorithm and the conformance suite is what holds
|
|
34
|
+
them to it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from dataclasses import dataclass
|
|
38
|
+
from typing import List, Optional, Sequence
|
|
39
|
+
|
|
40
|
+
from .trades import Trade
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class SideAnalysis:
|
|
45
|
+
"""One direction's own figures, folded over that side's closed trades."""
|
|
46
|
+
|
|
47
|
+
count: int
|
|
48
|
+
wins: int
|
|
49
|
+
losses: int
|
|
50
|
+
#: Exactly zero net, counted in neither half, as in the summary.
|
|
51
|
+
scratches: int
|
|
52
|
+
#: Net after charges, which is the figure the sides are compared on.
|
|
53
|
+
net_profit: float
|
|
54
|
+
#: ``wins / (wins + losses)``, ``None`` where this side decided nothing.
|
|
55
|
+
win_rate: Optional[float]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class TradeAnalysis:
|
|
60
|
+
"""The trades by direction, by extreme and by run.
|
|
61
|
+
|
|
62
|
+
``long`` and ``short`` partition the closed trades, so their counts sum to
|
|
63
|
+
the summary's ``trade_count`` and their nets sum to its ``net_profit``.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
long: SideAnalysis
|
|
67
|
+
short: SideAnalysis
|
|
68
|
+
#: The best closed trade's net, zero where none closed and zero where every
|
|
69
|
+
#: closed trade lost. Zero rather than ``None``, because the figure is read
|
|
70
|
+
#: beside ``largest_loss`` and against the net, and a ``None`` in a column
|
|
71
|
+
#: of money makes every reader handle a case that means "nothing won",
|
|
72
|
+
#: which is what zero already means in this column.
|
|
73
|
+
largest_win: float
|
|
74
|
+
#: The worst closed trade's net as a positive magnitude, zero where none lost.
|
|
75
|
+
largest_loss: float
|
|
76
|
+
#: The longest run of consecutive winning closed trades, in the order they
|
|
77
|
+
#: closed, and not the order they opened: that is the order the account
|
|
78
|
+
#: experienced them in. The two differ whenever a trade is held across
|
|
79
|
+
#: another one's whole life, which is every scaling strategy.
|
|
80
|
+
max_consecutive_wins: int
|
|
81
|
+
#: The same, for losses.
|
|
82
|
+
max_consecutive_losses: int
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class _SideTally:
|
|
87
|
+
"""A side's figures under construction, before the rates are taken."""
|
|
88
|
+
|
|
89
|
+
count: int = 0
|
|
90
|
+
wins: int = 0
|
|
91
|
+
losses: int = 0
|
|
92
|
+
scratches: int = 0
|
|
93
|
+
net_profit: float = 0.0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _side_of(tally: _SideTally) -> SideAnalysis:
|
|
97
|
+
decided = tally.wins + tally.losses
|
|
98
|
+
return SideAnalysis(
|
|
99
|
+
count=tally.count,
|
|
100
|
+
wins=tally.wins,
|
|
101
|
+
losses=tally.losses,
|
|
102
|
+
scratches=tally.scratches,
|
|
103
|
+
net_profit=tally.net_profit,
|
|
104
|
+
win_rate=None if decided == 0 else tally.wins / decided,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def analysis_of(trades: Sequence[Trade]) -> TradeAnalysis:
|
|
109
|
+
"""One pass over the closed trades, in closing order.
|
|
110
|
+
|
|
111
|
+
The list arrives in opening order, which is what the equity fold needs. The
|
|
112
|
+
streaks need closing order, so the closed trades are ordered here rather
|
|
113
|
+
than anywhere else: reordering the list the caller holds would change the
|
|
114
|
+
equity curve.
|
|
115
|
+
|
|
116
|
+
A trade whose net is exactly zero is a scratch, which is the summary's rule.
|
|
117
|
+
A scratch **breaks** a streak without extending either one: a strategy that
|
|
118
|
+
went right, flat, right was not right twice running, and counting the flat
|
|
119
|
+
trade as either would make the streak a figure that depends on a rounding at
|
|
120
|
+
the last digit.
|
|
121
|
+
"""
|
|
122
|
+
long = _SideTally()
|
|
123
|
+
short = _SideTally()
|
|
124
|
+
largest_win = 0.0
|
|
125
|
+
largest_loss = 0.0
|
|
126
|
+
max_consecutive_wins = 0
|
|
127
|
+
max_consecutive_losses = 0
|
|
128
|
+
win_streak = 0
|
|
129
|
+
loss_streak = 0
|
|
130
|
+
|
|
131
|
+
closed: List[Trade] = [trade for trade in trades if not trade.is_open]
|
|
132
|
+
# A trade closes on a bar, and two can close on the same one. The opening
|
|
133
|
+
# order breaks the tie, because it is the order the list arrived in and the
|
|
134
|
+
# only other fact available: a sort that is not total gives two engines two
|
|
135
|
+
# different streaks from one list of trades.
|
|
136
|
+
closed.sort(key=lambda trade: (
|
|
137
|
+
0 if trade.closed_on_bar is None else trade.closed_on_bar,
|
|
138
|
+
trade.index,
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
for trade in closed:
|
|
142
|
+
side = long if trade.side == "long" else short
|
|
143
|
+
side.count += 1
|
|
144
|
+
side.net_profit += trade.net_profit
|
|
145
|
+
|
|
146
|
+
if trade.net_profit > 0:
|
|
147
|
+
side.wins += 1
|
|
148
|
+
if trade.net_profit > largest_win:
|
|
149
|
+
largest_win = trade.net_profit
|
|
150
|
+
win_streak += 1
|
|
151
|
+
loss_streak = 0
|
|
152
|
+
if win_streak > max_consecutive_wins:
|
|
153
|
+
max_consecutive_wins = win_streak
|
|
154
|
+
elif trade.net_profit < 0:
|
|
155
|
+
side.losses += 1
|
|
156
|
+
if -trade.net_profit > largest_loss:
|
|
157
|
+
largest_loss = -trade.net_profit
|
|
158
|
+
loss_streak += 1
|
|
159
|
+
win_streak = 0
|
|
160
|
+
if loss_streak > max_consecutive_losses:
|
|
161
|
+
max_consecutive_losses = loss_streak
|
|
162
|
+
else:
|
|
163
|
+
side.scratches += 1
|
|
164
|
+
win_streak = 0
|
|
165
|
+
loss_streak = 0
|
|
166
|
+
|
|
167
|
+
return TradeAnalysis(
|
|
168
|
+
long=_side_of(long),
|
|
169
|
+
short=_side_of(short),
|
|
170
|
+
largest_win=largest_win,
|
|
171
|
+
largest_loss=largest_loss,
|
|
172
|
+
max_consecutive_wins=max_consecutive_wins,
|
|
173
|
+
max_consecutive_losses=max_consecutive_losses,
|
|
174
|
+
)
|