Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_Tinkoff-stream-grid-bot-main/tinkoff-stream-grid-bot.py
5925 views
1
import sqlite3
2
import time
3
import sys
4
import json
5
import logging
6
from tinkoff.invest import Client, RequestError
7
from tinkoff.invest import OrderType, OrderDirection, Quotation, OrderExecutionReportStatus
8
9
#for token import
10
import os
11
12
#Token import to variables
13
config_file = "config.json"
14
15
if os.path.isfile(config_file):
16
with open(file=config_file) as config:
17
config = json.load(config)
18
TOKEN = config.get('token')
19
ACCID = config.get('account_id')
20
shares = config.get('shares')
21
else:
22
print ("No " + config_file + " exists.")
23
exit(0)
24
25
sqlite = sqlite3.connect('sqlite_brand_new_stream2.db')
26
cursor = sqlite.cursor()
27
28
logger = open('bot_stat.log', 'a')
29
30
31
def log(*args):
32
message = ' '.join(map(str, args))
33
print(message)
34
logger.write(message + "\n")
35
logger.flush()
36
37
def makefloat(m) -> float:
38
return float(format(m.units + m.nano / 10 ** 9, '.9f'))
39
40
def makequotation(m) -> Quotation:
41
return Quotation(*[int(a) for a in format(m, '.9f').split('.')])
42
43
def main() -> int:
44
sql = '''CREATE TABLE IF NOT EXISTS `share` (
45
id INTEGER PRIMARY KEY AUTOINCREMENT,
46
ticker VARCHAR(50) NOT NULL UNIQUE,
47
figi VARCHAR(50)
48
);'''
49
cursor.execute(sql)
50
sqlite.commit()
51
52
sql = '''CREATE TABLE IF NOT EXISTS `price` (
53
id INTEGER PRIMARY KEY AUTOINCREMENT,
54
share_id INTEGER NOT NULL,
55
ticker VARCHAR(50) NOT NULL,
56
price DECIMAL(20,10) NOT NULL
57
);'''
58
cursor.execute(sql)
59
sqlite.commit()
60
61
sql = '''CREATE TABLE IF NOT EXISTS `order` (
62
id INTEGER PRIMARY KEY AUTOINCREMENT,
63
share_id INTEGER NOT NULL,
64
ticker VARCHAR(50) NOT NULL,
65
order_id VARCHAR(64) UNIQUE,
66
price DECIMAL(20,10) NOT NULL,
67
direction INTEGER
68
);
69
'''
70
cursor.execute(sql)
71
sqlite.commit()
72
73
with Client(token=TOKEN, app_name="nazmiev.Tinkoff-stream-grid-bot") as client:
74
for instrument in client.instruments.shares().instruments:
75
if (instrument.ticker in shares):
76
share = shares[instrument.ticker]
77
share['ticker'] = instrument.ticker
78
share['figi'] = instrument.figi
79
share['min_price_step'] = makefloat(instrument.min_price_increment)
80
81
cursor.execute("INSERT OR IGNORE INTO `share` (ticker, figi) VALUES (?, ?)", (instrument.ticker, instrument.figi))
82
sqlite.commit()
83
84
share['id'] = cursor.lastrowid if cursor.lastrowid else cursor.execute("SELECT id FROM `share` WHERE ticker = ?", (instrument.ticker, )).fetchone()[0]
85
86
for ticker, share in shares.items():
87
last_price = client.market_data.get_last_prices(figi=[share['figi']]).last_prices[0].price
88
last_price = makefloat(last_price)
89
print(ticker, share['figi'], "last price", last_price, "min step", share['min_price_step'])
90
91
if share['start_price'] == 0:
92
share['start_price'] = last_price
93
94
# достать всё из базы. Если нет, то заполнить
95
prices = get_prices(share)
96
if prices:
97
continue
98
99
# удаляем все открытые ордера
100
client.cancel_all_orders(account_id=ACCID)
101
102
# заполняем цены
103
for i in range(share['number'] + 1):
104
price = share['start_price'] + share['start_price'] * share['price_step'] * (i - share['number'] / 2)
105
price = round(price / share['min_price_step']) * share['min_price_step']
106
price = float(format(price, '.9f'))
107
108
cursor.execute("INSERT INTO `price` (share_id, ticker, price) VALUES (?, ?, ?)", (share['id'], ticker, price))
109
sqlite.commit()
110
111
112
# по ценам выставляем ордера
113
for share in shares.values():
114
create_orders(client, share)
115
116
# засечь время
117
orders_time = time.time()
118
119
while True:
120
try:
121
for response in client.orders_stream.trades_stream([ACCID]):
122
print(time.asctime(), response)
123
124
if not response.order_trades:
125
print(time.asctime(), "not response.order_trades")
126
# если в базе нет ордеров и прошла минута пытаться перевыставить ордера
127
if time.time() - orders_time >= 1 * 60:
128
for share in shares.values():
129
create_orders(client, share)
130
orders_time = time.time()
131
continue
132
133
order_id = response.order_trades.order_id
134
handle_order(client, order_id)
135
orders_time = time.time()
136
except RequestError as err:
137
# неведомая ошибка
138
print(time.asctime(), "неведомая ошибка", err)
139
140
return 0
141
142
def create_orders(client, share):
143
share_id = share['id']
144
figi = share['figi']
145
quantity = share['quantity']
146
147
prices = get_prices(share)
148
149
last_price = client.market_data.get_last_prices(figi=[figi]).last_prices[0].price
150
last_price = makefloat(last_price)
151
skip_price = 0
152
for price in prices:
153
if abs(price - last_price) < abs(price - skip_price):
154
skip_price = price
155
156
#пробуем найти цену под sell
157
new_skip_price = cursor.execute(
158
"SELECT MAX(price) FROM price P WHERE share_id = ? AND price < (SELECT MIN(price) FROM `order` WHERE direction = ? AND share_id = P.share_id)",
159
(share_id, OrderDirection.ORDER_DIRECTION_SELL)).fetchone()[0]
160
print(time.asctime(), 'new_skip_price sell', new_skip_price)
161
162
if not new_skip_price:
163
# пробуем найти цену над buy
164
new_skip_price = cursor.execute(
165
"SELECT MIN(price) FROM price P WHERE share_id = ? AND price > (SELECT MAX(price) FROM `order` WHERE direction = ? AND share_id = P.share_id)",
166
(share_id, OrderDirection.ORDER_DIRECTION_BUY)).fetchone()[0]
167
print(time.asctime(), 'new_skip_price buy', new_skip_price)
168
169
if new_skip_price:
170
print(time.asctime(), 'new_skip_price', new_skip_price)
171
skip_price = new_skip_price
172
173
print(time.asctime(), 'last', last_price, 'skip', skip_price)
174
175
# пробуем выставлять ордера от текущей цены вверх и вниз, чтобы сначала пытался выставить ближайшие ордера, которые скорей всего сработают
176
# sorted(prices, key=lambda x: abs(x - skip_price))
177
178
for price in prices:
179
# достать ордер из базы
180
order = cursor.execute("SELECT order_id FROM `order` WHERE `share_id` = ? AND `price` = ?", (share_id, price)).fetchone()
181
if order:
182
handle_order(client, order[0])
183
continue
184
185
if price == skip_price:
186
continue
187
188
# create order
189
direction = OrderDirection.ORDER_DIRECTION_SELL if price > last_price else OrderDirection.ORDER_DIRECTION_BUY
190
price_quotation = makequotation(price)
191
try:
192
order = client.orders.post_order(account_id=ACCID, figi=figi, quantity=quantity,
193
price=price_quotation, direction=direction,
194
order_type=OrderType.ORDER_TYPE_LIMIT,
195
order_id=str(time.time_ns()))
196
# print('post_order', ACCID, figi, quantity, price_quotation, direction)
197
# order = type('obj', (object,), { 'order_id': str(time.time_ns()) })
198
199
cursor.execute("INSERT INTO `order` (share_id, ticker, order_id, price, direction) VALUES (?, ?, ?, ?, ?)",
200
[share_id, share['ticker'], order.order_id, price, direction])
201
sqlite.commit()
202
print(time.asctime(), "Выставил ордер: ", share['ticker'], price, direction)
203
except RequestError as err:
204
# попробовать поймать ошибку что нет торгов сейчас, например 30079. Все ошибки тут: https://tinkoff.github.io/investAPI/errors/
205
print(time.asctime(), "Ошибка при выставлении ордера: ", share['ticker'], price, direction, err.metadata.message)
206
if err.details == '30079':
207
return
208
209
def handle_order(client, order_id):
210
order = cursor.execute("SELECT id, share_id, price FROM `order` WHERE `order_id` = ?", (order_id, )).fetchone()
211
if not order:
212
print(time.asctime(), "not order: ", order)
213
return
214
id = order[0]
215
share_id = order[1]
216
price = order[2]
217
218
share = None
219
for tmp in shares.values():
220
if tmp['id'] == share_id:
221
share = tmp
222
break
223
if not share:
224
print("share not found")
225
return
226
figi = share['figi']
227
quantity = share['quantity']
228
229
order_state = client.orders.get_order_state(account_id=ACCID, order_id=order_id)
230
if not order_state:
231
print(time.asctime(), "not order_state: ", order)
232
return
233
print(time.asctime(), order_state.execution_report_status)
234
235
bad_statuses = [
236
OrderExecutionReportStatus.EXECUTION_REPORT_STATUS_UNSPECIFIED,
237
OrderExecutionReportStatus.EXECUTION_REPORT_STATUS_REJECTED,
238
OrderExecutionReportStatus.EXECUTION_REPORT_STATUS_CANCELLED
239
]
240
241
if (order_state.execution_report_status in bad_statuses):
242
print(time.asctime(), " ORDER ", order_state.execution_report_status, order_id, price)
243
cursor.execute("DELETE FROM `order` WHERE id = ?", (id,))
244
sqlite.commit()
245
return
246
247
if (order_state.execution_report_status == OrderExecutionReportStatus.EXECUTION_REPORT_STATUS_FILL):
248
print(time.asctime(), " ORDER FILLED ", order_id, price)
249
log(time.asctime(), " ORDER FILLED ", order_id, price, order_state.direction)
250
251
prices = get_prices(share)
252
253
direction = order_state.direction
254
price_index = prices.index(price)
255
256
if (direction == OrderDirection.ORDER_DIRECTION_SELL):
257
direction = OrderDirection.ORDER_DIRECTION_BUY
258
price_index -= 1
259
else:
260
direction = OrderDirection.ORDER_DIRECTION_SELL
261
price_index += 1
262
263
price = prices[price_index]
264
price_quotation = makequotation(price)
265
266
try:
267
order = client.orders.post_order(account_id=ACCID, figi=figi, quantity=quantity,
268
price=price_quotation, direction=direction,
269
order_type=OrderType.ORDER_TYPE_LIMIT,
270
order_id=str(time.time_ns()))
271
# print('post_order', ACCID, figi, quantity, price_quotation, direction)
272
# order = type('obj', (object,), {'order_id': str(time.time_ns())})
273
274
cursor.execute("UPDATE `order` SET order_id = ?, price = ?, direction = ? WHERE id = ?",
275
[order.order_id, price, direction, id])
276
sqlite.commit()
277
print(time.asctime(), " NEW ORDER ", order.order_id, direction, price)
278
except RequestError as err:
279
print(time.asctime(), " ERROR ", err.metadata.message)
280
281
def get_prices(share):
282
cursor.execute("SELECT price FROM `price` WHERE share_id = ? ORDER BY price", (share['id'], ))
283
return [row[0] for row in cursor.fetchall()]
284
285
if __name__ == "__main__":
286
sys.exit(main())
287