Path: blob/master/ invest-robot-contest_TradingCompetition2022-main/account/Account.py
5931 views
from tinkoff.invest.services import (Services, OrderDirection, OrderType, MoneyValue)1from tinkoff.invest.schemas import (AccountStatus, AccessLevel)2from tinkoff.invest.exceptions import RequestError34from order.Orders import Order5from order.OrderStorage import OrderStorage6from stock.Stock import Stock7import decimal8from decimal import Decimal9from logger.LoggerFactory import tech_log, LoggerFactory10from logger.BusinessLogger import BusinessLogger11import datetime121314class Account:15"""16Client account. Implemented all manipulation with client (order, close, etc)17"""1819# Order types20LONG = "long" # Long order21SHORT = "short" # Short order22CLOSE_LONG = "close_long" # Close long order (sell stock)23CLOSE_SHORT = "close_short" # Close short order (bay stock)2425def __init__(self, client, account_id=None):26"""27:param client: TF service API object28:param account_id: TF account ID29"""30self._client: Services = client31self._order_storage: OrderStorage = OrderStorage.get_order_storage()32self._daily_limit: Decimal = Decimal()33self._daily_drop_limit: Decimal = Decimal()34self._account_balance: Decimal = Decimal()3536# account id from input constructor input or from used account (TF)37self._account_id, self._account_name = account_id if account_id else self.get_user_account_id()38self.id = self._account_id3940self._orders = dict()4142@property43def account_balance(self):44try:45total_amount_currencies = self.client.operations.get_portfolio(account_id=self.account_id). \46total_amount_currencies47self._account_balance = Decimal('.'.join((str(total_amount_currencies.units),48str(total_amount_currencies.nano))))49return self._account_balance50except RequestError:51print("error to get account balance")52pass # ToDo add log53return None5455@property56def client(self) -> Services:57return self._client5859@property60def account_id(self):61return self._account_id6263def get_user_account_id(self):64""" Get first correct available user account id """65try:66for i in self._client.users.get_accounts().accounts:67if i.status == AccountStatus.ACCOUNT_STATUS_OPEN and \68i.access_level == AccessLevel.ACCOUNT_ACCESS_LEVEL_FULL_ACCESS:69return i.id, i.name70except RequestError as error:71logger = LoggerFactory().get_tech_logger_instance()72logger.add_except(error)7374def __generate_order_id(self, ticker):75""" Generate unique order ID"""76return ticker + str(len(self._order_storage.orders[ticker]) + 1) + str(datetime.datetime.now())7778def set_daily_limit(self, daily_limit: Decimal):79self._daily_limit = daily_limit80return self8182@property83def daily_limit(self):84return self._daily_limit8586def set_daily_drop_limit(self, daily_drop_limit):87self._daily_drop_limit = daily_drop_limit88return self8990@tech_log91def set_order(self, order_type, stock, quantity):92""" Set order in market93"""94try:95match order_type:96case self.LONG: # Set Open long order97post_order = self._open_long_order(stock, quantity)98if post_order:99# Add Order to storage100self._order_storage.add_order(post_order.order_id,101post_order.lots_requested, stock,102Order.TYPE_LONG_OPEN)103# Inform model about opened order104stock.model.long_open_ordered()105106case self.CLOSE_LONG: # Set Close long order107post_order = self._close_long_order(stock, quantity)108if post_order:109# Add Order to storage110self._order_storage.add_order(post_order.order_id,111post_order.lots_requested, stock,112Order.TYPE_LONG_CLOSE)113# Inform model about opened order114stock.model.long_close_ordered()115116case self.SHORT:117# ToDo118# self._open_short_order()119pass120case self.CLOSE_SHORT:121# ToDo122# self._close_long_order()123pass124except RequestError as error:125# Log tech error126LoggerFactory.get_tech_logger_instance().add_except(error)127128def _post_order(self, stock, quantity, direction):129""" Post order into TF platform """130post_order = self._client.orders.post_order(figi=stock.figi, quantity=quantity, price=None,131direction=direction, account_id=self._account_id,132order_type=OrderType.ORDER_TYPE_MARKET,133order_id=self.__generate_order_id(ticker=stock.ticker))134# Log data135LoggerFactory.get_business_logger_instance().add_event(event_type=BusinessLogger.ORDER_POSTED,136obj=stock, value=post_order)137138return post_order139140def _open_long_order(self, stock: Stock, quantity):141"""142Set Open long order143:param quantity: count of lot144"""145return self._post_order(stock=stock, quantity=quantity, direction=OrderDirection.ORDER_DIRECTION_BUY)146147def _close_long_order(self, stock: Stock, quantity):148""" Set Close Long order149:param quantity: count of lot150"""151return self._post_order(stock=stock, quantity=quantity, direction=OrderDirection.ORDER_DIRECTION_SELL)152153def get_order_from_platform(self, order):154""" Read detail of order from TF platform """155try:156order_state = self._client.orders.get_order_state(account_id=self._account_id, order_id=order.order_id)157except RequestError as error:158# Log tech error159LoggerFactory.get_tech_logger_instance().add_except(error)160return None161162return order_state163164def do_upd_order_result(self, stock: Stock):165"""166Get and update result of order (only for non-completed)167"""168# ToDo TF has API for select all orders (not one by one) It should be changed (optimize issue)169# ToDo this method (set_available_lot) CAN'T be use for multi_orders.170# ToDo It should be changes (it could be + and - operation)171# ToDo it's work only when all order done.It'll be better to change, it have to increase and decrease every time172for order in self._order_storage.orders[stock.ticker]:173if order.order_type == Order.TYPE_LONG_OPEN and stock.model.is_long_open_done is True:174continue175if order.order_type == Order.TYPE_LONG_CLOSE and stock.model.is_long_close_done is True:176continue177order_state = self.get_order_from_platform(order)178if order_state is None:179continue180if order_state.lots_executed == order_state.lots_requested:181if order.order_type == Order.TYPE_LONG_OPEN:182stock.set_available_lot(order_state.lots_executed)183stock.model.set_average_position_price(184avg_price=Account.convert(order_state.average_position_price))185stock.model.is_long_open_done = True186# Log data187LoggerFactory.get_business_logger_instance().add_event(BusinessLogger.ORDER_OPEN_LONG_DONE,188stock, order_state)189190elif order.order_type == Order.TYPE_LONG_CLOSE:191stock.set_available_lot(stock.lot_available - order.lots_executed)192stock.model.is_long_close_done = True193# Log data194LoggerFactory.get_business_logger_instance().add_event(BusinessLogger.ORDER_CLOSE_LONG_DONE,195stock, order_state)196197def is_ready_to_buy(self, stock: Stock) -> bool:198"""199Check can we buy stock in this account200:param stock: Stock which we want to buy201"""202account_balance = self.account_balance203need_money = stock.model.possible_lot * (stock.model.count_in_lot * stock.model.last_price)204if account_balance:205if account_balance > need_money:206return True207else:208# not enough money on balance209# Log data210LoggerFactory.get_business_logger_instance().add_event_no_money(obj=stock, balance=account_balance,211need_money=need_money)212213return False214215@staticmethod216def is_ready_to_sell(stock: Stock) -> bool:217"""218Check can we sell stock in this account219:param stock: Stock which we want to sell220"""221if stock.lot_available == 0:222return False223# ToDo This method check only our session stock but it is better to read available stock from TF platform224return True225226@classmethod227def convert(cls, price: MoneyValue):228""" Convert money to dec """229return Decimal(price.units + price.nano / 10 ** 9)230231@classmethod232def convert_out(cls, price):233""" Convert money to dec """234return price.quantize(Decimal("1.00"), decimal.ROUND_CEILING)235236def get_operations(self, from_, to):237return self.client.operations.get_operations(account_id=self.account_id,238from_=from_,239to=to)240241242