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.
Files changed (97) hide show
  1. openscript/__init__.py +40 -0
  2. openscript/__main__.py +62 -0
  3. openscript/accounting/__init__.py +74 -0
  4. openscript/accounting/analysis.py +174 -0
  5. openscript/accounting/charges.py +397 -0
  6. openscript/accounting/equity.py +234 -0
  7. openscript/accounting/report.py +82 -0
  8. openscript/accounting/shapes.py +74 -0
  9. openscript/accounting/statistics.py +300 -0
  10. openscript/accounting/trades.py +294 -0
  11. openscript/adapter/__init__.py +32 -0
  12. openscript/adapter/answers.py +215 -0
  13. openscript/adapter/channels.py +137 -0
  14. openscript/adapter/expectations.py +67 -0
  15. openscript/adapter/facts.py +127 -0
  16. openscript/adapter/matching.py +257 -0
  17. openscript/adapter/ordering.py +187 -0
  18. openscript/adapter/page.py +130 -0
  19. openscript/adapter/reading.py +357 -0
  20. openscript/adapter/reporting.py +244 -0
  21. openscript/adapter/running.py +449 -0
  22. openscript/adapter/serving.py +229 -0
  23. openscript/adapter/sessions.py +168 -0
  24. openscript/adapter/spellings.py +184 -0
  25. openscript/bars.py +157 -0
  26. openscript/budget.py +342 -0
  27. openscript/canonical.py +192 -0
  28. openscript/civil.py +196 -0
  29. openscript/contracts.py +165 -0
  30. openscript/dates.py +302 -0
  31. openscript/diagnostics.py +104 -0
  32. openscript/hours.py +165 -0
  33. openscript/inputs.py +239 -0
  34. openscript/intervals.py +60 -0
  35. openscript/library/__init__.py +76 -0
  36. openscript/library/arithmetic.py +128 -0
  37. openscript/library/averages.py +133 -0
  38. openscript/library/bars.py +60 -0
  39. openscript/library/bookkeeping.py +166 -0
  40. openscript/library/code_points.py +85 -0
  41. openscript/library/colour.py +202 -0
  42. openscript/library/composites.py +208 -0
  43. openscript/library/counting.py +218 -0
  44. openscript/library/deviation.py +155 -0
  45. openscript/library/elementary.py +206 -0
  46. openscript/library/extremes.py +122 -0
  47. openscript/library/flows.py +220 -0
  48. openscript/library/momentum.py +203 -0
  49. openscript/library/number_text.py +223 -0
  50. openscript/library/prices.py +36 -0
  51. openscript/library/ranges.py +105 -0
  52. openscript/library/rounding.py +123 -0
  53. openscript/library/series.py +213 -0
  54. openscript/library/stateful.py +442 -0
  55. openscript/library/stateless.py +261 -0
  56. openscript/library/strength.py +180 -0
  57. openscript/library/strings.py +228 -0
  58. openscript/library/trend.py +260 -0
  59. openscript/library/values.py +91 -0
  60. openscript/logbook.py +119 -0
  61. openscript/machine.py +499 -0
  62. openscript/memory.py +204 -0
  63. openscript/opcodes.py +166 -0
  64. openscript/program.py +146 -0
  65. openscript/run.py +368 -0
  66. openscript/strategy/__init__.py +78 -0
  67. openscript/strategy/calls.py +201 -0
  68. openscript/strategy/closable.py +182 -0
  69. openscript/strategy/fills.py +131 -0
  70. openscript/strategy/holdings.py +277 -0
  71. openscript/strategy/intents.py +162 -0
  72. openscript/strategy/ledger.py +270 -0
  73. openscript/strategy/placing.py +206 -0
  74. openscript/strategy/positions.py +124 -0
  75. openscript/strategy/refusals.py +293 -0
  76. openscript/strategy/rows.py +219 -0
  77. openscript/strategy/sizing.py +229 -0
  78. openscript/strategy/statuses.py +65 -0
  79. openscript/surface/__init__.py +115 -0
  80. openscript/surface/bands.py +103 -0
  81. openscript/surface/levels.py +44 -0
  82. openscript/surface/marks.py +52 -0
  83. openscript/surface/paints.py +58 -0
  84. openscript/surface/plots.py +44 -0
  85. openscript/surface/published.py +119 -0
  86. openscript/values.py +210 -0
  87. openscript/verify.py +301 -0
  88. openscript/verify_code.py +290 -0
  89. openscript/verify_requests.py +271 -0
  90. openscript/verify_shape.py +162 -0
  91. openscript/verify_tables.py +256 -0
  92. openscript/version.py +39 -0
  93. openscript/zones.py +118 -0
  94. openscript-0.4.0.dist-info/METADATA +82 -0
  95. openscript-0.4.0.dist-info/RECORD +97 -0
  96. openscript-0.4.0.dist-info/WHEEL +5 -0
  97. 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
+ )