Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_tinkoff-invest-volume-analysis-robot-master/services/order_service.py
5932 views
1
import csv
2
import logging
3
import threading
4
from datetime import datetime
5
from itertools import groupby
6
from os.path import exists
7
from typing import List
8
9
from tinkoff.invest import Client, OrderType, OrderDirection
10
11
from constants import APP_NAME
12
from domains.order import Order
13
from services.telegram_service import TelegramService
14
from settings import NOTIFICATION, ACCOUNT_ID, TOKEN, IS_SANDBOX, CAN_REVERSE_ORDER
15
from utils.exchange_util import is_open_orders
16
from utils.format_util import fixed_float
17
from utils.instrument_util import get_instrument_by_name
18
from utils.order_util import is_order_already_open, get_reverse_order
19
20
logger = logging.getLogger(__name__)
21
22
orders_file_path = "./../data/orders.csv"
23
24
25
def write_file(order: Order):
26
try:
27
order_dict = dict(order)
28
with open(orders_file_path, "a", newline='') as file:
29
writer = csv.writer(file)
30
if file.tell() == 0:
31
writer.writerow(order_dict.keys())
32
writer.writerow(order_dict.values())
33
except Exception as ex:
34
logger.error(ex)
35
36
37
def rewrite_file(orders: List[Order]):
38
try:
39
with open(orders_file_path, "w", newline='') as file:
40
writer = csv.writer(file)
41
for order in orders:
42
order_dict = dict(order)
43
if file.tell() == 0:
44
writer.writerow(order_dict.keys())
45
writer.writerow(order_dict.values())
46
except Exception as ex:
47
logger.error(ex)
48
49
50
def load_orders():
51
orders: List[Order] = []
52
53
if not exists(orders_file_path):
54
return orders
55
56
try:
57
with open(orders_file_path, newline='') as file:
58
reader = csv.DictReader(file)
59
# header = next(reader)
60
for row in reader:
61
order = Order.from_dict(row)
62
if order.status == "open":
63
# после запуска приложения анализирую только незакрытые позиции
64
orders.append(order)
65
except Exception as ex:
66
logger.error(ex)
67
68
return orders
69
70
71
def open_order(
72
figi: str,
73
quantity: int,
74
direction: OrderDirection,
75
order_id: str,
76
order_type: OrderType = OrderType.ORDER_TYPE_MARKET
77
):
78
if not ACCOUNT_ID:
79
logger.error("Не задан счет для торговли. Проверьте общие настройки приложения")
80
81
with Client(TOKEN, app_name=APP_NAME) as client:
82
try:
83
# todo может возникнуть ситуация, когда будет создано 100 позиций с 1 лотом в каждой
84
# сервер не позволит выполнить моментально 100 запросов
85
if IS_SANDBOX:
86
close_order = client.sandbox.post_sandbox_order(
87
account_id=ACCOUNT_ID,
88
figi=figi,
89
quantity=quantity,
90
direction=direction,
91
order_type=order_type,
92
order_id=order_id
93
)
94
else:
95
close_order = client.orders.post_order(
96
account_id=ACCOUNT_ID,
97
figi=figi,
98
quantity=quantity,
99
direction=direction,
100
order_type=order_type,
101
order_id=order_id
102
)
103
logger.info(close_order)
104
return close_order
105
except Exception as ex:
106
logger.error(ex)
107
108
109
# в отдельном потоке, чтобы не замедлял процесс обработки
110
class OrderService(threading.Thread):
111
def __init__(self, is_notification=False, can_open_orders=False):
112
super().__init__()
113
114
self.telegram_service = TelegramService(NOTIFICATION["bot_token"], NOTIFICATION["chat_id"])
115
116
self.is_notification = is_notification
117
self.can_open_orders = can_open_orders
118
self.orders: List[Order] = load_orders()
119
120
def create_order(self, order: Order):
121
try:
122
if order is None:
123
return
124
125
if is_order_already_open(self.orders, order):
126
logger.info(f"сделка в направлении {order.direction} уже открыта: {order}")
127
return
128
129
if CAN_REVERSE_ORDER:
130
active_orders = get_reverse_order(self.orders, order)
131
if len(active_orders) > 0:
132
# если поступила сделка в обратном направлении, то переворачиваю позицию
133
logger.info(f"переворачиваю позицию - поступила сделка в обратном направлении: {order}")
134
for active_order in active_orders:
135
# цена закрытия предыдущей сделки = цене открытия новой
136
self.close_order(active_order, order.open)
137
138
instrument = get_instrument_by_name(order.instrument)
139
if self.can_open_orders:
140
new_order = open_order(
141
figi=instrument["future"],
142
quantity=order.quantity,
143
direction=order.direction,
144
order_id=order.id
145
)
146
order.order_id = new_order.order_id
147
148
self.orders.append(order)
149
write_file(order)
150
151
logger.info(f"✅ ТВ {order.instrument}: цена {order.open}, тейк {order.take}, стоп {order.stop}")
152
if self.is_notification:
153
self.telegram_service.post(
154
f"✅ ТВ {order.instrument}: цена {order.open}, тейк {order.take}, стоп {order.stop}")
155
except Exception as ex:
156
logger.error(ex)
157
158
def close_order(self, order: Order, close_price: float):
159
order.status = "close"
160
order.close = close_price
161
if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:
162
order.result = order.close - order.open
163
order.is_win = order.result > 0
164
else:
165
order.result = order.open - order.close
166
order.is_win = order.result > 0
167
168
if order.is_win:
169
logger.info(f"закрыта заявка по тейк-профиту с результатом {order.result}; открыта в {order.time}")
170
else:
171
logger.info(f"закрыта заявка по стоп-лоссу с результатом {order.result}; открыта в {order.time}")
172
173
if self.can_open_orders:
174
instrument = get_instrument_by_name(order.instrument)
175
# закрываю сделку обратным ордером
176
reverse_direction = OrderDirection.ORDER_DIRECTION_BUY.value
177
if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:
178
reverse_direction = OrderDirection.ORDER_DIRECTION_SELL.value
179
180
open_order(
181
figi=instrument["future"],
182
quantity=order.quantity,
183
direction=reverse_direction,
184
order_id=f"{order.id}-close"
185
)
186
187
# перезаписываю файл с результатами сделок
188
# todo перенести хранение сделок в БД
189
rewrite_file(self.orders)
190
if self.is_notification:
191
self.telegram_service.post(f"закрыта позиция на {order.instrument}: результат {order.result}")
192
193
def processed_orders(self, instrument: str, current_price: float, time: datetime):
194
for order in self.orders:
195
if order.status == "active":
196
if not is_open_orders(time):
197
# закрытие сделок по причине приближении закрытии биржи
198
self.close_order(order, current_price)
199
continue
200
201
if order.instrument != instrument:
202
continue
203
204
if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:
205
if current_price < order.stop:
206
# закрываю активные buy-заявки по стопу, если цена ниже стоп-лосса
207
self.close_order(order, current_price)
208
elif current_price > order.take:
209
# закрываю активные buy-заявки по цели, если цена выше заданной цели
210
self.close_order(order, current_price)
211
else:
212
if current_price > order.stop:
213
# закрываю активные sell-заявки по стопу, если цена выше стоп-лосса
214
self.close_order(order, current_price)
215
elif current_price < order.take:
216
# закрываю активные sell-заявки по цели, если цена ниже заданной цели
217
self.close_order(order, current_price)
218
219
def write_statistics(self):
220
groups = groupby(self.orders, lambda order: order.instrument)
221
for instrument, group in groups:
222
file_path = f"./../logs/statistics-{instrument}.log"
223
orders: List[Order] = list(group)
224
with open(file_path, "a", encoding="utf-8") as file:
225
take_orders = list(filter(lambda x: x.is_win, orders))
226
earned_points = sum(order.result for order in take_orders)
227
loss_orders = list(filter(lambda x: not x.is_win, orders))
228
lost_points = sum(order.result for order in loss_orders)
229
total = earned_points + lost_points
230
231
logger.info(f"инструмент: {instrument}")
232
logger.info(f"количество сделок: {len(orders)}")
233
logger.info(f"успешных сделок: {len(take_orders)}")
234
logger.info(f"заработано пунктов: {fixed_float(earned_points)}")
235
logger.info(f"отрицательных сделок: {len(loss_orders)}")
236
logger.info(f"потеряно пунктов: {fixed_float(lost_points)}")
237
logger.info(f"итого пунктов: {fixed_float(total)}")
238
logger.info("-------------------------------------")
239
240
file.write(f"количество сделок: {len(orders)}\n")
241
file.write(f"успешных сделок: {len(take_orders)}\n")
242
file.write(f"заработано пунктов: {fixed_float(earned_points)}\n")
243
file.write(f"отрицательных сделок: {len(loss_orders)}\n")
244
file.write(f"потеряно пунктов: {fixed_float(lost_points)}\n\n")
245
file.write(f"итого пунктов: {fixed_float(total)}\n")
246
file.write("-------------------------------------\n")
247
248