trading-state 0.0.1__tar.gz

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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 kaelzhang <>, contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: trading-state
3
+ Version: 0.0.1
4
+ Summary: A lightweight Python engine for simulating trading account balances, order states, and exchange-style constraints.
5
+ Author-email: Kael Zhang <i+pypi@kael.me>
6
+ Project-URL: Homepage, https://github.com/kaelzhang/python-trading-state
7
+ Keywords: trading-state
8
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.7
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: dev
21
+ Requires-Dist: coverage; extra == "dev"
22
+ Requires-Dist: ruff; extra == "dev"
23
+ Requires-Dist: pytest; extra == "dev"
24
+ Requires-Dist: pytest-asyncio; extra == "dev"
25
+ Requires-Dist: pytest-cov; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+ Requires-Dist: mypy; extra == "dev"
28
+ Requires-Dist: build; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ [![](https://github.com/kaelzhang/python-trading-state/actions/workflows/python.yml/badge.svg)](https://github.com/kaelzhang/python-trading-state/actions/workflows/python.yml)
32
+ [![](https://codecov.io/gh/kaelzhang/python-trading-state/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/python-trading-state)
33
+ [![](https://img.shields.io/pypi/v/trading-state.svg)](https://pypi.org/project/trading-state/)
34
+ [![Conda version](https://img.shields.io/conda/vn/conda-forge/trading-state)](https://anaconda.org/conda-forge/trading-state)
35
+ [![](https://img.shields.io/pypi/l/trading-state.svg)](https://github.com/kaelzhang/python-trading-state)
36
+
37
+ # trading-state
38
+
39
+ `trading-state` is a small, focused Python library that models the dynamic state of a trading account, including balances, positions, open orders, and PnL, under realistic exchange-like constraints.
40
+
41
+ It provides configurable rules for price limits, notional and quantity limits, order validation, and order lifecycle transitions (new, partially filled, filled, canceled, rejected), making it easy to plug into backtesting frameworks, execution simulators, or custom quant research tools.
42
+
43
+ By separating “trading state” from strategy logic, `trading-state` helps you build cleaner, more testable quantitative trading systems.
44
+
45
+ ## Install
46
+
47
+ ```sh
48
+ $ pip install trading-state
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```py
54
+ from trading_state import TradingState
55
+ ```
56
+
57
+ ## License
58
+
59
+ [MIT](LICENSE)
@@ -0,0 +1,29 @@
1
+ [![](https://github.com/kaelzhang/python-trading-state/actions/workflows/python.yml/badge.svg)](https://github.com/kaelzhang/python-trading-state/actions/workflows/python.yml)
2
+ [![](https://codecov.io/gh/kaelzhang/python-trading-state/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/python-trading-state)
3
+ [![](https://img.shields.io/pypi/v/trading-state.svg)](https://pypi.org/project/trading-state/)
4
+ [![Conda version](https://img.shields.io/conda/vn/conda-forge/trading-state)](https://anaconda.org/conda-forge/trading-state)
5
+ [![](https://img.shields.io/pypi/l/trading-state.svg)](https://github.com/kaelzhang/python-trading-state)
6
+
7
+ # trading-state
8
+
9
+ `trading-state` is a small, focused Python library that models the dynamic state of a trading account, including balances, positions, open orders, and PnL, under realistic exchange-like constraints.
10
+
11
+ It provides configurable rules for price limits, notional and quantity limits, order validation, and order lifecycle transitions (new, partially filled, filled, canceled, rejected), making it easy to plug into backtesting frameworks, execution simulators, or custom quant research tools.
12
+
13
+ By separating “trading state” from strategy logic, `trading-state` helps you build cleaner, more testable quantitative trading systems.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ $ pip install trading-state
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```py
24
+ from trading_state import TradingState
25
+ ```
26
+
27
+ ## License
28
+
29
+ [MIT](LICENSE)
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trading-state"
7
+ authors = [
8
+ {name = "Kael Zhang", email = "i+pypi@kael.me"}
9
+ ]
10
+ description = "A lightweight Python engine for simulating trading account balances, order states, and exchange-style constraints."
11
+ readme = "README.md"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["trading-state"]
14
+ classifiers = [
15
+ "Topic :: Software Development :: Libraries :: Python Modules",
16
+ "Programming Language :: Python :: 3.7",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: Implementation :: PyPy",
23
+ "Topic :: Software Development :: Libraries :: Python Modules"
24
+ ]
25
+ requires-python = ">=3.7"
26
+ dynamic = ['version']
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/kaelzhang/python-trading-state"
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ # "codecov",
35
+ "coverage",
36
+ "ruff",
37
+ "pytest",
38
+ "pytest-asyncio",
39
+ "pytest-cov",
40
+ "twine",
41
+ "mypy",
42
+ "build"
43
+ ]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = {attr = "trading_state.__version__"}
47
+
48
+ [tool.setuptools.package-data]
49
+ trading_state = ["py.typed"]
50
+
51
+ [tool.mypy]
52
+ warn_return_any = true
53
+ ignore_missing_imports = true
54
+ no_implicit_optional = true
55
+ strict_optional = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,401 @@
1
+ # import asyncio
2
+ import pytest
3
+
4
+ from trading_state.state import (
5
+ TraderState,
6
+
7
+ # TicketOrderSide
8
+ )
9
+ from trading_state.types import (
10
+ Balance
11
+ )
12
+
13
+ BTC = 'BTC'
14
+ USDT = 'USDT'
15
+ SYMBOL = BTC + USDT
16
+
17
+
18
+ def set_price(state: TraderState):
19
+ state.set_price(SYMBOL, 7000.)
20
+
21
+
22
+ def set_symbol_info(state: TraderState):
23
+ state.set_symbol_info(
24
+ SYMBOL,
25
+ BTC,
26
+ USDT,
27
+
28
+ '0.01',
29
+ '1000000',
30
+ '0.01',
31
+
32
+ '0.000001',
33
+ '9000',
34
+ '0.000001',
35
+
36
+ '10.00000000'
37
+ )
38
+
39
+
40
+ def set_balances(state: TraderState):
41
+ state.update_balances([
42
+ Balance(
43
+ BTC,
44
+ 15.,
45
+ 0.
46
+ ),
47
+ Balance(
48
+ USDT,
49
+ 0.001,
50
+ 0.
51
+ )
52
+ ])
53
+
54
+
55
+ def set_info(state: TraderState):
56
+ set_price(state)
57
+ set_symbol_info(state)
58
+
59
+
60
+ def test_support():
61
+ state = TraderState()
62
+
63
+ set_info(state)
64
+
65
+ assert state.support_symbol(SYMBOL)
66
+
67
+ LTEBTC = 'LTEBTC'
68
+
69
+ assert not state.support_symbol(LTEBTC)
70
+ assert state.create_ticket(
71
+ LTEBTC,
72
+ 1.,
73
+ False,
74
+ None
75
+ ) == (None, set())
76
+
77
+
78
+ def test_non_diff():
79
+ state = TraderState()
80
+
81
+ set_balances(state)
82
+ set_info(state)
83
+
84
+ state.expect(
85
+ SYMBOL,
86
+ 1.,
87
+ False,
88
+ None
89
+ )
90
+
91
+ diffed, to_cancel = state.get_tickets()
92
+
93
+ assert not diffed
94
+ assert not to_cancel
95
+
96
+
97
+ def test_specified_price():
98
+ state = TraderState()
99
+
100
+ set_balances(state)
101
+ set_info(state)
102
+
103
+ ticket, to_cancel = state.create_ticket(
104
+ SYMBOL,
105
+ 0.,
106
+ False,
107
+ 7000.
108
+ )
109
+
110
+ assert not to_cancel
111
+ assert ticket is not None
112
+ assert ticket.price == 7000.
113
+
114
+ ticket2, to_cancel2 = state.create_ticket(
115
+ SYMBOL,
116
+ 0.,
117
+ False,
118
+ 6000.
119
+ )
120
+
121
+ assert ticket in to_cancel2
122
+ assert ticket2.price == 6000.
123
+
124
+ state.expect(
125
+ SYMBOL,
126
+ 1.,
127
+ False,
128
+ 5000.
129
+ )
130
+
131
+ # Different direction, cancel previous ticket
132
+ # ---------------------------------------------
133
+
134
+ tickets, to_cancel = state.get_tickets()
135
+
136
+ assert ticket2 in to_cancel
137
+ assert not tickets
138
+
139
+ # get_tickets again, previous ticket already closed
140
+ # ---------------------------------------------
141
+
142
+ tickets, to_cancel = state.get_tickets()
143
+
144
+ assert not tickets
145
+ assert not tickets
146
+
147
+ # Update balances should reset diff
148
+
149
+ state.update_balances([
150
+ Balance(
151
+ USDT,
152
+ 1000.,
153
+ 0.
154
+ )
155
+ ])
156
+
157
+ tickets, to_cancel = state.get_tickets()
158
+
159
+ ticket = tickets[0]
160
+
161
+ assert ticket.price == 5000.
162
+ assert ticket.symbol.name == SYMBOL
163
+ assert not to_cancel
164
+
165
+ # Will remove expectations
166
+
167
+ state.create_ticket(
168
+ SYMBOL,
169
+ 0.,
170
+ False,
171
+ None
172
+ )
173
+
174
+ assert not state._expected
175
+
176
+
177
+ @pytest.mark.asyncio
178
+ async def test_diff():
179
+ state = TraderState()
180
+
181
+ def expect_sell_all(quantity: float = 15.):
182
+ # sell all
183
+ state.expect(
184
+ SYMBOL,
185
+ 0.,
186
+ False,
187
+ None
188
+ )
189
+
190
+ if not state._expected:
191
+ return
192
+
193
+ assert repr(state._expected[SYMBOL]) == '<SymbolPosition BTCUSDT: value: 0.0, asap: False, price: None>'
194
+
195
+ diff, _ = state.get_tickets()
196
+
197
+ if not diff:
198
+ return
199
+
200
+ ticket = diff[0]
201
+
202
+ assert str(ticket.order_side) == 'SELL'
203
+ assert ticket.symbol.name == SYMBOL
204
+ assert ticket.price == 7000.
205
+
206
+ assert ticket.quantity == '15.000000'
207
+
208
+ assert ticket.locked_asset == BTC
209
+ assert ticket.locked_quantity == quantity
210
+
211
+ return ticket
212
+
213
+ def expect_all_in():
214
+ # buy all
215
+ state.expect(
216
+ SYMBOL,
217
+ 1.,
218
+ False,
219
+ None
220
+ )
221
+
222
+ diff, _ = state.get_tickets()
223
+
224
+ if not diff:
225
+ return
226
+
227
+ return diff[0]
228
+
229
+ state.expect(
230
+ SYMBOL,
231
+ 0.,
232
+ False,
233
+ None
234
+ )
235
+
236
+ assert not state._expected, 'BTC not supported'
237
+
238
+ tickets, to_cancel = state.get_tickets()
239
+ assert not tickets
240
+ assert not to_cancel
241
+
242
+ assert expect_sell_all() is None, 'symbol info is not set'
243
+
244
+ set_symbol_info(state)
245
+
246
+ assert repr(state._symbol_infos[SYMBOL]) == '<SymbolInfo BTCUSDT: price_step >= 0.01, quantity_step >= 0.000001>'
247
+
248
+ assert expect_sell_all() is None, 'price is not set'
249
+
250
+ state.set_price(SYMBOL, 7000.)
251
+
252
+ assert expect_sell_all() is None, 'balances are not set'
253
+
254
+ state.update_balances([
255
+ Balance(
256
+ BTC,
257
+ 15.,
258
+ 0.
259
+ ),
260
+ Balance(
261
+ USDT,
262
+ 0.001,
263
+ 0.
264
+ )
265
+ ])
266
+
267
+ assert repr(state._balances[BTC]) == '<Balance BTC: free: 15.0, locked: 0.0>'
268
+
269
+ ticket0 = expect_sell_all()
270
+ assert ticket0 is not None
271
+
272
+ # Unsolved tickets will be always returned by state.tickets
273
+
274
+ tickets, _ = state.get_tickets()
275
+ assert tickets[0] is ticket0
276
+
277
+ # ticket 0 will be closed
278
+ assert expect_all_in() is None
279
+
280
+ ticket1 = expect_sell_all()
281
+
282
+ assert ticket1 is not None
283
+
284
+ # Test to close ticket duplicately
285
+ # ------------------------------------------------------
286
+
287
+ state.close_ticket(ticket1)
288
+ state.close_ticket(ticket1)
289
+
290
+ # Test to create ticket
291
+ # ------------------------------------------------------
292
+
293
+ ticket_sell, to_cancel = state.create_ticket(
294
+ SYMBOL,
295
+ 0.,
296
+ False,
297
+ None
298
+ )
299
+
300
+ assert ticket_sell.position.value == 0.
301
+ assert ticket_sell.symbol.name == SYMBOL
302
+ assert not to_cancel
303
+
304
+ # There is already a ticket
305
+ # ------------------------------------------------------
306
+
307
+ ticket_sell2, to_cancel2 = state.create_ticket(
308
+ SYMBOL,
309
+ 0.,
310
+ False,
311
+ None
312
+ )
313
+
314
+ assert ticket_sell2 is None
315
+ assert not to_cancel2
316
+
317
+ ticket_buy, to_cancel = state.create_ticket(
318
+ SYMBOL,
319
+ 1.,
320
+ False,
321
+ None
322
+ )
323
+
324
+ assert ticket_buy is None
325
+ assert ticket_sell in to_cancel
326
+
327
+ assert not state._expected
328
+
329
+ state.update_balances([
330
+ Balance(
331
+ BTC,
332
+ 0.,
333
+ 0.
334
+ ),
335
+ Balance(
336
+ USDT,
337
+ 70000,
338
+ 0.
339
+ )
340
+ ])
341
+
342
+ assert expect_all_in() is not None
343
+ assert expect_sell_all() is None
344
+
345
+ # Set quota to 0
346
+ state.set_quota(BTC, 0.)
347
+ state.set_quota('ETH', 0.)
348
+
349
+ assert expect_all_in() is None, 'not enough quota'
350
+
351
+ state.set_quota(BTC, None)
352
+
353
+ state.update_balances([
354
+ Balance(
355
+ USDT,
356
+ 0.006,
357
+ 0.
358
+ )
359
+ ])
360
+
361
+ assert expect_all_in() is None, 'min price step'
362
+
363
+ state.update_balances([
364
+ Balance(
365
+ USDT,
366
+ 9.,
367
+ 0.
368
+ )
369
+ ])
370
+
371
+ assert expect_all_in() is None, 'min notional'
372
+
373
+ state.update_balances([
374
+ Balance(
375
+ USDT,
376
+ 15.,
377
+ 0.
378
+ )
379
+ ])
380
+
381
+ state.set_price(SYMBOL, 70000000.)
382
+ state.set_price(SYMBOL, 70000000.)
383
+
384
+ assert expect_all_in() is None, 'not enough'
385
+
386
+ state.set_price(SYMBOL, 7000.)
387
+
388
+ ticket2 = expect_all_in()
389
+
390
+ assert ticket2 is not None
391
+
392
+ assert ticket2.quantity == '0.002142'
393
+
394
+ state.clear()
395
+
396
+ tickets, to_cancel = state.get_tickets()
397
+
398
+ assert not tickets
399
+ assert not to_cancel
400
+
401
+ assert not state._expected
@@ -0,0 +1,30 @@
1
+ from trading_state.types import (
2
+ SymbolInfo,
3
+ Balance
4
+ )
5
+
6
+
7
+ def test_float_precision():
8
+ info = SymbolInfo(
9
+ 'BTCUSDT',
10
+ 'BTC',
11
+ 'USDT',
12
+
13
+ '0.010000000000',
14
+ '1000000',
15
+ '0.010000000000',
16
+
17
+ '0.000001000000',
18
+ '9000',
19
+ '0.000001000000',
20
+
21
+ '10.00000000'
22
+ )
23
+
24
+ assert info.min_quantity_step_precision == 6
25
+
26
+
27
+ def test_balance():
28
+ balance = Balance('BTCUSDT', 0, 0)
29
+
30
+ assert not balance.exists()
@@ -0,0 +1,2 @@
1
+ # The first alpha version
2
+ __version__ = '0.0.1'