Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_tinkoff-contest-python-main/src/service/trader.py
5935 views
1
import asyncio
2
import datetime
3
import uuid
4
5
import tinkoff.invest
6
7
from src import settings
8
from src.containers.config import TraderConfig
9
from src.containers.market import (
10
AccountBalance,
11
CancelOrder,
12
CreateOrder,
13
MarketData,
14
MarketState,
15
)
16
from src.service.errors import DecisionExecutionError
17
from src.traders.base import BaseTrader
18
19
20
class TraderRunner:
21
@classmethod
22
async def start_trader_loop(cls, trader: BaseTrader) -> None:
23
"""Start the trade loop with the given trader."""
24
print("The trader has been started")
25
while True:
26
# fetch data from the market
27
market_data = await cls._fetch_current_market_state(trader.trader_config)
28
29
# make decisions in accordance with the strategy
30
decisions = await trader.make_decisions(market_data)
31
32
# execute decisions, if any
33
await cls._execute_trader_decisions(decisions, trader.trader_config)
34
35
# wait for the next step
36
await asyncio.sleep(trader.trader_config.config["decision_interval_s"])
37
38
@classmethod
39
async def _fetch_current_market_state(cls, trader_config: TraderConfig) -> MarketState:
40
async with tinkoff.invest.AsyncClient(
41
settings.INVEST_TOKEN, sandbox_token=settings.SANDBOX_TOKEN, app_name=settings.APP_NAME
42
) as services:
43
# orders book
44
order_book = await services.market_data.get_order_book(
45
figi=trader_config.instrument_figi, depth=trader_config.config["order_book_depth"]
46
)
47
48
# candles for required period
49
now = datetime.datetime.utcnow()
50
candles = (
51
await services.market_data.get_candles(
52
figi=trader_config.instrument_figi,
53
from_=now - trader_config.candle_timedelta,
54
to=now,
55
interval=trader_config.candle_interval,
56
)
57
).candles
58
59
# available balance for the account
60
positions = await services.operations.get_positions(account_id=trader_config.account_id)
61
62
# orders
63
orders = (await services.orders.get_orders(account_id=trader_config.account_id)).orders
64
65
return MarketState(
66
account_balance=AccountBalance(
67
money=positions.money,
68
securities=positions.securities,
69
),
70
market_data=MarketData(
71
bids=order_book.bids,
72
asks=order_book.asks,
73
candles=candles,
74
),
75
opened_orders=orders,
76
)
77
78
@classmethod
79
async def _execute_trader_decisions(cls, decisions, trader_config):
80
async with tinkoff.invest.AsyncClient(
81
settings.INVEST_TOKEN, sandbox_token=settings.SANDBOX_TOKEN, app_name=settings.APP_NAME
82
) as services:
83
for decision in decisions:
84
# execute decision
85
response = None
86
try:
87
response = await cls._execute_decision(services, trader_config, decision)
88
except DecisionExecutionError:
89
print("error executing decision")
90
91
# log the decision and its execution result
92
await cls._log_algorithm_decision(trader_config, decision, response)
93
94
@classmethod
95
async def _execute_decision(cls, client, trader_config, decision):
96
if isinstance(decision, CreateOrder):
97
try:
98
return await client.orders.post_order(
99
order_id=str(uuid.uuid4()),
100
figi=trader_config.instrument_figi,
101
account_id=trader_config.account_id,
102
# decision
103
order_type=decision.order_type,
104
direction=decision.order_direction,
105
price=decision.price,
106
quantity=decision.quantity,
107
)
108
except Exception as exc:
109
print("unable to post the order", str(exc))
110
elif isinstance(decision, CancelOrder):
111
try:
112
return await client.orders.cancel_order(account_id=trader_config.account_id, order_id=decision.order_id)
113
except Exception as exc:
114
print("unable to cancel the order", str(exc))
115
else:
116
print("Unsupported decision type", type(decision))
117
118
@classmethod
119
async def _log_algorithm_decision(cls, trader_config, decision, response):
120
with open(f"logs/{trader_config.account_id}.log", "a") as f:
121
f.write(str(decision) + str(response))
122
123