RAPTOR v18.4: Исправлена отчетность, активированы выходные

This commit is contained in:
root
2026-04-18 23:26:45 +03:00
commit ef0958239e
312 changed files with 54247 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Type, cast
import mplfinance as mpf
import numpy as np
import pandas as pd
from t_tech.invest.strategies.base.event import DataEvent, SignalEvent, StrategyEvent
from t_tech.invest.strategies.base.signal import (
CloseLongMarketOrder,
CloseShortMarketOrder,
OpenLongMarketOrder,
OpenShortMarketOrder,
OrderSignal,
Signal,
SignalDirection,
)
from t_tech.invest.strategies.moving_average.strategy_settings import (
MovingAverageStrategySettings,
)
from t_tech.invest.strategies.plotting.plotter import PlotKwargs, StrategyPlotter
logger = logging.getLogger(__name__)
class MovingAverageStrategyPlotter(StrategyPlotter):
def __init__(self, settings: MovingAverageStrategySettings):
self._was_not_executed_color = "grey"
self._settings = settings
self._signal_type_to_style_map: Dict[Type[Signal], Dict[str, Any]] = {
OpenLongMarketOrder: {
"type": "scatter",
"markersize": 50,
"marker": "^",
"color": "green",
},
CloseLongMarketOrder: {
"type": "scatter",
"markersize": 50,
"marker": "^",
"color": "black",
},
OpenShortMarketOrder: {
"type": "scatter",
"markersize": 50,
"marker": "v",
"color": "red",
},
CloseShortMarketOrder: {
"type": "scatter",
"markersize": 50,
"marker": "v",
"color": "black",
},
}
self._signal_type_to_candle_point_map = {
SignalDirection.LONG: lambda candle: candle.low,
SignalDirection.SHORT: lambda candle: candle.high,
}
def _filter_data_events(
self, strategy_events: List[StrategyEvent]
) -> List[DataEvent]:
return cast(
List[DataEvent],
list(filter(lambda e: isinstance(e, DataEvent), strategy_events)),
)
def _filter_signal_events(
self, strategy_events: List[StrategyEvent]
) -> List[SignalEvent]:
return cast(
List[SignalEvent],
list(filter(lambda e: isinstance(e, SignalEvent), strategy_events)),
)
def _get_interval_count_between_dates(
self, start: datetime, end: datetime, interval_delta: timedelta
) -> float:
return (end - start) / interval_delta
def get_candle_plot_kwargs(
self, strategy_events: List[StrategyEvent]
) -> PlotKwargs:
data_events = self._filter_data_events(strategy_events)
quotes = {
"open": [float(e.candle_event.candle.open) for e in data_events],
"close": [float(e.candle_event.candle.close) for e in data_events],
"high": [float(e.candle_event.candle.high) for e in data_events],
"low": [float(e.candle_event.candle.low) for e in data_events],
"volume": [float(e.candle_event.volume) for e in data_events],
"time": [e.candle_event.time for e in data_events],
}
df = pd.DataFrame(quotes, index=quotes["time"])
interval_count = self._get_interval_count_between_dates(
start=df["time"].idxmin(),
end=df["time"].idxmax(),
interval_delta=self._settings.candle_interval_timedelta,
)
non_trading_coefficient = len(data_events) / interval_count
mav = {
"ma_short": int(
self._settings.short_period
/ self._settings.candle_interval_timedelta
* non_trading_coefficient
),
"ma_long": int(
self._settings.long_period
/ self._settings.candle_interval_timedelta
* non_trading_coefficient
),
}
style = mpf.make_mpf_style(
base_mpf_style="charles", mavcolors=["#1f77b4", "#ff7f0e", "#2ca02c"]
)
return cast(
PlotKwargs,
{
"data": df,
"type": "candle",
"volume": True,
"mav": tuple(mav.values()),
"style": style,
"returnfig": True,
},
)
def _get_plot_for_signal_type(
self,
signal_type: Type[Signal],
signal_event_types_to_event_index: Dict[Type[Signal], Dict[int, SignalEvent]],
data_events: List[DataEvent],
was_executed_flag: bool,
) -> Optional[PlotKwargs]:
style = self._signal_type_to_style_map[signal_type]
price = [np.nan] * len(data_events)
color = style["color"]
has_signal = False
for index, signal_event in signal_event_types_to_event_index[
signal_type
].items():
if was_executed_flag == signal_event.was_executed:
has_signal = True
candle = data_events[index].candle_event.candle
signal = cast(OrderSignal, signal_event.signal)
price[index] = self._signal_type_to_candle_point_map[signal.direction](
candle
)
if not signal_event.was_executed:
color = self._was_not_executed_color
if not has_signal:
return None
style.update({"color": color})
params = {
"price": price,
"time": [e.candle_event.time for e in data_events],
}
df = pd.DataFrame(params, index=params["time"])
return cast(PlotKwargs, dict(data=df["price"], **style))
def get_signal_plot_kwargs(
self, strategy_events: List[StrategyEvent]
) -> List[PlotKwargs]:
signal_events = self._filter_signal_events(strategy_events)
data_events = self._filter_data_events(strategy_events)
data_events.sort(key=lambda e: e.time)
first_data_event, last_data_event = data_events[0], data_events[-1]
data_events_timedelta = last_data_event.time - first_data_event.time
signal_event_types_to_event_index: Dict[
Type[Signal], Dict[int, SignalEvent]
] = {}
for signal_event in signal_events:
signal_type = type(signal_event.signal)
event_index = int(
((signal_event.time - first_data_event.time) / data_events_timedelta)
* len(data_events)
)
event_index = min(event_index, len(data_events) - 1)
if signal_type not in signal_event_types_to_event_index:
signal_event_types_to_event_index[signal_type] = {}
signal_event_types_to_event_index[signal_type][event_index] = signal_event
plots = []
for was_executed_flag in [False, True]:
for signal_type in signal_event_types_to_event_index:
kwargs = self._get_plot_for_signal_type(
signal_type=signal_type,
signal_event_types_to_event_index=signal_event_types_to_event_index,
data_events=data_events,
was_executed_flag=was_executed_flag,
)
plots.append(kwargs)
return cast(List[PlotKwargs], list(filter(lambda p: p is not None, plots)))

View File

@@ -0,0 +1,65 @@
import logging
from functools import singledispatchmethod
from t_tech.invest.services import Services
from t_tech.invest.strategies.base.errors import UnknownSignal
from t_tech.invest.strategies.base.signal import (
CloseLongMarketOrder,
CloseShortMarketOrder,
OpenLongMarketOrder,
OpenShortMarketOrder,
Signal,
)
from t_tech.invest.strategies.base.signal_executor_base import SignalExecutor
from t_tech.invest.strategies.moving_average.strategy_settings import (
MovingAverageStrategySettings,
)
from t_tech.invest.strategies.moving_average.strategy_state import (
MovingAverageStrategyState,
)
logger = logging.getLogger(__name__)
class MovingAverageSignalExecutor(SignalExecutor):
def __init__(
self,
services: Services,
state: MovingAverageStrategyState,
settings: MovingAverageStrategySettings,
):
super().__init__(services, settings)
self._services = services
self._state = state
@singledispatchmethod
def execute(self, signal: Signal) -> None:
raise UnknownSignal()
@execute.register
def _execute_open_long_market_order(self, signal: OpenLongMarketOrder) -> None:
self.execute_open_long_market_order(signal)
self._state.long_open = True
self._state.position = signal.lots
logger.info("Signal executed %s", signal)
@execute.register
def _execute_close_long_market_order(self, signal: CloseLongMarketOrder) -> None:
self.execute_close_long_market_order(signal)
self._state.long_open = False
self._state.position = 0
logger.info("Signal executed %s", signal)
@execute.register
def _execute_open_short_market_order(self, signal: OpenShortMarketOrder) -> None:
self.execute_open_short_market_order(signal)
self._state.short_open = True
self._state.position = signal.lots
logger.info("Signal executed %s", signal)
@execute.register
def _execute_close_short_market_order(self, signal: CloseShortMarketOrder) -> None:
self.execute_close_short_market_order(signal)
self._state.short_open = False
self._state.position = 0
logger.info("Signal executed %s", signal)

View File

@@ -0,0 +1,305 @@
import logging
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Callable, Iterable, List
import numpy as np
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.base.errors import (
CandleEventForDateNotFound,
NotEnoughData,
OldCandleObservingError,
)
from t_tech.invest.strategies.base.models import CandleEvent
from t_tech.invest.strategies.base.signal import (
CloseLongMarketOrder,
CloseShortMarketOrder,
OpenLongMarketOrder,
OpenShortMarketOrder,
Signal,
)
from t_tech.invest.strategies.base.strategy_interface import InvestStrategy
from t_tech.invest.strategies.moving_average.strategy_settings import (
MovingAverageStrategySettings,
)
from t_tech.invest.strategies.moving_average.strategy_state import (
MovingAverageStrategyState,
)
from t_tech.invest.utils import (
candle_interval_to_timedelta,
ceil_datetime,
floor_datetime,
now,
)
logger = logging.getLogger(__name__)
class MovingAverageStrategy(InvestStrategy):
def __init__(
self,
settings: MovingAverageStrategySettings,
account_manager: AccountManager,
state: MovingAverageStrategyState,
):
self._data: List[CandleEvent] = []
self._settings = settings
self._account_manager = account_manager
self._state = state
self._MA_LONG_START: Decimal
self._candle_interval_timedelta = candle_interval_to_timedelta(
self._settings.candle_interval
)
def _ensure_enough_candles(self) -> None:
candles_needed = (
self._settings.short_period + self._settings.long_period
) / self._settings.candle_interval_timedelta
if candles_needed > len(self._data):
raise NotEnoughData(
f"Got {len(self._data)} candles but needed {candles_needed}"
)
logger.info("Got enough data for strategy")
def fit(self, candles: Iterable[CandleEvent]) -> None:
logger.debug("Strategy fitting with candles %s", candles)
for candle in candles:
self.observe(candle)
self._ensure_enough_candles()
def _append_candle_event(self, candle_event: CandleEvent) -> None:
last_candle_event = self._data[-1]
last_interval_floor = floor_datetime(
last_candle_event.time, self._candle_interval_timedelta
)
last_interval_ceil = ceil_datetime(
last_candle_event.time, self._candle_interval_timedelta
)
if candle_event.time < last_interval_floor:
raise OldCandleObservingError()
if (
candle_event.time < last_interval_ceil
or candle_event.time == last_interval_floor
):
self._data[-1] = candle_event
else:
self._data.append(candle_event)
def observe(self, candle: CandleEvent) -> None:
logger.debug("Observing candle event: %s", candle)
if len(self._data) > 0:
self._append_candle_event(candle)
else:
self._data.append(candle)
@staticmethod
def _get_newer_than_datetime_predicate(
anchor: datetime,
) -> Callable[[CandleEvent], bool]:
def _(event: CandleEvent) -> bool:
return event.time > anchor
return _
def _filter_from_the_end_with_early_stop(
self, predicate: Callable[[CandleEvent], bool]
) -> Iterable[CandleEvent]:
for event in reversed(self._data):
if not predicate(event):
break
yield event
def _select_for_period(self, period: timedelta):
predicate = self._get_newer_than_datetime_predicate(now() - period)
return self._filter_from_the_end_with_early_stop(predicate)
@staticmethod
def _get_prices(events: Iterable[CandleEvent]) -> Iterable[Decimal]:
for event in events:
yield event.candle.close
def _calculate_moving_average(self, period: timedelta) -> Decimal:
prices = list(self._get_prices(self._select_for_period(period)))
logger.debug("Selected prices: %s", prices)
return np.mean(prices, axis=0) # type: ignore
def _calculate_std(self, period: timedelta) -> Decimal:
prices = list(self._get_prices(self._select_for_period(period)))
return np.std(prices, axis=0) # type: ignore
def _get_first_candle_before(self, date: datetime) -> CandleEvent:
predicate = self._get_newer_than_datetime_predicate(date)
for event in reversed(self._data):
if not predicate(event):
return event
raise CandleEventForDateNotFound()
def _init_MA_LONG_START(self):
date = now() - self._settings.short_period
event = self._get_first_candle_before(date)
self._MA_LONG_START = event.candle.close
@staticmethod
def _is_long_open_signal(
MA_SHORT: Decimal,
MA_LONG: Decimal,
PRICE: Decimal,
STD: Decimal,
MA_LONG_START: Decimal,
) -> bool:
logger.debug("Try long opening")
logger.debug("\tMA_SHORT > MA_LONG, %s", MA_SHORT > MA_LONG)
logger.debug(
"\tand abs((PRICE - MA_LONG) / MA_LONG) < STD, %s",
abs((PRICE - MA_LONG) / MA_LONG) < STD,
)
logger.debug("\tand MA_LONG < MA_LONG_START, %s", MA_LONG > MA_LONG_START)
logger.debug(
"== %s",
MA_SHORT > MA_LONG > MA_LONG_START
and abs((PRICE - MA_LONG) / MA_LONG) < STD,
)
return (
MA_SHORT > MA_LONG > MA_LONG_START
and abs((PRICE - MA_LONG) / MA_LONG) < STD
)
@staticmethod
def _is_short_open_signal(
MA_SHORT: Decimal,
MA_LONG: Decimal,
PRICE: Decimal,
STD: Decimal,
MA_LONG_START: Decimal,
) -> bool:
logger.debug("Try short opening")
logger.debug("\tMA_SHORT < MA_LONG, %s", MA_SHORT < MA_LONG)
logger.debug(
"\tand abs((PRICE - MA_LONG) / MA_LONG) < STD, %s",
abs((PRICE - MA_LONG) / MA_LONG) < STD,
)
logger.debug("\tand MA_LONG > MA_LONG_START, %s", MA_LONG < MA_LONG_START)
logger.debug(
"== %s",
MA_SHORT < MA_LONG < MA_LONG_START
and abs((PRICE - MA_LONG) / MA_LONG) < STD,
)
return (
MA_SHORT < MA_LONG < MA_LONG_START
and abs((PRICE - MA_LONG) / MA_LONG) < STD
)
@staticmethod
def _is_long_close_signal(
MA_LONG: Decimal,
PRICE: Decimal,
STD: Decimal,
has_short_open_signal: bool,
) -> bool:
logger.debug("Try long closing")
logger.debug("\tPRICE > MA_LONG + 10 * STD, %s", PRICE > MA_LONG + 10 * STD)
logger.debug("\tor has_short_open_signal, %s", has_short_open_signal)
logger.debug("\tor PRICE < MA_LONG - 3 * STD, %s", PRICE < MA_LONG - 3 * STD)
logger.debug(
"== %s",
PRICE > MA_LONG + 10 * STD
or has_short_open_signal
or PRICE < MA_LONG - 3 * STD,
)
return (
PRICE > MA_LONG + 10 * STD
or has_short_open_signal
or PRICE < MA_LONG - 3 * STD
)
@staticmethod
def _is_short_close_signal(
MA_LONG: Decimal,
PRICE: Decimal,
STD: Decimal,
has_long_open_signal: bool,
) -> bool:
logger.debug("Try short closing")
logger.debug("\tPRICE < MA_LONG - 10 * STD, %s", PRICE < MA_LONG - 10 * STD)
logger.debug("\tor has_long_open_signal, %s", has_long_open_signal)
logger.debug("\tor PRICE > MA_LONG + 3 * STD, %s", PRICE > MA_LONG + 3 * STD)
logger.debug(
"== %s",
PRICE < MA_LONG - 10 * STD # кажется, что не работает закрытие
or has_long_open_signal
or PRICE > MA_LONG + 3 * STD,
)
return (
PRICE < MA_LONG - 10 * STD
or has_long_open_signal
or PRICE > MA_LONG + 3 * STD
)
def predict(self) -> Iterable[Signal]:
logger.info("Strategy predict")
self._init_MA_LONG_START()
MA_LONG_START = self._MA_LONG_START
logger.debug("MA_LONG_START: %s", MA_LONG_START)
PRICE = self._data[-1].candle.close
logger.debug("PRICE: %s", PRICE)
MA_LONG = self._calculate_moving_average(self._settings.long_period)
logger.debug("MA_LONG: %s", MA_LONG)
MA_SHORT = self._calculate_moving_average(self._settings.short_period)
logger.debug("MA_SHORT: %s", MA_SHORT)
STD = self._calculate_std(self._settings.std_period)
logger.debug("STD: %s", STD)
MONEY = self._account_manager.get_current_balance()
logger.debug("MONEY: %s", MONEY)
has_long_open_signal = False
has_short_open_signal = False
possible_lots = int(MONEY // PRICE)
if (
not self._state.long_open
and self._is_long_open_signal(
MA_SHORT=MA_SHORT,
MA_LONG=MA_LONG,
PRICE=PRICE,
STD=STD,
MA_LONG_START=MA_LONG_START,
)
and possible_lots > 0
):
has_long_open_signal = True
yield OpenLongMarketOrder(lots=possible_lots)
if (
not self._state.short_open
and self._is_short_open_signal(
MA_SHORT=MA_SHORT,
MA_LONG=MA_LONG,
PRICE=PRICE,
STD=STD,
MA_LONG_START=MA_LONG_START,
)
and possible_lots > 0
):
has_short_open_signal = True
yield OpenShortMarketOrder(lots=possible_lots)
if self._state.long_open and self._is_long_close_signal(
MA_LONG=MA_LONG,
PRICE=PRICE,
STD=STD,
has_short_open_signal=has_short_open_signal,
):
yield CloseLongMarketOrder(lots=self._state.position)
if self._state.short_open and self._is_short_close_signal(
MA_LONG=MA_LONG,
PRICE=PRICE,
STD=STD,
has_long_open_signal=has_long_open_signal,
):
yield CloseShortMarketOrder(lots=self._state.position)

View File

@@ -0,0 +1,11 @@
import dataclasses
from datetime import timedelta
from t_tech.invest.strategies.base.strategy_settings_base import StrategySettings
@dataclasses.dataclass
class MovingAverageStrategySettings(StrategySettings):
long_period: timedelta
short_period: timedelta
std_period: timedelta

View File

@@ -0,0 +1,29 @@
class MovingAverageStrategyState:
def __init__(self):
self._long_open: bool = False
self._short_open: bool = False
self._position: int = 0
@property
def long_open(self) -> bool:
return self._long_open
@long_open.setter
def long_open(self, value: bool) -> None:
self._long_open = value
@property
def short_open(self) -> bool:
return self._short_open
@short_open.setter
def short_open(self, value: bool) -> None:
self._short_open = value
@property
def position(self) -> int:
return self._position
@position.setter
def position(self, value: int) -> None:
self._position = value

View File

@@ -0,0 +1,24 @@
from itertools import chain
from typing import Dict, Iterable, List, Type, cast
from t_tech.invest.strategies.base.errors import EventsWereNotSupervised
from t_tech.invest.strategies.base.event import StrategyEvent
from t_tech.invest.strategies.base.strategy_supervisor import StrategySupervisor
class MovingAverageStrategySupervisor(StrategySupervisor):
def __init__(self):
self._events: Dict[Type[StrategyEvent], List[StrategyEvent]] = {}
def notify(self, event: StrategyEvent) -> None:
if type(event) not in self._events:
self._events[type(event)] = []
self._events[type(event)].append(event)
def get_events(self) -> Iterable[StrategyEvent]:
return cast(Iterable[StrategyEvent], chain(*self._events.values()))
def get_events_of_type(self, cls: Type[StrategyEvent]) -> List[StrategyEvent]:
if cls in self._events:
return self._events[cls]
raise EventsWereNotSupervised()

View File

@@ -0,0 +1,172 @@
import logging
import time
from typing import Iterator, List
import t_tech
from t_tech.invest import (
CandleInstrument,
InvestError,
MarketDataRequest,
MarketDataResponse,
SubscribeCandlesRequest,
SubscriptionAction,
SubscriptionInterval,
)
from t_tech.invest.services import Services
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.base.errors import MarketDataNotAvailableError
from t_tech.invest.strategies.base.event import DataEvent, SignalEvent
from t_tech.invest.strategies.base.models import CandleEvent
from t_tech.invest.strategies.base.signal import CloseSignal, OpenSignal, Signal
from t_tech.invest.strategies.base.signal_executor_base import SignalExecutor
from t_tech.invest.strategies.base.trader_base import Trader
from t_tech.invest.strategies.moving_average.strategy import MovingAverageStrategy
from t_tech.invest.strategies.moving_average.strategy_settings import (
MovingAverageStrategySettings,
)
from t_tech.invest.strategies.moving_average.strategy_state import (
MovingAverageStrategyState,
)
from t_tech.invest.strategies.moving_average.supervisor import (
MovingAverageStrategySupervisor,
)
from t_tech.invest.utils import floor_datetime, now
logger = logging.getLogger(__name__)
class MovingAverageStrategyTrader(Trader):
def __init__(
self,
strategy: MovingAverageStrategy,
settings: MovingAverageStrategySettings,
services: Services,
state: MovingAverageStrategyState,
signal_executor: SignalExecutor,
account_manager: AccountManager,
supervisor: MovingAverageStrategySupervisor,
):
super().__init__(strategy, services, settings)
self._settings: MovingAverageStrategySettings = settings
self._strategy = strategy
self._services = services
self._data: List[CandleEvent]
self._market_data_stream: Iterator[MarketDataResponse]
self._state = state
self._signal_executor = signal_executor
self._account_manager = account_manager
self._supervisor = supervisor
self._data = list(
self._load_candles(
(self._settings.short_period + self._settings.long_period) * 3
)
)
for candle_event in self._data:
self._supervisor.notify(self._convert_to_data_event(candle_event))
self._strategy.fit(self._data)
self._ensure_marginal_trade_active()
self._subscribe()
def _ensure_marginal_trade_active(self) -> None:
self._account_manager.ensure_marginal_trade()
def _subscribe(self):
current_instrument = CandleInstrument(
figi=self._settings.share_id,
interval=SubscriptionInterval.SUBSCRIPTION_INTERVAL_ONE_MINUTE,
)
candle_subscribe_request = MarketDataRequest(
subscribe_candles_request=SubscribeCandlesRequest(
subscription_action=SubscriptionAction.SUBSCRIPTION_ACTION_SUBSCRIBE,
instruments=[current_instrument],
)
)
def request_iterator():
yield candle_subscribe_request
while True:
time.sleep(1)
self._market_data_stream = self._services.market_data_stream.market_data_stream(
request_iterator()
)
def _is_candle_fresh(self, candle: t_tech.invest.Candle) -> bool:
is_fresh_border = floor_datetime(
now(), delta=self._settings.candle_interval_timedelta
)
logger.debug(
"Checking if candle is fresh: candle.time=%s > is_fresh_border=%s %s)",
candle.time,
is_fresh_border,
candle.time >= is_fresh_border,
)
return candle.time >= is_fresh_border
@staticmethod
def _convert_to_data_event(candle_event: CandleEvent) -> DataEvent:
return DataEvent(candle_event=candle_event, time=candle_event.time)
def _make_observations(self) -> None:
while True:
market_data_response: MarketDataResponse = next(self._market_data_stream)
logger.debug("got market_data_response: %s", market_data_response)
if market_data_response.candle is None:
logger.debug("market_data_response didn't have candle")
continue
candle = market_data_response.candle
logger.debug("candle extracted: %s", candle)
candle_event = self._convert_candle(candle)
self._strategy.observe(candle_event)
self._supervisor.notify(self._convert_to_data_event(candle_event))
if self._is_candle_fresh(candle):
logger.info("Data refreshed")
break
def _refresh_data(self) -> None:
logger.info("Refreshing data")
try:
self._make_observations()
except StopIteration as e:
logger.info("Fresh quotations not available")
raise MarketDataNotAvailableError() from e
def _filter_closing_signals(self, signals: List[Signal]) -> List[Signal]:
return list(filter(lambda signal: isinstance(signal, CloseSignal), signals))
def _filter_opening_signals(self, signals: List[Signal]) -> List[Signal]:
return list(filter(lambda signal: isinstance(signal, OpenSignal), signals))
def _execute(self, signal: Signal) -> None:
logger.info("Trying to execute signal %s", signal)
try:
self._signal_executor.execute(signal) # type: ignore
except InvestError:
was_executed = False
else:
was_executed = True
self._supervisor.notify(
SignalEvent(signal=signal, was_executed=was_executed, time=now())
)
def _get_signals(self) -> List[Signal]:
signals = list(self._strategy.predict())
return [
*self._filter_closing_signals(signals),
*self._filter_opening_signals(signals),
]
def trade(self) -> None:
"""Делает попытку следовать стратегии."""
logger.info("Balance: %s", self._account_manager.get_current_balance())
self._refresh_data()
signals = self._get_signals()
if signals:
logger.info("Got signals %s", signals)
for signal in signals:
self._execute(signal)
if self._state.position == 0:
logger.info("Trade try complete")