architect-py 5.1.4rc1__py3-none-any.whl → 5.1.6__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.
- architect_py/__init__.py +24 -4
- architect_py/async_client.py +121 -67
- architect_py/client.pyi +16 -37
- architect_py/grpc/models/Accounts/ResetPaperAccountRequest.py +59 -0
- architect_py/grpc/models/Accounts/ResetPaperAccountResponse.py +20 -0
- architect_py/grpc/models/Boss/OptionsTransactionsRequest.py +42 -0
- architect_py/grpc/models/Boss/OptionsTransactionsResponse.py +27 -0
- architect_py/grpc/models/Core/ConfigResponse.py +7 -2
- architect_py/grpc/models/Folio/AccountSummary.py +7 -1
- architect_py/grpc/models/Marketdata/Candle.py +6 -0
- architect_py/grpc/models/Marketdata/L1BookSnapshot.py +6 -0
- architect_py/grpc/models/Marketdata/L2BookSnapshot.py +6 -0
- architect_py/grpc/models/Marketdata/Liquidation.py +6 -0
- architect_py/grpc/models/Marketdata/Ticker.py +6 -0
- architect_py/grpc/models/Marketdata/Trade.py +6 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +5 -5
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +5 -5
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +5 -1
- architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +5 -1
- architect_py/grpc/models/OptionsMarketdata/OptionsContract.py +45 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsContractGreeksRequest.py +40 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsContractRequest.py +40 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +4 -1
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +8 -1
- architect_py/grpc/models/OptionsMarketdata/OptionsGreeks.py +58 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsWraps.py +28 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsWrapsRequest.py +40 -0
- architect_py/grpc/models/__init__.py +11 -1
- architect_py/grpc/models/definitions.py +57 -86
- architect_py/grpc/orderflow.py +3 -7
- architect_py/grpc/server.py +1 -3
- architect_py/tests/test_order_entry.py +120 -1
- architect_py/tests/test_positions.py +364 -0
- architect_py/tests/test_rounding.py +28 -28
- architect_py/utils/pandas.py +50 -1
- {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/METADATA +1 -1
- {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/RECORD +49 -38
- examples/external_cpty.py +2 -1
- examples/funding_rate_mean_reversion_algo.py +4 -4
- examples/order_sending.py +3 -3
- examples/orderflow_channel.py +75 -56
- examples/stream_l1_marketdata.py +3 -1
- examples/stream_l2_marketdata.py +3 -1
- examples/tutorial_async.py +3 -2
- examples/tutorial_sync.py +4 -3
- scripts/generate_functions_md.py +2 -1
- {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/WHEEL +0 -0
- {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
|
|
1
|
+
import asyncio
|
2
|
+
from decimal import Decimal
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from architect_py import AsyncClient, OrderDir, OrderType, TradableProduct
|
7
|
+
|
8
|
+
ES_MULTIPLIER = Decimal("50.0") # ES futures multiplier
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.mark.asyncio
|
12
|
+
async def test_paper_setup(async_client: AsyncClient):
|
13
|
+
accounts = await async_client.list_accounts()
|
14
|
+
|
15
|
+
assert len(accounts) == 1, (
|
16
|
+
f"Expected exactly one account in paper trading mode, got {len(accounts)}"
|
17
|
+
)
|
18
|
+
|
19
|
+
front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
|
20
|
+
|
21
|
+
product_info = await async_client.get_product_info(front_ES_future.base())
|
22
|
+
assert product_info is not None, (
|
23
|
+
f"Expected product info for {front_ES_future.base()} to be not None"
|
24
|
+
)
|
25
|
+
assert product_info.multiplier == ES_MULTIPLIER, (
|
26
|
+
f"Expected multiplier for {front_ES_future.base()} to be {ES_MULTIPLIER}, got {product_info.multiplier}"
|
27
|
+
)
|
28
|
+
await async_client.close()
|
29
|
+
|
30
|
+
|
31
|
+
@pytest.mark.asyncio
|
32
|
+
async def test_flattening_position(async_client: AsyncClient):
|
33
|
+
if not async_client.paper_trading:
|
34
|
+
return
|
35
|
+
|
36
|
+
front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
|
37
|
+
[account] = await async_client.list_accounts()
|
38
|
+
account_id = account.account.id
|
39
|
+
|
40
|
+
market_status = await async_client.get_market_status(front_ES_future, "CME")
|
41
|
+
if not market_status.is_trading:
|
42
|
+
await async_client.close()
|
43
|
+
pytest.skip(
|
44
|
+
f"Market for {front_ES_future} is not trading, skipping test_flattening_position"
|
45
|
+
)
|
46
|
+
|
47
|
+
await async_client.place_order(
|
48
|
+
symbol=front_ES_future,
|
49
|
+
venue="CME",
|
50
|
+
dir=OrderDir.BUY,
|
51
|
+
quantity=Decimal(value="100"),
|
52
|
+
account=account_id,
|
53
|
+
order_type=OrderType.MARKET,
|
54
|
+
)
|
55
|
+
|
56
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
57
|
+
|
58
|
+
for tp, position in positions.items():
|
59
|
+
tradable_product = TradableProduct(tp)
|
60
|
+
product_info = await async_client.get_product_info(tradable_product.base())
|
61
|
+
assert product_info is not None, (
|
62
|
+
f"Expected product info for {tradable_product.base()} to be not None"
|
63
|
+
)
|
64
|
+
venue = product_info.primary_venue
|
65
|
+
assert venue is not None, (
|
66
|
+
f"Expected primary venue for {tradable_product.base()} to be not None"
|
67
|
+
)
|
68
|
+
|
69
|
+
market_status = await async_client.get_market_status(
|
70
|
+
symbol=tp,
|
71
|
+
venue=venue,
|
72
|
+
)
|
73
|
+
if not market_status.is_trading:
|
74
|
+
continue
|
75
|
+
|
76
|
+
if position != Decimal(0):
|
77
|
+
flatten_direction = OrderDir.SELL if position > Decimal(0) else OrderDir.BUY
|
78
|
+
|
79
|
+
await async_client.place_order(
|
80
|
+
symbol=tp,
|
81
|
+
dir=flatten_direction,
|
82
|
+
quantity=abs(position),
|
83
|
+
account=account_id,
|
84
|
+
order_type=OrderType.MARKET,
|
85
|
+
)
|
86
|
+
|
87
|
+
await asyncio.sleep(1.5) # wait for orders to be processed
|
88
|
+
|
89
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
90
|
+
assert len(positions) == 0, (
|
91
|
+
f"Expected no positions in paper trading mode, got {len(positions)}"
|
92
|
+
)
|
93
|
+
await async_client.close()
|
94
|
+
|
95
|
+
|
96
|
+
@pytest.mark.asyncio
|
97
|
+
@pytest.mark.timeout(10)
|
98
|
+
async def test_paper_positions(async_client: AsyncClient):
|
99
|
+
venue = "CME"
|
100
|
+
if not async_client.paper_trading:
|
101
|
+
return
|
102
|
+
|
103
|
+
[account] = await async_client.list_accounts()
|
104
|
+
account_id = account.account.id
|
105
|
+
front_ES_future = await async_client.get_front_future("ES CME Futures", venue)
|
106
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
107
|
+
ES_position = positions.get(front_ES_future)
|
108
|
+
|
109
|
+
market_status = await async_client.get_market_status(
|
110
|
+
symbol=front_ES_future,
|
111
|
+
venue=venue,
|
112
|
+
)
|
113
|
+
if not market_status.is_trading:
|
114
|
+
await async_client.close()
|
115
|
+
pytest.skip(
|
116
|
+
f"Market for {front_ES_future} is not trading, skipping test_paper_pnl"
|
117
|
+
)
|
118
|
+
|
119
|
+
# flatten position
|
120
|
+
if ES_position is not None:
|
121
|
+
flatten_direction = OrderDir.SELL if ES_position > Decimal(0) else OrderDir.BUY
|
122
|
+
|
123
|
+
order = await async_client.place_order(
|
124
|
+
symbol=front_ES_future,
|
125
|
+
venue=venue,
|
126
|
+
dir=flatten_direction,
|
127
|
+
quantity=Decimal(value="1"),
|
128
|
+
account=account_id,
|
129
|
+
order_type=OrderType.MARKET,
|
130
|
+
)
|
131
|
+
while True:
|
132
|
+
open_orders = await async_client.get_open_orders(order_ids=[order.id])
|
133
|
+
if not open_orders:
|
134
|
+
break
|
135
|
+
await asyncio.sleep(0.2)
|
136
|
+
|
137
|
+
fills = await async_client.get_fills(order_id=order.id)
|
138
|
+
assert len(fills.fills) == 1, "Expected exactly one fill for the order"
|
139
|
+
assert fills.fills[0].dir == flatten_direction, (
|
140
|
+
"Fill direction does not match order direction"
|
141
|
+
)
|
142
|
+
|
143
|
+
# go long
|
144
|
+
order = await async_client.place_order(
|
145
|
+
symbol=front_ES_future,
|
146
|
+
venue=venue,
|
147
|
+
dir=OrderDir.BUY,
|
148
|
+
quantity=Decimal(value="5"),
|
149
|
+
account=account_id,
|
150
|
+
order_type=OrderType.MARKET,
|
151
|
+
)
|
152
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
153
|
+
assert positions.get(front_ES_future) == Decimal(5), (
|
154
|
+
f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
|
155
|
+
)
|
156
|
+
|
157
|
+
# go long to flat
|
158
|
+
order = await async_client.place_order(
|
159
|
+
symbol=front_ES_future,
|
160
|
+
venue=venue,
|
161
|
+
dir=OrderDir.SELL,
|
162
|
+
quantity=Decimal(value="5"),
|
163
|
+
account=account_id,
|
164
|
+
order_type=OrderType.MARKET,
|
165
|
+
)
|
166
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
167
|
+
assert positions.get(front_ES_future) is None, (
|
168
|
+
f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
|
169
|
+
)
|
170
|
+
|
171
|
+
# go long
|
172
|
+
order = await async_client.place_order(
|
173
|
+
symbol=front_ES_future,
|
174
|
+
venue=venue,
|
175
|
+
dir=OrderDir.BUY,
|
176
|
+
quantity=Decimal(value="8"),
|
177
|
+
account=account_id,
|
178
|
+
order_type=OrderType.MARKET,
|
179
|
+
)
|
180
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
181
|
+
assert positions.get(front_ES_future) == Decimal(8), (
|
182
|
+
f"Expected position in {front_ES_future} to be 8, got {positions.get(front_ES_future)}"
|
183
|
+
)
|
184
|
+
|
185
|
+
# go long to short
|
186
|
+
order = await async_client.place_order(
|
187
|
+
symbol=front_ES_future,
|
188
|
+
venue=venue,
|
189
|
+
dir=OrderDir.SELL,
|
190
|
+
quantity=Decimal(value="10"),
|
191
|
+
account=account_id,
|
192
|
+
order_type=OrderType.MARKET,
|
193
|
+
)
|
194
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
195
|
+
assert positions.get(front_ES_future) == Decimal(-2), (
|
196
|
+
f"Expected position in {front_ES_future} to be -2, got {positions.get(front_ES_future)}"
|
197
|
+
)
|
198
|
+
|
199
|
+
# go flat
|
200
|
+
order = await async_client.place_order(
|
201
|
+
symbol=front_ES_future,
|
202
|
+
venue=venue,
|
203
|
+
dir=OrderDir.BUY,
|
204
|
+
quantity=Decimal(value="2"),
|
205
|
+
account=account_id,
|
206
|
+
order_type=OrderType.MARKET,
|
207
|
+
)
|
208
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
209
|
+
assert positions.get(front_ES_future) is None, (
|
210
|
+
f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
|
211
|
+
)
|
212
|
+
|
213
|
+
# go short
|
214
|
+
order = await async_client.place_order(
|
215
|
+
symbol=front_ES_future,
|
216
|
+
venue=venue,
|
217
|
+
dir=OrderDir.SELL,
|
218
|
+
quantity=Decimal(value="5"),
|
219
|
+
account=account_id,
|
220
|
+
order_type=OrderType.MARKET,
|
221
|
+
)
|
222
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
223
|
+
assert positions.get(front_ES_future) == Decimal(-5), (
|
224
|
+
f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
|
225
|
+
)
|
226
|
+
|
227
|
+
# go short to flat
|
228
|
+
order = await async_client.place_order(
|
229
|
+
symbol=front_ES_future,
|
230
|
+
venue=venue,
|
231
|
+
dir=OrderDir.BUY,
|
232
|
+
quantity=Decimal(value="5"),
|
233
|
+
account=account_id,
|
234
|
+
order_type=OrderType.MARKET,
|
235
|
+
)
|
236
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
237
|
+
assert positions.get(front_ES_future) is None, (
|
238
|
+
f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
|
239
|
+
)
|
240
|
+
|
241
|
+
# go short
|
242
|
+
order = await async_client.place_order(
|
243
|
+
symbol=front_ES_future,
|
244
|
+
venue=venue,
|
245
|
+
dir=OrderDir.SELL,
|
246
|
+
quantity=Decimal(value="5"),
|
247
|
+
account=account_id,
|
248
|
+
order_type=OrderType.MARKET,
|
249
|
+
)
|
250
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
251
|
+
assert positions.get(front_ES_future) == Decimal(-5), (
|
252
|
+
f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
|
253
|
+
)
|
254
|
+
|
255
|
+
# go short to long
|
256
|
+
order = await async_client.place_order(
|
257
|
+
symbol=front_ES_future,
|
258
|
+
venue=venue,
|
259
|
+
dir=OrderDir.BUY,
|
260
|
+
quantity=Decimal(value="10"),
|
261
|
+
account=account_id,
|
262
|
+
order_type=OrderType.MARKET,
|
263
|
+
)
|
264
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
265
|
+
assert positions.get(front_ES_future) == Decimal(5), (
|
266
|
+
f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
|
267
|
+
)
|
268
|
+
await async_client.close()
|
269
|
+
|
270
|
+
|
271
|
+
@pytest.mark.asyncio
|
272
|
+
@pytest.mark.timeout(10)
|
273
|
+
async def test_paper_pnl(async_client: AsyncClient):
|
274
|
+
if not async_client.paper_trading:
|
275
|
+
return
|
276
|
+
|
277
|
+
[account] = await async_client.list_accounts()
|
278
|
+
account_id = account.account.id
|
279
|
+
front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
|
280
|
+
positions = await async_client.get_positions(accounts=[account_id])
|
281
|
+
ES_position = positions.get(front_ES_future)
|
282
|
+
|
283
|
+
market_status = await async_client.get_market_status(
|
284
|
+
symbol=front_ES_future,
|
285
|
+
venue="CME",
|
286
|
+
)
|
287
|
+
if not market_status.is_trading:
|
288
|
+
await async_client.close()
|
289
|
+
pytest.skip(
|
290
|
+
f"Market for {front_ES_future} is not trading, skipping test_paper_pnl"
|
291
|
+
)
|
292
|
+
|
293
|
+
# flatten position
|
294
|
+
if ES_position is not None:
|
295
|
+
flatten_direction = OrderDir.SELL if ES_position > Decimal(0) else OrderDir.BUY
|
296
|
+
|
297
|
+
quantity = abs(ES_position)
|
298
|
+
|
299
|
+
order = await async_client.place_order(
|
300
|
+
symbol=front_ES_future,
|
301
|
+
venue="CME",
|
302
|
+
dir=flatten_direction,
|
303
|
+
quantity=quantity,
|
304
|
+
account=account_id,
|
305
|
+
order_type=OrderType.MARKET,
|
306
|
+
)
|
307
|
+
while True:
|
308
|
+
open_orders = await async_client.get_open_orders(order_ids=[order.id])
|
309
|
+
if not open_orders:
|
310
|
+
break
|
311
|
+
await asyncio.sleep(0.2)
|
312
|
+
|
313
|
+
position = await async_client.get_positions(accounts=[account_id])
|
314
|
+
assert len(position) == 0, (
|
315
|
+
f"Expected no positions in paper trading mode, got {position}"
|
316
|
+
)
|
317
|
+
|
318
|
+
account_summary = await async_client.get_account_summary(account_id)
|
319
|
+
assert account_summary.purchasing_power is not None, (
|
320
|
+
"Expected purchasing power after trades to be set, got None"
|
321
|
+
)
|
322
|
+
|
323
|
+
pre_purchasing_power = account_summary.purchasing_power
|
324
|
+
|
325
|
+
quantity = Decimal(value="7")
|
326
|
+
|
327
|
+
sell_order = await async_client.place_order(
|
328
|
+
symbol=front_ES_future,
|
329
|
+
venue="CME",
|
330
|
+
dir=OrderDir.SELL,
|
331
|
+
quantity=quantity,
|
332
|
+
account=account_id,
|
333
|
+
order_type=OrderType.MARKET,
|
334
|
+
)
|
335
|
+
|
336
|
+
buy_order = await async_client.place_order(
|
337
|
+
symbol=front_ES_future,
|
338
|
+
venue="CME",
|
339
|
+
dir=OrderDir.BUY,
|
340
|
+
quantity=quantity,
|
341
|
+
account=account_id,
|
342
|
+
order_type=OrderType.MARKET,
|
343
|
+
)
|
344
|
+
await asyncio.sleep(1.5) # wait for order to be processed
|
345
|
+
|
346
|
+
sell_fill = await async_client.get_fills(order_id=sell_order.id)
|
347
|
+
sell_fill_price = sell_fill.fills[0].price
|
348
|
+
|
349
|
+
buy_fill = await async_client.get_fills(order_id=buy_order.id)
|
350
|
+
buy_fill_price = buy_fill.fills[0].price
|
351
|
+
|
352
|
+
pnl = (sell_fill_price - buy_fill_price) * ES_MULTIPLIER * quantity
|
353
|
+
|
354
|
+
account_summary = await async_client.get_account_summary(account_id)
|
355
|
+
assert account_summary.purchasing_power is not None, (
|
356
|
+
"Expected purchasing power after trades to be set, got None"
|
357
|
+
)
|
358
|
+
post_purchasing_power = account_summary.purchasing_power
|
359
|
+
|
360
|
+
assert post_purchasing_power == pre_purchasing_power + pnl, (
|
361
|
+
f"Expected purchasing power to be {pre_purchasing_power + pnl}, got {post_purchasing_power}.\n"
|
362
|
+
f"Buy fill price: {buy_fill_price}, Sell fill price: {sell_fill_price}, quantity: {quantity}, pnl: {pnl}, pre_purchasing_power: {pre_purchasing_power}, post_purchasing_power: {post_purchasing_power}"
|
363
|
+
)
|
364
|
+
await async_client.close()
|
@@ -1,41 +1,41 @@
|
|
1
|
-
|
1
|
+
from decimal import Decimal
|
2
2
|
|
3
|
-
|
3
|
+
from architect_py.utils.nearest_tick import TickRoundMethod
|
4
4
|
|
5
5
|
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
|
6
|
+
def _test_rounding():
|
7
|
+
# Example usage
|
8
|
+
value = Decimal("123.454")
|
9
|
+
tick_size = Decimal("0.01")
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
rounded_value = TickRoundMethod.ROUND(value, tick_size=tick_size)
|
12
|
+
assert rounded_value == Decimal("123.45")
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
rounded_ceil = TickRoundMethod.CEIL(value, tick_size)
|
15
|
+
assert rounded_ceil == Decimal("123.46")
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
rounded_floor = TickRoundMethod.FLOOR(value, tick_size)
|
18
|
+
assert rounded_floor == Decimal("123.45")
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
rounded_floor = TickRoundMethod.FLOOR(Decimal("123.459"), tick_size)
|
21
|
+
assert rounded_floor == Decimal("123.45")
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
rounded_toward_zero_pos = TickRoundMethod.TOWARD_ZERO(value, tick_size)
|
24
|
+
assert rounded_toward_zero_pos == Decimal("123.45")
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
value_negative = Decimal("-123.456")
|
27
|
+
rounded_toward_zero_neg = TickRoundMethod.TOWARD_ZERO(value_negative, tick_size)
|
28
|
+
assert rounded_toward_zero_neg == Decimal("-123.45")
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
rounded_away_from_zero_pos = TickRoundMethod.AWAY_FROM_ZERO(value, tick_size)
|
31
|
+
assert rounded_away_from_zero_pos == Decimal("123.46")
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
rounded_away_from_zero_neg = TickRoundMethod.AWAY_FROM_ZERO(
|
34
|
+
value_negative, tick_size
|
35
|
+
)
|
36
|
+
assert rounded_away_from_zero_neg == Decimal("-123.46")
|
37
37
|
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
if __name__ == "__main__":
|
40
|
+
_test_rounding()
|
41
|
+
print("rounding.py: All tests passed!")
|
architect_py/utils/pandas.py
CHANGED
@@ -4,7 +4,7 @@ import msgspec
|
|
4
4
|
import pandas as pd
|
5
5
|
|
6
6
|
if TYPE_CHECKING:
|
7
|
-
from .. import Candle
|
7
|
+
from .. import Candle, Ticker
|
8
8
|
|
9
9
|
CANDLES_FIELD_MAP = {
|
10
10
|
"av": "sell_volume",
|
@@ -43,3 +43,52 @@ def candles_to_dataframe(candles: List["Candle"]) -> pd.DataFrame:
|
|
43
43
|
df.style.hide(["tn", "ts"], axis=1)
|
44
44
|
df.set_index("timestamp", inplace=True)
|
45
45
|
return df
|
46
|
+
|
47
|
+
|
48
|
+
def tickers_to_dataframe(tickers: List["Ticker"]) -> pd.DataFrame:
|
49
|
+
records = msgspec.to_builtins(tickers)
|
50
|
+
df = pd.DataFrame.from_records(records)
|
51
|
+
df.rename(
|
52
|
+
columns={
|
53
|
+
"s": "symbol",
|
54
|
+
"ve": "venue",
|
55
|
+
"ap": "ask_price",
|
56
|
+
"as": "ask_size",
|
57
|
+
"bp": "bid_price",
|
58
|
+
"bs": "bid_size",
|
59
|
+
"dividend": "dividend",
|
60
|
+
"dividend_yield": "dividend_yield",
|
61
|
+
"eps_adj": "eps_adj",
|
62
|
+
"fr": "funding_rate",
|
63
|
+
"ft": "next_funding_time",
|
64
|
+
"h": "high_24h",
|
65
|
+
"ip": "index_price",
|
66
|
+
"isp": "indicative_settlement_price",
|
67
|
+
"l": "low_24h",
|
68
|
+
"market_cap": "market_cap",
|
69
|
+
"mp": "mark_price",
|
70
|
+
"o": "open_24h",
|
71
|
+
"oi": "open_interest",
|
72
|
+
"p": "last_price",
|
73
|
+
"price_to_earnings": "price_to_earnings",
|
74
|
+
"q": "last_size",
|
75
|
+
"sd": "last_settlement_date",
|
76
|
+
"shares_outstanding_weighted_adj": "shares_outstanding_weighted_adj",
|
77
|
+
"sp": "last_settlement_price",
|
78
|
+
"v": "volume_24h",
|
79
|
+
"vm": "volume_30d",
|
80
|
+
"xh": "session_high",
|
81
|
+
"xl": "session_low",
|
82
|
+
"xo": "session_open",
|
83
|
+
"xv": "session_volume",
|
84
|
+
},
|
85
|
+
inplace=True,
|
86
|
+
)
|
87
|
+
df["timestamp"] = pd.to_datetime(
|
88
|
+
df["ts"] * 1_000_000_000 + df["tn"],
|
89
|
+
unit="ns",
|
90
|
+
utc=True,
|
91
|
+
)
|
92
|
+
df.style.hide(["tn", "ts"], axis=1)
|
93
|
+
df.set_index("symbol", inplace=True)
|
94
|
+
return df
|