RAPTOR v18.4: Исправлена отчетность, активированы выходные
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from t_tech.invest import Quotation
|
||||
from t_tech.invest.services import Services
|
||||
from t_tech.invest.strategies.base.errors import (
|
||||
InsufficientMarginalTradeFunds,
|
||||
MarginalTradeIsNotActive,
|
||||
)
|
||||
from t_tech.invest.strategies.base.strategy_settings_base import StrategySettings
|
||||
from t_tech.invest.utils import quotation_to_decimal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountManager:
|
||||
def __init__(self, services: Services, strategy_settings: StrategySettings):
|
||||
self._services = services
|
||||
self._strategy_settings = strategy_settings
|
||||
|
||||
def get_current_balance(self) -> Decimal:
|
||||
account_id = self._strategy_settings.account_id
|
||||
portfolio_response = self._services.operations.get_portfolio(
|
||||
account_id=account_id
|
||||
)
|
||||
balance = portfolio_response.total_amount_currencies
|
||||
return quotation_to_decimal(Quotation(units=balance.units, nano=balance.nano))
|
||||
|
||||
def ensure_marginal_trade(self) -> None:
|
||||
account_id = self._strategy_settings.account_id
|
||||
try:
|
||||
response = self._services.users.get_margin_attributes(account_id=account_id)
|
||||
except Exception as e:
|
||||
raise MarginalTradeIsNotActive() from e
|
||||
value = quotation_to_decimal(response.funds_sufficiency_level)
|
||||
if value <= 1:
|
||||
raise InsufficientMarginalTradeFunds()
|
||||
logger.info("Marginal trade is active")
|
||||
38
invest-python-master/t_tech/invest/strategies/base/errors.py
Normal file
38
invest-python-master/t_tech/invest/strategies/base/errors.py
Normal file
@@ -0,0 +1,38 @@
|
||||
class StrategyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotEnoughData(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class MarginalTradeIsNotActive(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class InsufficientMarginalTradeFunds(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class CandleEventForDateNotFound(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSignal(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class OldCandleObservingError(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class MarketDataNotAvailableError(StrategyError):
|
||||
pass
|
||||
|
||||
|
||||
class StrategySupervisorError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EventsWereNotSupervised(StrategySupervisorError):
|
||||
pass
|
||||
21
invest-python-master/t_tech/invest/strategies/base/event.py
Normal file
21
invest-python-master/t_tech/invest/strategies/base/event.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from t_tech.invest.strategies.base.models import CandleEvent
|
||||
from t_tech.invest.strategies.base.signal import Signal
|
||||
|
||||
|
||||
@dataclass
|
||||
class StrategyEvent:
|
||||
time: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataEvent(StrategyEvent):
|
||||
candle_event: CandleEvent
|
||||
|
||||
|
||||
@dataclass
|
||||
class SignalEvent(StrategyEvent):
|
||||
signal: Signal
|
||||
was_executed: bool
|
||||
19
invest-python-master/t_tech/invest/strategies/base/models.py
Normal file
19
invest-python-master/t_tech/invest/strategies/base/models.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=True)
|
||||
class Candle:
|
||||
open: Decimal
|
||||
high: Decimal
|
||||
low: Decimal
|
||||
close: Decimal
|
||||
|
||||
|
||||
@dataclass(eq=False, repr=True)
|
||||
class CandleEvent:
|
||||
candle: Candle
|
||||
volume: int
|
||||
time: datetime
|
||||
is_complete: bool
|
||||
48
invest-python-master/t_tech/invest/strategies/base/signal.py
Normal file
48
invest-python-master/t_tech/invest/strategies/base/signal.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import enum
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
class SignalDirection(enum.Enum):
|
||||
LONG = "LONG"
|
||||
SHORT = "SHORT"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Signal:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrderSignal(Signal):
|
||||
lots: int
|
||||
direction: SignalDirection
|
||||
|
||||
|
||||
@dataclass
|
||||
class CloseSignal(OrderSignal):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpenSignal(OrderSignal):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpenLongMarketOrder(OpenSignal):
|
||||
direction: SignalDirection = field(default=SignalDirection.LONG)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CloseLongMarketOrder(CloseSignal):
|
||||
direction: SignalDirection = field(default=SignalDirection.LONG)
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpenShortMarketOrder(OpenSignal):
|
||||
direction: SignalDirection = field(default=SignalDirection.SHORT)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CloseShortMarketOrder(CloseSignal):
|
||||
direction: SignalDirection = field(default=SignalDirection.SHORT)
|
||||
@@ -0,0 +1,55 @@
|
||||
from t_tech.invest import OrderDirection, OrderType
|
||||
from t_tech.invest.services import Services
|
||||
from t_tech.invest.strategies.base.signal import (
|
||||
CloseLongMarketOrder,
|
||||
CloseShortMarketOrder,
|
||||
OpenLongMarketOrder,
|
||||
OpenShortMarketOrder,
|
||||
)
|
||||
from t_tech.invest.strategies.base.strategy_settings_base import StrategySettings
|
||||
|
||||
|
||||
class SignalExecutor:
|
||||
def __init__(
|
||||
self,
|
||||
services: Services,
|
||||
settings: StrategySettings,
|
||||
):
|
||||
self._services = services
|
||||
self._settings = settings
|
||||
|
||||
def execute_open_long_market_order(self, signal: OpenLongMarketOrder) -> None:
|
||||
self._services.orders.post_order(
|
||||
figi=self._settings.share_id,
|
||||
quantity=signal.lots,
|
||||
direction=OrderDirection.ORDER_DIRECTION_BUY,
|
||||
account_id=self._settings.account_id,
|
||||
order_type=OrderType.ORDER_TYPE_MARKET,
|
||||
)
|
||||
|
||||
def execute_close_long_market_order(self, signal: CloseLongMarketOrder) -> None:
|
||||
self._services.orders.post_order(
|
||||
figi=self._settings.share_id,
|
||||
quantity=signal.lots,
|
||||
direction=OrderDirection.ORDER_DIRECTION_SELL,
|
||||
account_id=self._settings.account_id,
|
||||
order_type=OrderType.ORDER_TYPE_MARKET,
|
||||
)
|
||||
|
||||
def execute_open_short_market_order(self, signal: OpenShortMarketOrder) -> None:
|
||||
self._services.orders.post_order(
|
||||
figi=self._settings.share_id,
|
||||
quantity=signal.lots,
|
||||
direction=OrderDirection.ORDER_DIRECTION_SELL,
|
||||
account_id=self._settings.account_id,
|
||||
order_type=OrderType.ORDER_TYPE_MARKET,
|
||||
)
|
||||
|
||||
def execute_close_short_market_order(self, signal: CloseShortMarketOrder) -> None:
|
||||
self._services.orders.post_order(
|
||||
figi=self._settings.share_id,
|
||||
quantity=signal.lots,
|
||||
direction=OrderDirection.ORDER_DIRECTION_BUY,
|
||||
account_id=self._settings.account_id,
|
||||
order_type=OrderType.ORDER_TYPE_MARKET,
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import Iterable, Protocol
|
||||
|
||||
from t_tech.invest.strategies.base.models import CandleEvent
|
||||
from t_tech.invest.strategies.base.signal import Signal
|
||||
|
||||
|
||||
class InvestStrategy(Protocol):
|
||||
def fit(self, candles: Iterable[CandleEvent]) -> None:
|
||||
pass
|
||||
|
||||
def observe(self, candle: CandleEvent) -> None:
|
||||
pass
|
||||
|
||||
def predict(self) -> Iterable[Signal]:
|
||||
pass
|
||||
@@ -0,0 +1,19 @@
|
||||
import dataclasses
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from t_tech.invest import CandleInterval
|
||||
from t_tech.invest.typedefs import AccountId, ShareId
|
||||
from t_tech.invest.utils import candle_interval_to_timedelta
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StrategySettings:
|
||||
share_id: ShareId
|
||||
account_id: AccountId
|
||||
max_transaction_price: Decimal
|
||||
candle_interval: CandleInterval
|
||||
|
||||
@property
|
||||
def candle_interval_timedelta(self) -> timedelta:
|
||||
return candle_interval_to_timedelta(self.candle_interval)
|
||||
@@ -0,0 +1,29 @@
|
||||
import abc
|
||||
from typing import Iterable, Protocol, Type
|
||||
|
||||
from t_tech.invest.strategies.base.event import StrategyEvent
|
||||
|
||||
|
||||
class IStrategySupervisor(Protocol):
|
||||
def notify(self, event: StrategyEvent) -> None:
|
||||
pass
|
||||
|
||||
def get_events(self) -> Iterable[StrategyEvent]:
|
||||
pass
|
||||
|
||||
def get_events_of_type(self, cls: Type[StrategyEvent]) -> Iterable[StrategyEvent]:
|
||||
pass
|
||||
|
||||
|
||||
class StrategySupervisor(abc.ABC, IStrategySupervisor):
|
||||
@abc.abstractmethod
|
||||
def notify(self, event: StrategyEvent) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_events(self) -> Iterable[StrategyEvent]:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_events_of_type(self, cls: Type[StrategyEvent]) -> Iterable[StrategyEvent]:
|
||||
pass
|
||||
@@ -0,0 +1,69 @@
|
||||
import abc
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Iterable
|
||||
|
||||
import t_tech
|
||||
from t_tech.invest import HistoricCandle
|
||||
from t_tech.invest.services import Services
|
||||
from t_tech.invest.strategies.base.models import Candle, CandleEvent
|
||||
from t_tech.invest.strategies.base.strategy_interface import InvestStrategy
|
||||
from t_tech.invest.strategies.base.strategy_settings_base import StrategySettings
|
||||
from t_tech.invest.strategies.base.trader_interface import ITrader
|
||||
from t_tech.invest.utils import now, quotation_to_decimal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Trader(ITrader, abc.ABC):
|
||||
def __init__(
|
||||
self,
|
||||
strategy: InvestStrategy,
|
||||
services: Services,
|
||||
settings: StrategySettings,
|
||||
):
|
||||
self._strategy = strategy
|
||||
self._services = services
|
||||
self._settings = settings
|
||||
|
||||
@staticmethod
|
||||
def _convert_historic_candles_into_candle_events(
|
||||
historic_candles: Iterable[HistoricCandle],
|
||||
) -> Iterable[CandleEvent]:
|
||||
for candle in historic_candles:
|
||||
yield CandleEvent(
|
||||
candle=Candle(
|
||||
open=quotation_to_decimal(candle.open),
|
||||
close=quotation_to_decimal(candle.close),
|
||||
high=quotation_to_decimal(candle.high),
|
||||
low=quotation_to_decimal(candle.low),
|
||||
),
|
||||
volume=candle.volume,
|
||||
time=candle.time,
|
||||
is_complete=candle.is_complete,
|
||||
)
|
||||
|
||||
def _load_candles(self, period: timedelta) -> Iterable[CandleEvent]:
|
||||
logger.info("Loading candles for period %s from %s", period, now())
|
||||
|
||||
yield from self._convert_historic_candles_into_candle_events(
|
||||
self._services.get_all_candles(
|
||||
figi=self._settings.share_id,
|
||||
from_=now() - period,
|
||||
interval=self._settings.candle_interval,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _convert_candle(candle: t_tech.invest.schemas.Candle) -> CandleEvent:
|
||||
return CandleEvent(
|
||||
candle=Candle(
|
||||
open=quotation_to_decimal(candle.open),
|
||||
close=quotation_to_decimal(candle.close),
|
||||
high=quotation_to_decimal(candle.high),
|
||||
low=quotation_to_decimal(candle.low),
|
||||
),
|
||||
volume=candle.volume,
|
||||
time=candle.time,
|
||||
is_complete=False,
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class ITrader(Protocol):
|
||||
def trade(self):
|
||||
pass
|
||||
Reference in New Issue
Block a user