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,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")

View 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

View 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

View 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

View 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)

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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,
)

View File

@@ -0,0 +1,6 @@
from typing import Protocol
class ITrader(Protocol):
def trade(self):
pass