Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_TradingCompetition2022-main/account/Account.py
5931 views
1
from tinkoff.invest.services import (Services, OrderDirection, OrderType, MoneyValue)
2
from tinkoff.invest.schemas import (AccountStatus, AccessLevel)
3
from tinkoff.invest.exceptions import RequestError
4
5
from order.Orders import Order
6
from order.OrderStorage import OrderStorage
7
from stock.Stock import Stock
8
import decimal
9
from decimal import Decimal
10
from logger.LoggerFactory import tech_log, LoggerFactory
11
from logger.BusinessLogger import BusinessLogger
12
import datetime
13
14
15
class Account:
16
"""
17
Client account. Implemented all manipulation with client (order, close, etc)
18
"""
19
20
# Order types
21
LONG = "long" # Long order
22
SHORT = "short" # Short order
23
CLOSE_LONG = "close_long" # Close long order (sell stock)
24
CLOSE_SHORT = "close_short" # Close short order (bay stock)
25
26
def __init__(self, client, account_id=None):
27
"""
28
:param client: TF service API object
29
:param account_id: TF account ID
30
"""
31
self._client: Services = client
32
self._order_storage: OrderStorage = OrderStorage.get_order_storage()
33
self._daily_limit: Decimal = Decimal()
34
self._daily_drop_limit: Decimal = Decimal()
35
self._account_balance: Decimal = Decimal()
36
37
# account id from input constructor input or from used account (TF)
38
self._account_id, self._account_name = account_id if account_id else self.get_user_account_id()
39
self.id = self._account_id
40
41
self._orders = dict()
42
43
@property
44
def account_balance(self):
45
try:
46
total_amount_currencies = self.client.operations.get_portfolio(account_id=self.account_id). \
47
total_amount_currencies
48
self._account_balance = Decimal('.'.join((str(total_amount_currencies.units),
49
str(total_amount_currencies.nano))))
50
return self._account_balance
51
except RequestError:
52
print("error to get account balance")
53
pass # ToDo add log
54
return None
55
56
@property
57
def client(self) -> Services:
58
return self._client
59
60
@property
61
def account_id(self):
62
return self._account_id
63
64
def get_user_account_id(self):
65
""" Get first correct available user account id """
66
try:
67
for i in self._client.users.get_accounts().accounts:
68
if i.status == AccountStatus.ACCOUNT_STATUS_OPEN and \
69
i.access_level == AccessLevel.ACCOUNT_ACCESS_LEVEL_FULL_ACCESS:
70
return i.id, i.name
71
except RequestError as error:
72
logger = LoggerFactory().get_tech_logger_instance()
73
logger.add_except(error)
74
75
def __generate_order_id(self, ticker):
76
""" Generate unique order ID"""
77
return ticker + str(len(self._order_storage.orders[ticker]) + 1) + str(datetime.datetime.now())
78
79
def set_daily_limit(self, daily_limit: Decimal):
80
self._daily_limit = daily_limit
81
return self
82
83
@property
84
def daily_limit(self):
85
return self._daily_limit
86
87
def set_daily_drop_limit(self, daily_drop_limit):
88
self._daily_drop_limit = daily_drop_limit
89
return self
90
91
@tech_log
92
def set_order(self, order_type, stock, quantity):
93
""" Set order in market
94
"""
95
try:
96
match order_type:
97
case self.LONG: # Set Open long order
98
post_order = self._open_long_order(stock, quantity)
99
if post_order:
100
# Add Order to storage
101
self._order_storage.add_order(post_order.order_id,
102
post_order.lots_requested, stock,
103
Order.TYPE_LONG_OPEN)
104
# Inform model about opened order
105
stock.model.long_open_ordered()
106
107
case self.CLOSE_LONG: # Set Close long order
108
post_order = self._close_long_order(stock, quantity)
109
if post_order:
110
# Add Order to storage
111
self._order_storage.add_order(post_order.order_id,
112
post_order.lots_requested, stock,
113
Order.TYPE_LONG_CLOSE)
114
# Inform model about opened order
115
stock.model.long_close_ordered()
116
117
case self.SHORT:
118
# ToDo
119
# self._open_short_order()
120
pass
121
case self.CLOSE_SHORT:
122
# ToDo
123
# self._close_long_order()
124
pass
125
except RequestError as error:
126
# Log tech error
127
LoggerFactory.get_tech_logger_instance().add_except(error)
128
129
def _post_order(self, stock, quantity, direction):
130
""" Post order into TF platform """
131
post_order = self._client.orders.post_order(figi=stock.figi, quantity=quantity, price=None,
132
direction=direction, account_id=self._account_id,
133
order_type=OrderType.ORDER_TYPE_MARKET,
134
order_id=self.__generate_order_id(ticker=stock.ticker))
135
# Log data
136
LoggerFactory.get_business_logger_instance().add_event(event_type=BusinessLogger.ORDER_POSTED,
137
obj=stock, value=post_order)
138
139
return post_order
140
141
def _open_long_order(self, stock: Stock, quantity):
142
"""
143
Set Open long order
144
:param quantity: count of lot
145
"""
146
return self._post_order(stock=stock, quantity=quantity, direction=OrderDirection.ORDER_DIRECTION_BUY)
147
148
def _close_long_order(self, stock: Stock, quantity):
149
""" Set Close Long order
150
:param quantity: count of lot
151
"""
152
return self._post_order(stock=stock, quantity=quantity, direction=OrderDirection.ORDER_DIRECTION_SELL)
153
154
def get_order_from_platform(self, order):
155
""" Read detail of order from TF platform """
156
try:
157
order_state = self._client.orders.get_order_state(account_id=self._account_id, order_id=order.order_id)
158
except RequestError as error:
159
# Log tech error
160
LoggerFactory.get_tech_logger_instance().add_except(error)
161
return None
162
163
return order_state
164
165
def do_upd_order_result(self, stock: Stock):
166
"""
167
Get and update result of order (only for non-completed)
168
"""
169
# ToDo TF has API for select all orders (not one by one) It should be changed (optimize issue)
170
# ToDo this method (set_available_lot) CAN'T be use for multi_orders.
171
# ToDo It should be changes (it could be + and - operation)
172
# ToDo it's work only when all order done.It'll be better to change, it have to increase and decrease every time
173
for order in self._order_storage.orders[stock.ticker]:
174
if order.order_type == Order.TYPE_LONG_OPEN and stock.model.is_long_open_done is True:
175
continue
176
if order.order_type == Order.TYPE_LONG_CLOSE and stock.model.is_long_close_done is True:
177
continue
178
order_state = self.get_order_from_platform(order)
179
if order_state is None:
180
continue
181
if order_state.lots_executed == order_state.lots_requested:
182
if order.order_type == Order.TYPE_LONG_OPEN:
183
stock.set_available_lot(order_state.lots_executed)
184
stock.model.set_average_position_price(
185
avg_price=Account.convert(order_state.average_position_price))
186
stock.model.is_long_open_done = True
187
# Log data
188
LoggerFactory.get_business_logger_instance().add_event(BusinessLogger.ORDER_OPEN_LONG_DONE,
189
stock, order_state)
190
191
elif order.order_type == Order.TYPE_LONG_CLOSE:
192
stock.set_available_lot(stock.lot_available - order.lots_executed)
193
stock.model.is_long_close_done = True
194
# Log data
195
LoggerFactory.get_business_logger_instance().add_event(BusinessLogger.ORDER_CLOSE_LONG_DONE,
196
stock, order_state)
197
198
def is_ready_to_buy(self, stock: Stock) -> bool:
199
"""
200
Check can we buy stock in this account
201
:param stock: Stock which we want to buy
202
"""
203
account_balance = self.account_balance
204
need_money = stock.model.possible_lot * (stock.model.count_in_lot * stock.model.last_price)
205
if account_balance:
206
if account_balance > need_money:
207
return True
208
else:
209
# not enough money on balance
210
# Log data
211
LoggerFactory.get_business_logger_instance().add_event_no_money(obj=stock, balance=account_balance,
212
need_money=need_money)
213
214
return False
215
216
@staticmethod
217
def is_ready_to_sell(stock: Stock) -> bool:
218
"""
219
Check can we sell stock in this account
220
:param stock: Stock which we want to sell
221
"""
222
if stock.lot_available == 0:
223
return False
224
# ToDo This method check only our session stock but it is better to read available stock from TF platform
225
return True
226
227
@classmethod
228
def convert(cls, price: MoneyValue):
229
""" Convert money to dec """
230
return Decimal(price.units + price.nano / 10 ** 9)
231
232
@classmethod
233
def convert_out(cls, price):
234
""" Convert money to dec """
235
return price.quantize(Decimal("1.00"), decimal.ROUND_CEILING)
236
237
def get_operations(self, from_, to):
238
return self.client.operations.get_operations(account_id=self.account_id,
239
from_=from_,
240
to=to)
241
242