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
@@ -0,0 +1,91 @@
1
+ """Absence, and the two rules every number this library returns has passed.
2
+
3
+ `compiled-program.md` section 3.1 states both, and they are here rather than in
4
+ each function because a rule written out thirty times is a rule with thirty
5
+ places to get it wrong:
6
+
7
+ - **A number is always finite.** Any result that is not is absent, checked after
8
+ each individual operation rather than at the end of an expression, so that two
9
+ engines cannot differ over where one of them happened to round.
10
+ - **Negative zero is normalised to positive zero.** Nothing in the language can
11
+ observe the sign of a zero, and a rule that could spell one would let it reach
12
+ a string through ``text`` and differ between two engines for no reason a
13
+ reader could act on.
14
+
15
+ Absence is ``None`` and never a not-a-number. `compiled-program.md` section 3.4
16
+ gives the reason: a not-a-number propagates through arithmetic by accident,
17
+ which is the right answer for some operators and the wrong one for others, and
18
+ it would turn an absence test into a floating point comparison.
19
+
20
+ **Nothing here raises.** A wrong argument is a diagnostic the checker or the
21
+ interpreter produces, with a code and a span, before a call reaches a function
22
+ in this package (`stdlib.md` section 2.4). The backstops below return absence
23
+ instead, because a wrong number drawn on a chart is worse than a gap.
24
+ """
25
+
26
+ import math
27
+
28
+ # A value the machine holds, minus the reference: the interpreter owns the heap
29
+ # and the object that lives in it, so a type written here would be that type
30
+ # stated twice. What this package promises about one is narrower than the tag
31
+ # list: a function takes what its entry in `stdlib.md` says it takes.
32
+ Value = float | bool | str | None
33
+
34
+ # Absence, written `none` in a script.
35
+ ABSENT: None = None
36
+
37
+
38
+ def is_number(value: Value) -> bool:
39
+ """Whether a value is a number, which a bool is not.
40
+
41
+ ``isinstance(True, float)`` is false in this language and true in several
42
+ others, but a bool is an integer here, so ``value == 1`` is true of ``True``
43
+ and a numeric guard written that way would let a bool through into
44
+ arithmetic. The tag is asked for instead.
45
+ """
46
+ return isinstance(value, (int, float)) and not isinstance(value, bool)
47
+
48
+
49
+ def result(x: float) -> Value:
50
+ """A computed number, made safe to return: finite, with one zero."""
51
+ if not math.isfinite(x):
52
+ return ABSENT
53
+ return 0.0 if x == 0 else float(x)
54
+
55
+
56
+ def number(value: Value) -> float | None:
57
+ """A numeric argument as a number, or absence for anything that is not one.
58
+
59
+ The absence covers two cases that are one case here: the argument the script
60
+ wrote was absent on this bar, and the argument was of another type, which is
61
+ a program the checker should not have accepted. Both are absence rather than
62
+ a raise, per the module's opening note.
63
+ """
64
+ return float(value) if is_number(value) else None
65
+
66
+
67
+ def whole(value: Value) -> int | None:
68
+ """A count argument as a whole number of zero or more, or absence.
69
+
70
+ A digit count and a width are counts, not measurements: `stdlib.md` section
71
+ 2.5 refuses a fractional one at compile time when it is a literal and at run
72
+ time when it is not, with a fix naming ``round``, because a length of 14.5 is
73
+ a bug in the script and rounding it on the script's behalf hides the bug.
74
+ This is the backstop under those diagnostics and not a second rule.
75
+ """
76
+ x = number(value)
77
+ if x is None or not math.isfinite(x) or x != int(x) or x < 0:
78
+ return None
79
+ return int(x)
80
+
81
+
82
+ def floor_of(x: float) -> float:
83
+ """``floor`` over binary64, including the values it has no whole number for.
84
+
85
+ The interpreter's own floor raises on an infinity rather than returning one,
86
+ and an infinity is exactly what the argument is when a division inside
87
+ ``mod`` or a scaling inside a rounding overflows. Returning it unchanged
88
+ lets the arithmetic carry on and ``result`` turn the overflow into absence
89
+ at the end, which is where `compiled-program.md` section 3.1 puts it.
90
+ """
91
+ return x if not math.isfinite(x) else float(math.floor(x))
openscript/logbook.py ADDED
@@ -0,0 +1,119 @@
1
+ """The script's log: what ``print`` leaves behind, and what a host does with it.
2
+
3
+ `stdlib.md` 14.3 is the call. Three sentences of it are the whole of the
4
+ behaviour, and each one is a line of code here rather than a paragraph in a
5
+ document nobody runs:
6
+
7
+ - **``print`` writes to the per-script log, not to the chart.** It draws
8
+ nothing, lands in no contract field and answers nothing, so a case's numeric
9
+ output is identical with logging on and with logging off. That is not this
10
+ module's promise to keep: ``print`` carries an effect, so the machine pushes
11
+ absence and holds the record until step 9 (`compiled-program.md` 5.4), and the
12
+ columns are written at step 8, before any of this runs. What is kept here is a
13
+ test that would notice if it stopped being true.
14
+ - **Every entry carries the bar's time**, so a log line can be matched to a bar.
15
+ A bar index goes with it, because `conformance.md` section 7's example of a
16
+ log case is a line carrying one, and the index is what a runner compares
17
+ against an expected file without knowing the dataset's own clock.
18
+ - **It is rate limited by the host rather than by the language, and a host that
19
+ drops lines must say how many it dropped rather than truncating silently.** So
20
+ the limit arrives from the caller and is not a number this engine chose, and
21
+ the count of what it dropped is part of the answer rather than a log line
22
+ saying so, which would itself be a line a limit could drop.
23
+
24
+ **A moving bar writes nothing that is not decided.** Step 9 applies the records
25
+ of a bar this engine decided, and `stdlib.md` 14.3's rollback question is open in
26
+ `feature-matrix.md` for the case it does not cover: whether lines written during
27
+ a re-executed moving bar replace the previous execution's or are deferred like an
28
+ order. Nothing here answers it. The records this is handed are the ones step 9
29
+ applied, which is the half the pages do fix, and a caller replaying a moving bar
30
+ is handed the same answer twice rather than a reading this module invented.
31
+
32
+ **The shape of a line is this engine's and not a page's, and it is written down
33
+ once.** `conformance.md` section 4 says the ``log`` channel is an ordered list
34
+ whose elements are flat objects of named fields, and nothing anywhere names the
35
+ fields. The first engine hands the host the raw effect and builds no line at all,
36
+ so there is no second engine's spelling to agree with either. ``rows`` below is
37
+ therefore the one place a name is chosen, and the day a page fixes them it is the
38
+ one place that changes.
39
+ """
40
+
41
+ from dataclasses import dataclass, field
42
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
43
+
44
+ from .contracts import LibraryEntry
45
+ from .values import ABSENT
46
+
47
+ #: `stdlib.md` 14.3's one call, and the effect `compiled-program.md` 5.4 gives
48
+ #: it. One argument: the value. The extra argument of section 4.10 belongs to an
49
+ #: order call, whose defaults are absence itself, and this call has no default to
50
+ #: tell apart from a value that came out absent.
51
+ PRINT = "print"
52
+
53
+ LOG_ENTRIES: Dict[Tuple[str, int], LibraryEntry] = {
54
+ (PRINT, 1): LibraryEntry(PRINT, 1, False, "log")
55
+ }
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class Line:
60
+ """One entry of the log: the bar it was written on, and the value written."""
61
+
62
+ #: The index of the bar being executed, which is what a case compares.
63
+ bar: int
64
+ #: The bar's own open time, `host-interface.md` 3.1, in UTC milliseconds.
65
+ time: Any
66
+ #: Whatever the script passed, absence included. A script that printed a
67
+ #: value which was absent during warmup wrote an absent line, and an engine
68
+ #: that turned it into an empty string would hide the one thing the author is
69
+ #: looking at the log for.
70
+ value: Any = ABSENT
71
+
72
+
73
+ @dataclass
74
+ class Logbook:
75
+ """One run's log, with the ceiling the host sets on it.
76
+
77
+ ``limit`` is the host's rate limit and ``None`` is a host that states none,
78
+ which is every conformance run: a case fixes its input in files and a limit
79
+ the suite did not state would be a limit one engine applied and another did
80
+ not. A host that does state one gets what it asked for and the count of what
81
+ that cost, which is 14.3's own requirement and the reason the count is a
82
+ field here rather than a line in the log.
83
+ """
84
+
85
+ limit: Optional[int] = None
86
+ lines: List[Line] = field(default_factory=list)
87
+ dropped: int = 0
88
+
89
+ def write(self, effects: Iterable[Any], bar: int, time: Any) -> None:
90
+ """The log records one execution left behind, in the order it made them.
91
+
92
+ The records are step 9's, so a call this is handed was made on a bar the
93
+ engine decided. Order is the bar's own and is never sorted: a script
94
+ printing a heading and then a row is a script whose two lines are only
95
+ useful in that order.
96
+ """
97
+ for effect in effects:
98
+ if getattr(effect, "name", None) != PRINT:
99
+ continue
100
+ arguments = getattr(effect, "arguments", ())
101
+ self.add(bar, time, arguments[0] if arguments else ABSENT)
102
+
103
+ def add(self, bar: int, time: Any, value: Any) -> None:
104
+ """One line, unless the host's limit has been reached and it is counted instead."""
105
+ if self.limit is not None and len(self.lines) >= self.limit:
106
+ self.dropped += 1
107
+ return
108
+ self.lines.append(Line(bar, time, value))
109
+
110
+ def rows(self) -> List[Dict[str, Any]]:
111
+ """The lines as the flat objects section 4 compares a non columnar channel as.
112
+
113
+ The field names are this module's, for the reason the opening note gives:
114
+ no page fixes them. What is not this module's is the encoding of the
115
+ value, which section 4 already fixes for every channel and the adapter
116
+ already carries in one place, so a value goes out as it is held and is
117
+ spelled where every other spelling is decided.
118
+ """
119
+ return [{"barIndex": one.bar, "time": one.time, "value": one.value} for one in self.lines]