Path: blob/master/ invest-robot-contest_tinkoff-invest-volume-analysis-robot-master/services/order_service.py
5932 views
import csv1import logging2import threading3from datetime import datetime4from itertools import groupby5from os.path import exists6from typing import List78from tinkoff.invest import Client, OrderType, OrderDirection910from constants import APP_NAME11from domains.order import Order12from services.telegram_service import TelegramService13from settings import NOTIFICATION, ACCOUNT_ID, TOKEN, IS_SANDBOX, CAN_REVERSE_ORDER14from utils.exchange_util import is_open_orders15from utils.format_util import fixed_float16from utils.instrument_util import get_instrument_by_name17from utils.order_util import is_order_already_open, get_reverse_order1819logger = logging.getLogger(__name__)2021orders_file_path = "./../data/orders.csv"222324def write_file(order: Order):25try:26order_dict = dict(order)27with open(orders_file_path, "a", newline='') as file:28writer = csv.writer(file)29if file.tell() == 0:30writer.writerow(order_dict.keys())31writer.writerow(order_dict.values())32except Exception as ex:33logger.error(ex)343536def rewrite_file(orders: List[Order]):37try:38with open(orders_file_path, "w", newline='') as file:39writer = csv.writer(file)40for order in orders:41order_dict = dict(order)42if file.tell() == 0:43writer.writerow(order_dict.keys())44writer.writerow(order_dict.values())45except Exception as ex:46logger.error(ex)474849def load_orders():50orders: List[Order] = []5152if not exists(orders_file_path):53return orders5455try:56with open(orders_file_path, newline='') as file:57reader = csv.DictReader(file)58# header = next(reader)59for row in reader:60order = Order.from_dict(row)61if order.status == "open":62# после запуска приложения анализирую только незакрытые позиции63orders.append(order)64except Exception as ex:65logger.error(ex)6667return orders686970def open_order(71figi: str,72quantity: int,73direction: OrderDirection,74order_id: str,75order_type: OrderType = OrderType.ORDER_TYPE_MARKET76):77if not ACCOUNT_ID:78logger.error("Не задан счет для торговли. Проверьте общие настройки приложения")7980with Client(TOKEN, app_name=APP_NAME) as client:81try:82# todo может возникнуть ситуация, когда будет создано 100 позиций с 1 лотом в каждой83# сервер не позволит выполнить моментально 100 запросов84if IS_SANDBOX:85close_order = client.sandbox.post_sandbox_order(86account_id=ACCOUNT_ID,87figi=figi,88quantity=quantity,89direction=direction,90order_type=order_type,91order_id=order_id92)93else:94close_order = client.orders.post_order(95account_id=ACCOUNT_ID,96figi=figi,97quantity=quantity,98direction=direction,99order_type=order_type,100order_id=order_id101)102logger.info(close_order)103return close_order104except Exception as ex:105logger.error(ex)106107108# в отдельном потоке, чтобы не замедлял процесс обработки109class OrderService(threading.Thread):110def __init__(self, is_notification=False, can_open_orders=False):111super().__init__()112113self.telegram_service = TelegramService(NOTIFICATION["bot_token"], NOTIFICATION["chat_id"])114115self.is_notification = is_notification116self.can_open_orders = can_open_orders117self.orders: List[Order] = load_orders()118119def create_order(self, order: Order):120try:121if order is None:122return123124if is_order_already_open(self.orders, order):125logger.info(f"сделка в направлении {order.direction} уже открыта: {order}")126return127128if CAN_REVERSE_ORDER:129active_orders = get_reverse_order(self.orders, order)130if len(active_orders) > 0:131# если поступила сделка в обратном направлении, то переворачиваю позицию132logger.info(f"переворачиваю позицию - поступила сделка в обратном направлении: {order}")133for active_order in active_orders:134# цена закрытия предыдущей сделки = цене открытия новой135self.close_order(active_order, order.open)136137instrument = get_instrument_by_name(order.instrument)138if self.can_open_orders:139new_order = open_order(140figi=instrument["future"],141quantity=order.quantity,142direction=order.direction,143order_id=order.id144)145order.order_id = new_order.order_id146147self.orders.append(order)148write_file(order)149150logger.info(f"✅ ТВ {order.instrument}: цена {order.open}, тейк {order.take}, стоп {order.stop}")151if self.is_notification:152self.telegram_service.post(153f"✅ ТВ {order.instrument}: цена {order.open}, тейк {order.take}, стоп {order.stop}")154except Exception as ex:155logger.error(ex)156157def close_order(self, order: Order, close_price: float):158order.status = "close"159order.close = close_price160if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:161order.result = order.close - order.open162order.is_win = order.result > 0163else:164order.result = order.open - order.close165order.is_win = order.result > 0166167if order.is_win:168logger.info(f"закрыта заявка по тейк-профиту с результатом {order.result}; открыта в {order.time}")169else:170logger.info(f"закрыта заявка по стоп-лоссу с результатом {order.result}; открыта в {order.time}")171172if self.can_open_orders:173instrument = get_instrument_by_name(order.instrument)174# закрываю сделку обратным ордером175reverse_direction = OrderDirection.ORDER_DIRECTION_BUY.value176if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:177reverse_direction = OrderDirection.ORDER_DIRECTION_SELL.value178179open_order(180figi=instrument["future"],181quantity=order.quantity,182direction=reverse_direction,183order_id=f"{order.id}-close"184)185186# перезаписываю файл с результатами сделок187# todo перенести хранение сделок в БД188rewrite_file(self.orders)189if self.is_notification:190self.telegram_service.post(f"закрыта позиция на {order.instrument}: результат {order.result}")191192def processed_orders(self, instrument: str, current_price: float, time: datetime):193for order in self.orders:194if order.status == "active":195if not is_open_orders(time):196# закрытие сделок по причине приближении закрытии биржи197self.close_order(order, current_price)198continue199200if order.instrument != instrument:201continue202203if order.direction == OrderDirection.ORDER_DIRECTION_BUY.value:204if current_price < order.stop:205# закрываю активные buy-заявки по стопу, если цена ниже стоп-лосса206self.close_order(order, current_price)207elif current_price > order.take:208# закрываю активные buy-заявки по цели, если цена выше заданной цели209self.close_order(order, current_price)210else:211if current_price > order.stop:212# закрываю активные sell-заявки по стопу, если цена выше стоп-лосса213self.close_order(order, current_price)214elif current_price < order.take:215# закрываю активные sell-заявки по цели, если цена ниже заданной цели216self.close_order(order, current_price)217218def write_statistics(self):219groups = groupby(self.orders, lambda order: order.instrument)220for instrument, group in groups:221file_path = f"./../logs/statistics-{instrument}.log"222orders: List[Order] = list(group)223with open(file_path, "a", encoding="utf-8") as file:224take_orders = list(filter(lambda x: x.is_win, orders))225earned_points = sum(order.result for order in take_orders)226loss_orders = list(filter(lambda x: not x.is_win, orders))227lost_points = sum(order.result for order in loss_orders)228total = earned_points + lost_points229230logger.info(f"инструмент: {instrument}")231logger.info(f"количество сделок: {len(orders)}")232logger.info(f"успешных сделок: {len(take_orders)}")233logger.info(f"заработано пунктов: {fixed_float(earned_points)}")234logger.info(f"отрицательных сделок: {len(loss_orders)}")235logger.info(f"потеряно пунктов: {fixed_float(lost_points)}")236logger.info(f"итого пунктов: {fixed_float(total)}")237logger.info("-------------------------------------")238239file.write(f"количество сделок: {len(orders)}\n")240file.write(f"успешных сделок: {len(take_orders)}\n")241file.write(f"заработано пунктов: {fixed_float(earned_points)}\n")242file.write(f"отрицательных сделок: {len(loss_orders)}\n")243file.write(f"потеряно пунктов: {fixed_float(lost_points)}\n\n")244file.write(f"итого пунктов: {fixed_float(total)}\n")245file.write("-------------------------------------\n")246247248