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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""What a leg holds, one entry per position reference, including what is still working.
|
|
2
|
+
|
|
3
|
+
**This file answers which position an order is sent against**, which the leg's
|
|
4
|
+
net cannot answer. The net is folded from settled fills, so with the destination
|
|
5
|
+
silent an entry of six on one bar and an opposing nine on the next would read as
|
|
6
|
+
opposing nothing at all, and both would go on one reference: that reference would
|
|
7
|
+
open six long and settle three short, which is the one failure ``stdlib.md`` 17.1
|
|
8
|
+
names, because a fill arriving late could no longer say which position it settled.
|
|
9
|
+
|
|
10
|
+
**So a reference is measured by what is on it, settled and working together.** An
|
|
11
|
+
order the destination has not answered has moved no position figure, and the
|
|
12
|
+
ledger's rows are where it is visible. A reference with six units of buy still to
|
|
13
|
+
come is a long position whether or not any of it has settled, and an opposing
|
|
14
|
+
order has to take those six off it before it opens anything new: otherwise
|
|
15
|
+
nothing can ever bring that reference back to zero, which is 17.7's sentence
|
|
16
|
+
about how a position ends.
|
|
17
|
+
|
|
18
|
+
**Two numbers, because two questions are asked and they are not the same
|
|
19
|
+
question.** ``units`` is what an opposing order may take, and it counts what is
|
|
20
|
+
working: the order is being sent either way, at the size the script wrote, and
|
|
21
|
+
the only thing being decided is which reference each unit lands on. ``settled``
|
|
22
|
+
is what a close may send, and it counts only what has settled less what is
|
|
23
|
+
already working against it, because a close chooses its own quantity and one
|
|
24
|
+
counting a working entry would sell units that may never exist.
|
|
25
|
+
|
|
26
|
+
**What this costs, stated rather than discovered.** An entry that opposes an
|
|
27
|
+
unanswered entry is sent against it, so if the first order is rejected and the
|
|
28
|
+
second fills, that reference settles on the side it did not open on. No engine
|
|
29
|
+
avoids that without holding an order back until the destination answers, which is
|
|
30
|
+
an engine that stops trading when a destination is slow. What 17.1 asks for is
|
|
31
|
+
kept either way: every order names exactly one position.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import Callable, List, Optional, Protocol, Sequence, Tuple
|
|
36
|
+
|
|
37
|
+
from .positions import sign
|
|
38
|
+
from .rows import LedgerRow
|
|
39
|
+
from .statuses import is_terminal
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Book(Protocol):
|
|
43
|
+
"""What this file reads: the rows of the ledger and the position book."""
|
|
44
|
+
|
|
45
|
+
def rows(self) -> Sequence[LedgerRow]:
|
|
46
|
+
"""The rows this strategy placed, newest last."""
|
|
47
|
+
|
|
48
|
+
def size_of(self, ref: int) -> float:
|
|
49
|
+
"""The settled size of one position reference, signed the way a position is."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class Holding:
|
|
54
|
+
"""One position reference a leg holds, and what may still be done to it."""
|
|
55
|
+
|
|
56
|
+
ref: int
|
|
57
|
+
#: ``buy`` for a long position, ``sell`` for a short one.
|
|
58
|
+
side: str
|
|
59
|
+
#: The units an opposing order may take, or absent where the engine cannot
|
|
60
|
+
#: read one of its quantities. Absent is not zero: it means the reference
|
|
61
|
+
#: holds something whose size is in the declaration's own unit.
|
|
62
|
+
units: Optional[float]
|
|
63
|
+
#: The settled units nothing is working against, which is what a close sends.
|
|
64
|
+
settled: float
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class _Tally:
|
|
69
|
+
"""One reference under construction, before it is decided what it holds."""
|
|
70
|
+
|
|
71
|
+
settled: float
|
|
72
|
+
working: float = 0.0
|
|
73
|
+
#: Working units on the side that reduces what has settled here, unsigned.
|
|
74
|
+
#: Which side that is depends on what settled, not on what an order was when
|
|
75
|
+
#: it left: an order recorded as adding is a reduction now on a position that
|
|
76
|
+
#: has since changed sign, and a reduction recorded then is adding now.
|
|
77
|
+
against: float = 0.0
|
|
78
|
+
#: The side of a working order whose quantity is not readable, or none.
|
|
79
|
+
unreadable: Optional[str] = None
|
|
80
|
+
#: Whether a quantity it cannot read is working against what settled here.
|
|
81
|
+
blocked: bool = False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _side_of(signed: float) -> str:
|
|
85
|
+
return "buy" if signed > 0 else "sell"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def holdings(ctx: Book) -> Tuple[Holding, ...]:
|
|
89
|
+
"""Every position reference this leg holds, oldest first.
|
|
90
|
+
|
|
91
|
+
Oldest first is the order the references were minted in, which is the order
|
|
92
|
+
the rows first name them in, and it is the order a reducing order takes them
|
|
93
|
+
in: the position opened first is the position closed first.
|
|
94
|
+
|
|
95
|
+
A reference with nothing left on it is not here. It has no room for an
|
|
96
|
+
opposing order and nothing for a close to send, and it is either already back
|
|
97
|
+
at zero or has its whole quantity spoken for by orders that are still going.
|
|
98
|
+
"""
|
|
99
|
+
tallies = {}
|
|
100
|
+
minted: List[int] = []
|
|
101
|
+
|
|
102
|
+
for row in ctx.rows():
|
|
103
|
+
tally = tallies.get(row.position_ref)
|
|
104
|
+
if tally is None:
|
|
105
|
+
tally = _Tally(settled=ctx.size_of(row.position_ref))
|
|
106
|
+
tallies[row.position_ref] = tally
|
|
107
|
+
minted.append(row.position_ref)
|
|
108
|
+
# An order that has ended has nothing more coming from it, whatever it
|
|
109
|
+
# filled: the fill is already in the settled figure above and the rest is
|
|
110
|
+
# released. That is how a strategy whose order was rejected gets its room
|
|
111
|
+
# back.
|
|
112
|
+
if is_terminal(row.status):
|
|
113
|
+
continue
|
|
114
|
+
way = 1 if row.side == "buy" else -1
|
|
115
|
+
# Every row is read the same way, by the order's own quantity in units,
|
|
116
|
+
# because this sum is compared against what has settled on this reference
|
|
117
|
+
# and both halves have to fall together when a fill arrives. Read instead
|
|
118
|
+
# by what a reduction claimed of the leg, the settled half fell and the
|
|
119
|
+
# claimed half did not, the sum went negative, and an entry opposing the
|
|
120
|
+
# reference was handed it as an order that adds.
|
|
121
|
+
remaining = None if row.units is None else max(0.0, row.units - row.filled_qty)
|
|
122
|
+
reduces = sign(tally.settled) == -way
|
|
123
|
+
if remaining is None:
|
|
124
|
+
tally.unreadable = row.side
|
|
125
|
+
# Nothing is sent against a position an order the engine cannot read
|
|
126
|
+
# is working against. Between a close that sends nothing and an order
|
|
127
|
+
# that crosses zero, 17.1 has already chosen.
|
|
128
|
+
if reduces:
|
|
129
|
+
tally.blocked = True
|
|
130
|
+
continue
|
|
131
|
+
tally.working += way * remaining
|
|
132
|
+
if reduces:
|
|
133
|
+
tally.against += remaining
|
|
134
|
+
|
|
135
|
+
held: List[Holding] = []
|
|
136
|
+
for ref in minted:
|
|
137
|
+
tally = tallies[ref]
|
|
138
|
+
outstanding = tally.settled + tally.working
|
|
139
|
+
if outstanding == 0 and tally.unreadable is None:
|
|
140
|
+
continue
|
|
141
|
+
side = tally.unreadable if outstanding == 0 else _side_of(outstanding)
|
|
142
|
+
# A reference holding a quantity the engine cannot read holds an unknown
|
|
143
|
+
# number of units, and understating it is the safe half of that: an
|
|
144
|
+
# opposing order divided against too small a number opens the remainder
|
|
145
|
+
# on a reference of its own, which crosses nothing.
|
|
146
|
+
units = None if outstanding == 0 else abs(outstanding)
|
|
147
|
+
# What a close may send is what settled here and is not already coming
|
|
148
|
+
# off, and only where what settled is on this side. A reference whose
|
|
149
|
+
# working orders run past its settled quantity reads as the other side,
|
|
150
|
+
# because that is what it will hold, and nothing of that side has settled.
|
|
151
|
+
facing = 1 if side == "buy" else -1
|
|
152
|
+
if tally.blocked or sign(tally.settled) != facing:
|
|
153
|
+
settled = 0.0
|
|
154
|
+
else:
|
|
155
|
+
settled = max(0.0, abs(tally.settled) - tally.against)
|
|
156
|
+
held.append(Holding(ref=ref, side=side, units=units, settled=settled))
|
|
157
|
+
return tuple(held)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def opposing(book: Sequence[Holding], side: str) -> Tuple[Holding, ...]:
|
|
161
|
+
"""The references an order of this side reduces, oldest first."""
|
|
162
|
+
return tuple(one for one in book if one.side != side)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def outgoing_for(ctx: Book, book: Sequence[Holding], side: str) -> Optional[int]:
|
|
166
|
+
"""The position an order the engine cannot size is sent against, or none.
|
|
167
|
+
|
|
168
|
+
A close is never minted a position of its own. It is named for reducing, so
|
|
169
|
+
it goes on the position it is closing, and where the engine cannot read the
|
|
170
|
+
quantity it states, that position is the one the order may take past zero:
|
|
171
|
+
the one shape of 17.1 an engine does not keep.
|
|
172
|
+
|
|
173
|
+
What has settled comes first and the book's own list second. A reference
|
|
174
|
+
whose settled quantity is entirely spoken for by orders still going is not in
|
|
175
|
+
the book, because it has nothing left for an engine-sized close to send; an
|
|
176
|
+
order the engine cannot size is not choosing a quantity, so that exclusion
|
|
177
|
+
does not apply to it. Taking the book's answer alone sent a close on the
|
|
178
|
+
reference a short entry had just opened, which is a call named close adding
|
|
179
|
+
to a position.
|
|
180
|
+
"""
|
|
181
|
+
facing = -1 if side == "buy" else 1
|
|
182
|
+
seen = set()
|
|
183
|
+
for row in ctx.rows():
|
|
184
|
+
if row.position_ref in seen:
|
|
185
|
+
continue
|
|
186
|
+
seen.add(row.position_ref)
|
|
187
|
+
if sign(ctx.size_of(row.position_ref)) == facing:
|
|
188
|
+
return row.position_ref
|
|
189
|
+
rest = opposing(book, side)
|
|
190
|
+
return rest[0].ref if rest else None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def protecting(ctx: Book) -> Optional[int]:
|
|
194
|
+
"""The position a bracket protects, or none where the leg holds none.
|
|
195
|
+
|
|
196
|
+
Holding first, opening second, which is the order 17.7 states them in, and
|
|
197
|
+
the newest of them where the leg holds more than one. A bracket appends no
|
|
198
|
+
row and moves no position, so it has nothing of its own to name: it names
|
|
199
|
+
what the leg has, and what the leg has is what its own fills settled.
|
|
200
|
+
|
|
201
|
+
Derived here rather than kept as the last reference minted. A stored slot
|
|
202
|
+
answered neither question: cleared when one reference returned to zero it
|
|
203
|
+
reported a leg still holding an older position as holding none, and kept for
|
|
204
|
+
an order the destination then refused it named a position that never opened.
|
|
205
|
+
Both are wrong in the direction a host cannot check, because ``0`` is the one
|
|
206
|
+
value ``host-interface.md`` 7.1 tells a host means there is nothing to look up.
|
|
207
|
+
"""
|
|
208
|
+
held: Optional[int] = None
|
|
209
|
+
seen = set()
|
|
210
|
+
for row in ctx.rows():
|
|
211
|
+
if row.position_ref in seen:
|
|
212
|
+
continue
|
|
213
|
+
seen.add(row.position_ref)
|
|
214
|
+
# Rows are oldest first and references are minted in order, so the last
|
|
215
|
+
# one this loop keeps is the newest reference something has settled on.
|
|
216
|
+
if ctx.size_of(row.position_ref) != 0:
|
|
217
|
+
held = row.position_ref
|
|
218
|
+
if held is not None:
|
|
219
|
+
return held
|
|
220
|
+
book = holdings(ctx)
|
|
221
|
+
return None if not book else book[-1].ref
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def joining(book: Sequence[Holding], side: str) -> Optional[int]:
|
|
225
|
+
"""The reference an order that adds to a position joins, or none to mint one.
|
|
226
|
+
|
|
227
|
+
The newest open reference on that side, so that two entries sent on two bars
|
|
228
|
+
before either fills belong to one position rather than to two. A reference
|
|
229
|
+
whose whole quantity is already going is not open to join: an entry joining
|
|
230
|
+
it would settle into a position that reaches zero and ends, and 17.7's
|
|
231
|
+
sentence about how a position ends would have to happen twice for one
|
|
232
|
+
reference.
|
|
233
|
+
"""
|
|
234
|
+
for one in reversed(list(book)):
|
|
235
|
+
if one.side == side:
|
|
236
|
+
return one.ref
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True)
|
|
241
|
+
class Share:
|
|
242
|
+
"""One order of a call: the units it sends and the reference it sends them on."""
|
|
243
|
+
|
|
244
|
+
ref: int
|
|
245
|
+
units: float
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def divide(
|
|
249
|
+
book: Sequence[Holding],
|
|
250
|
+
side: str,
|
|
251
|
+
units: float,
|
|
252
|
+
room: Callable[[Holding], Optional[float]],
|
|
253
|
+
) -> Tuple[Tuple[Share, ...], float]:
|
|
254
|
+
"""How a quantity is divided across the references it reduces, oldest first.
|
|
255
|
+
|
|
256
|
+
A reducing order that spans two positions is two orders, for the reason 17.1
|
|
257
|
+
gives for a flip: one order against two positions would leave a late fill
|
|
258
|
+
with no way to say which of them it settled.
|
|
259
|
+
|
|
260
|
+
``room`` is which of the two numbers a holding offers is the ceiling here:
|
|
261
|
+
what an opposing order may take, or what has settled. The caller chooses,
|
|
262
|
+
because the caller knows whether the quantity is the script's or the
|
|
263
|
+
engine's. What is left over when every reference is full is the caller's to
|
|
264
|
+
open or to drop, and nothing is ever sent past a reference's own ceiling.
|
|
265
|
+
"""
|
|
266
|
+
shares: List[Share] = []
|
|
267
|
+
left = units
|
|
268
|
+
for one in opposing(book, side):
|
|
269
|
+
if left <= 0:
|
|
270
|
+
break
|
|
271
|
+
ceiling = room(one)
|
|
272
|
+
if ceiling is None or ceiling <= 0:
|
|
273
|
+
continue
|
|
274
|
+
take = min(left, ceiling)
|
|
275
|
+
shares.append(Share(ref=one.ref, units=take))
|
|
276
|
+
left -= take
|
|
277
|
+
return tuple(shares), left
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""The two shapes that cross the order boundary, ``host-interface.md`` 7.1 and 7.2.
|
|
2
|
+
|
|
3
|
+
**An intent is not an order.** The engine states what the strategy decided and
|
|
4
|
+
the destination makes the order, which is why the duty is two messages rather
|
|
5
|
+
than one call. An engine that named a destination's own order id at the moment a
|
|
6
|
+
script called ``buy()`` would be handing the script an identifier for something
|
|
7
|
+
that may never exist, which is also why an order function returns absence
|
|
8
|
+
(``compiled-program.md`` section 5.4).
|
|
9
|
+
|
|
10
|
+
**A frame is cumulative.** Every frame restates the whole life of one order
|
|
11
|
+
rather than what changed since the frame before it, which is what makes a
|
|
12
|
+
repeat, a pair that crossed in flight and a reconnecting session that resends
|
|
13
|
+
its last frames all harmless. The fold that depends on it is ``rows``.
|
|
14
|
+
|
|
15
|
+
Nothing here parses an identity. A resolved identity may be a string, a number,
|
|
16
|
+
a pair or a row in the host's own table (``host-interface.md`` section 9.1), so
|
|
17
|
+
the engine carries the one it was given and hands it back unchanged. That is the
|
|
18
|
+
whole of what this engine knows about where an order goes: the destination is
|
|
19
|
+
the host's, and ``spec/decisions.md`` says the engine learns nothing about it.
|
|
20
|
+
|
|
21
|
+
A field a destination has not spoken about yet is ``None`` rather than a
|
|
22
|
+
substituted zero or an empty string, because absence and zero are different
|
|
23
|
+
answers everywhere else in this language and an order is the last place to stop
|
|
24
|
+
telling them apart.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, replace
|
|
28
|
+
from typing import Any, Optional, Tuple
|
|
29
|
+
|
|
30
|
+
#: The two sides, ``stdlib.md`` 17.2.
|
|
31
|
+
SIDES: Tuple[str, ...] = ("buy", "sell")
|
|
32
|
+
|
|
33
|
+
#: The four types, ``stdlib.md`` 17.2.
|
|
34
|
+
TYPES: Tuple[str, ...] = ("market", "limit", "stop", "stopLimit")
|
|
35
|
+
|
|
36
|
+
#: What an intent asks the destination to do, ``host-interface.md`` 7.1.
|
|
37
|
+
KINDS: Tuple[str, ...] = ("place", "cancel", "bracket")
|
|
38
|
+
|
|
39
|
+
#: The reference an intent about no position of its own carries. No reference an
|
|
40
|
+
#: engine mints is this, so a host looks one up only when it is not this.
|
|
41
|
+
NO_POSITION_REF = 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class Identity:
|
|
46
|
+
"""The instrument an order names, as the chart's record states it.
|
|
47
|
+
|
|
48
|
+
A pair, which is one of the spellings section 9.1 allows an identity to
|
|
49
|
+
take. It is carried and compared and never split, formatted or inferred
|
|
50
|
+
from.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
symbol: Optional[str] = None
|
|
54
|
+
exchange: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class IntentBar:
|
|
59
|
+
"""The bar whose close decided an order, ``host-interface.md`` 7.1."""
|
|
60
|
+
|
|
61
|
+
index: int = 0
|
|
62
|
+
time: Optional[float] = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class Placement:
|
|
67
|
+
"""An order this run is about to send, before it is given an id.
|
|
68
|
+
|
|
69
|
+
The intent's own shape less the four fields that are the same for every
|
|
70
|
+
order this run sends, so that the mapping cannot state one of them
|
|
71
|
+
differently from one call to the next.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
kind: str = "place"
|
|
75
|
+
side: Optional[str] = None
|
|
76
|
+
qty: Optional[float] = None
|
|
77
|
+
#: The unit ``qty`` is counted in, ``language.md`` 13.3, passed untranslated.
|
|
78
|
+
qty_type: str = "units"
|
|
79
|
+
order_type: Optional[str] = None
|
|
80
|
+
limit: Optional[float] = None
|
|
81
|
+
trigger: Optional[float] = None
|
|
82
|
+
#: A bracket's target and stop, as prices.
|
|
83
|
+
target: Optional[float] = None
|
|
84
|
+
stop: Optional[float] = None
|
|
85
|
+
#: A bracket's target and stop as distances from the entry, carried as
|
|
86
|
+
#: distances because the entry they are measured from is a fill, and on the
|
|
87
|
+
#: bar a script writes an entry and its bracket together nothing has filled.
|
|
88
|
+
profit: Optional[float] = None
|
|
89
|
+
loss: Optional[float] = None
|
|
90
|
+
tag: str = ""
|
|
91
|
+
position_ref: int = NO_POSITION_REF
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class OrderIntent:
|
|
96
|
+
"""What the engine hands over, ``host-interface.md`` 7.1.
|
|
97
|
+
|
|
98
|
+
``qty`` carries the unit its ``qty_type`` names rather than a count of units
|
|
99
|
+
the engine worked out for itself. A lot is the venue's own fact and the host
|
|
100
|
+
owns symbology, so an engine that multiplied by a lot size the host never
|
|
101
|
+
stated would send a quantity nobody asked for. A quantity the engine folded
|
|
102
|
+
from filled quantities, as a flattening order's is, states units.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
intent_id: int
|
|
106
|
+
placement: Placement
|
|
107
|
+
instrument: Identity
|
|
108
|
+
product: str
|
|
109
|
+
bar: IntentBar
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def kind(self) -> str:
|
|
113
|
+
return self.placement.kind
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def side(self) -> Optional[str]:
|
|
117
|
+
return self.placement.side
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def qty(self) -> Optional[float]:
|
|
121
|
+
return self.placement.qty
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def qty_type(self) -> str:
|
|
125
|
+
return self.placement.qty_type
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def order_type(self) -> Optional[str]:
|
|
129
|
+
return self.placement.order_type
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def tag(self) -> str:
|
|
133
|
+
return self.placement.tag
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def position_ref(self) -> int:
|
|
137
|
+
return self.placement.position_ref
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class OrderFrame:
|
|
142
|
+
"""What a host reports back about one order, ``host-interface.md`` 7.2.
|
|
143
|
+
|
|
144
|
+
``filled_qty`` is cumulative, from the beginning of this order's life, and
|
|
145
|
+
``avg_fill_price`` is the destination's average over the whole of it.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
intent_id: Any
|
|
149
|
+
status: str
|
|
150
|
+
filled_qty: float
|
|
151
|
+
avg_fill_price: Optional[float] = None
|
|
152
|
+
order_ref: Optional[str] = None
|
|
153
|
+
sent_instrument: Optional[Identity] = None
|
|
154
|
+
sent_product: Optional[str] = None
|
|
155
|
+
time: Optional[float] = None
|
|
156
|
+
text: Optional[str] = None
|
|
157
|
+
seq: Optional[float] = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def at_position(placement: Placement, ref: int) -> Placement:
|
|
161
|
+
"""The same placement sent against another position, which is what a split makes."""
|
|
162
|
+
return replace(placement, position_ref=ref)
|