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.
- trading_state-0.0.1/LICENSE +20 -0
- trading_state-0.0.1/PKG-INFO +59 -0
- trading_state-0.0.1/README.md +29 -0
- trading_state-0.0.1/pyproject.toml +55 -0
- trading_state-0.0.1/setup.cfg +4 -0
- trading_state-0.0.1/test/test_trading_state.py +401 -0
- trading_state-0.0.1/test/test_types.py +30 -0
- trading_state-0.0.1/trading_state/__init__.py +2 -0
- trading_state-0.0.1/trading_state/state.py +632 -0
- trading_state-0.0.1/trading_state/symbol_info.py +99 -0
- trading_state-0.0.1/trading_state/types.py +374 -0
- trading_state-0.0.1/trading_state/util.py +69 -0
- trading_state-0.0.1/trading_state.egg-info/PKG-INFO +59 -0
- trading_state-0.0.1/trading_state.egg-info/SOURCES.txt +15 -0
- trading_state-0.0.1/trading_state.egg-info/dependency_links.txt +1 -0
- trading_state-0.0.1/trading_state.egg-info/requires.txt +10 -0
- trading_state-0.0.1/trading_state.egg-info/top_level.txt +1 -0
|
@@ -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)
|
|
32
|
+
[](https://codecov.io/gh/kaelzhang/python-trading-state)
|
|
33
|
+
[](https://pypi.org/project/trading-state/)
|
|
34
|
+
[](https://anaconda.org/conda-forge/trading-state)
|
|
35
|
+
[](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)
|
|
2
|
+
[](https://codecov.io/gh/kaelzhang/python-trading-state)
|
|
3
|
+
[](https://pypi.org/project/trading-state/)
|
|
4
|
+
[](https://anaconda.org/conda-forge/trading-state)
|
|
5
|
+
[](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,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()
|