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,385 @@
import logging
from datetime import timedelta
from decimal import Decimal
from math import exp, sqrt
from random import gauss, seed
from typing import Callable, Dict, Iterable, Iterator, List, Optional
import pytest
from grpc import StatusCode
from t_tech.invest import (
Candle,
Client,
GetCandlesResponse,
GetMarginAttributesResponse,
HistoricCandle,
MarketDataResponse,
MoneyValue,
OrderDirection,
OrderType,
PortfolioPosition,
PortfolioResponse,
Quotation,
RequestError,
)
from t_tech.invest.services import Services
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.moving_average.plotter import MovingAverageStrategyPlotter
from t_tech.invest.strategies.moving_average.signal_executor import (
MovingAverageSignalExecutor,
)
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.strategies.moving_average.trader import MovingAverageStrategyTrader
from t_tech.invest.utils import (
candle_interval_to_subscription_interval,
candle_interval_to_timedelta,
decimal_to_quotation,
now,
quotation_to_decimal,
)
logging.basicConfig(format="%(asctime)s %(levelname)s:%(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)
seed(1338)
def create_noise(s0, mu, sigma) -> Callable[[int], Iterable[float]]:
"""Create Noise.
Generates a price following a geometric brownian motion process based on the input.
- s0: Asset initial price.
- mu: Interest rate expressed annual terms.
- sigma: Volatility expressed annual terms.
"""
st = s0
def generate_range(limit: int):
nonlocal st
for _ in range(limit):
st *= exp(
(mu - 0.5 * sigma**2) * (1.0 / 365.0)
+ sigma * sqrt(1.0 / 365.0) * gauss(mu=0, sigma=1)
)
yield st
return generate_range
@pytest.fixture()
def stock_prices_generator() -> Callable[[int], Iterable[float]]:
return create_noise(100, 0.01, 0.1)
@pytest.fixture()
def stock_volume_generator() -> Callable[[int], Iterable[float]]:
return create_noise(1000, 0.9, 1.1)
@pytest.fixture()
def initial_candles(
settings: MovingAverageStrategySettings,
stock_prices_generator: Callable[[int], Iterable[float]],
stock_volume_generator: Callable[[int], Iterable[float]],
) -> Iterable[HistoricCandle]:
candles = []
intervals = 365
interval_delta = candle_interval_to_timedelta(settings.candle_interval)
base = now() - interval_delta * intervals
(close,) = stock_prices_generator(1)
for i in range(intervals):
open_ = close
low, high, close = stock_prices_generator(3)
low, high = min(low, high, open_, close), max(low, high, open_, close)
(volume,) = stock_volume_generator(1)
candle = HistoricCandle(
open=decimal_to_quotation(Decimal(open_)),
high=decimal_to_quotation(Decimal(high)),
low=decimal_to_quotation(Decimal(low)),
close=decimal_to_quotation(Decimal(close)),
volume=int(volume),
time=base + interval_delta * i,
is_complete=True,
)
candles.append(candle)
return candles
@pytest.fixture()
def real_services(token: str) -> Iterator[Services]:
with Client(token) as services:
yield services
@pytest.fixture()
def mock_market_data_service(
real_services: Services,
mocker,
initial_candles: List[HistoricCandle],
) -> Services:
real_services.market_data = mocker.Mock(wraps=real_services.market_data)
real_services.market_data.get_candles = mocker.Mock()
real_services.market_data.get_candles.return_value = GetCandlesResponse(
candles=initial_candles
)
return real_services
@pytest.fixture()
def current_market_data() -> List[Candle]:
return []
@pytest.fixture()
def mock_market_data_stream_service(
real_services: Services,
mocker,
figi: str,
stock_prices_generator: Callable[[int], Iterable[float]],
stock_volume_generator: Callable[[int], Iterable[float]],
settings: MovingAverageStrategySettings,
current_market_data: List[Candle],
freezer,
) -> Services:
real_services.market_data_stream = mocker.Mock(
wraps=real_services.market_data_stream
)
def _market_data_stream(*args, **kwargs):
yield MarketDataResponse(candle=None) # type: ignore
(close,) = stock_prices_generator(1)
while True:
open_ = close
low, high, close = stock_prices_generator(3)
low, high = min(low, high, open_, close), max(low, high, open_, close)
(volume,) = stock_volume_generator(1)
candle = Candle(
figi=figi,
interval=candle_interval_to_subscription_interval(
settings.candle_interval
),
open=decimal_to_quotation(Decimal(open_)),
high=decimal_to_quotation(Decimal(high)),
low=decimal_to_quotation(Decimal(low)),
close=decimal_to_quotation(Decimal(close)),
volume=int(volume),
time=now(),
)
current_market_data.append(candle)
yield MarketDataResponse(candle=candle)
freezer.move_to(now() + timedelta(minutes=1))
real_services.market_data_stream.market_data_stream = _market_data_stream
return real_services
@pytest.fixture()
def mock_operations_service(
real_services: Services,
mocker,
portfolio_response: PortfolioResponse,
) -> Services:
real_services.operations = mocker.Mock(wraps=real_services.operations)
real_services.operations.get_portfolio.return_value = portfolio_response
return real_services
@pytest.fixture()
def mock_users_service(
real_services: Services,
mocker,
marginal_trade_active: bool,
) -> Services:
real_services.users = mocker.Mock(wraps=real_services.users)
if marginal_trade_active:
real_services.users.get_margin_attributes.return_value = (
GetMarginAttributesResponse(
liquid_portfolio=MoneyValue(currency="", units=0, nano=0),
starting_margin=MoneyValue(currency="", units=0, nano=0),
minimal_margin=MoneyValue(currency="", units=0, nano=0),
funds_sufficiency_level=Quotation(units=322, nano=0),
amount_of_missing_funds=MoneyValue(currency="", units=0, nano=0),
)
)
else:
real_services.users.get_margin_attributes.side_effect = RequestError(
code=StatusCode.PERMISSION_DENIED,
details="Marginal trade disabled",
metadata=None,
)
return real_services
@pytest.fixture()
def marginal_trade_active() -> bool:
return True
@pytest.fixture()
def mock_orders_service(
real_services: Services,
mocker,
portfolio_positions: Dict[str, PortfolioPosition],
balance: MoneyValue,
current_market_data: List[Candle],
settings: MovingAverageStrategySettings,
marginal_trade_active: bool,
) -> Services:
real_services.orders = mocker.Mock(wraps=real_services.orders)
def _post_order(
*,
figi: str = "",
quantity: int = 0,
price: Optional[Quotation] = None,
direction: OrderDirection = OrderDirection(0),
account_id: str = "",
order_type: OrderType = OrderType(0),
order_id: str = "",
):
assert figi == settings.share_id
assert quantity > 0
assert account_id == settings.account_id
assert order_type.ORDER_TYPE_MARKET
last_candle = current_market_data[-1]
last_market_price = quotation_to_decimal(last_candle.close)
position = portfolio_positions.get(figi)
if position is None:
position = PortfolioPosition(
figi=figi,
quantity=decimal_to_quotation(Decimal(0)),
)
if direction == OrderDirection.ORDER_DIRECTION_SELL:
quantity_delta = -quantity
balance_delta = last_market_price * quantity
elif direction == OrderDirection.ORDER_DIRECTION_BUY:
quantity_delta = +quantity
balance_delta = -(last_market_price * quantity)
else:
raise AssertionError("Incorrect direction")
logger.warning("Operation: %s, %s", direction, balance_delta)
old_quantity = quotation_to_decimal(position.quantity)
new_quantity = decimal_to_quotation(old_quantity + quantity_delta)
position.quantity.units = new_quantity.units
position.quantity.nano = new_quantity.nano
old_balance = quotation_to_decimal(
Quotation(units=balance.units, nano=balance.nano)
)
new_balance = decimal_to_quotation(old_balance + balance_delta)
balance.units = new_balance.units
balance.nano = new_balance.nano
portfolio_positions[figi] = position
real_services.orders.post_order = _post_order
return real_services
@pytest.fixture()
def mocked_services(
real_services: Services,
mock_market_data_service,
mock_market_data_stream_service,
mock_operations_service,
mock_users_service,
mock_orders_service,
) -> Services:
return real_services
@pytest.fixture()
def account_manager(
mocked_services: Services, settings: MovingAverageStrategySettings
) -> AccountManager:
return AccountManager(services=mocked_services, strategy_settings=settings)
@pytest.fixture()
def state() -> MovingAverageStrategyState:
return MovingAverageStrategyState()
@pytest.fixture()
def strategy(
settings: MovingAverageStrategySettings,
account_manager: AccountManager,
state: MovingAverageStrategyState,
) -> MovingAverageStrategy:
return MovingAverageStrategy(
settings=settings,
account_manager=account_manager,
state=state,
)
@pytest.fixture()
def signal_executor(
mocked_services: Services,
state: MovingAverageStrategyState,
settings: MovingAverageStrategySettings,
) -> MovingAverageSignalExecutor:
return MovingAverageSignalExecutor(
services=mocked_services,
state=state,
settings=settings,
)
@pytest.fixture()
def supervisor() -> MovingAverageStrategySupervisor:
return MovingAverageStrategySupervisor()
@pytest.fixture()
def moving_average_strategy_trader(
strategy: MovingAverageStrategy,
settings: MovingAverageStrategySettings,
mocked_services: Services,
state: MovingAverageStrategyState,
signal_executor: MovingAverageSignalExecutor,
account_manager: AccountManager,
supervisor: MovingAverageStrategySupervisor,
) -> MovingAverageStrategyTrader:
return MovingAverageStrategyTrader(
strategy=strategy,
settings=settings,
services=mocked_services,
state=state,
signal_executor=signal_executor,
account_manager=account_manager,
supervisor=supervisor,
)
@pytest.fixture()
def plotter(
settings: MovingAverageStrategySettings,
) -> MovingAverageStrategyPlotter:
return MovingAverageStrategyPlotter(settings=settings)

View File

@@ -0,0 +1,133 @@
import logging
from datetime import timedelta
from decimal import Decimal
from typing import Dict
import matplotlib.pyplot as plt
import pytest
from t_tech.invest import (
CandleInterval,
MoneyValue,
PortfolioPosition,
PortfolioResponse,
Quotation,
)
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.moving_average.plotter import MovingAverageStrategyPlotter
from t_tech.invest.strategies.moving_average.signal_executor import (
MovingAverageSignalExecutor,
)
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.supervisor import (
MovingAverageStrategySupervisor,
)
from t_tech.invest.strategies.moving_average.trader import MovingAverageStrategyTrader
from t_tech.invest.typedefs import AccountId, ShareId
logging.basicConfig(format="%(asctime)s %(levelname)s:%(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)
@pytest.fixture()
def token() -> str:
return "some"
@pytest.fixture()
def portfolio_positions() -> Dict[str, PortfolioPosition]:
return {
"BBG004730N88": PortfolioPosition(
figi="BBG004730N88",
instrument_type="share",
quantity=Quotation(units=110, nano=0),
average_position_price=MoneyValue(
currency="rub", units=261, nano=800000000
),
expected_yield=Quotation(units=-106, nano=-700000000),
current_nkd=MoneyValue(currency="", units=0, nano=0),
average_position_price_pt=Quotation(units=0, nano=0),
current_price=MoneyValue(currency="rub", units=260, nano=830000000),
)
}
@pytest.fixture()
def balance() -> MoneyValue:
return MoneyValue(currency="rub", units=20050, nano=690000000)
@pytest.fixture()
def portfolio_response(
portfolio_positions: Dict[str, PortfolioPosition],
balance: MoneyValue,
) -> PortfolioResponse:
return PortfolioResponse(
total_amount_shares=MoneyValue(currency="rub", units=28691, nano=300000000),
total_amount_bonds=MoneyValue(currency="rub", units=0, nano=0),
total_amount_etf=MoneyValue(currency="rub", units=0, nano=0),
total_amount_currencies=balance,
total_amount_futures=MoneyValue(currency="rub", units=0, nano=0),
expected_yield=Quotation(units=0, nano=-350000000),
positions=list(portfolio_positions.values()),
)
@pytest.fixture()
def figi() -> str:
return "BBG0047315Y7"
@pytest.fixture()
def account_id() -> str:
return AccountId("1337007228")
@pytest.fixture()
def settings(figi: str, account_id: AccountId) -> MovingAverageStrategySettings:
return MovingAverageStrategySettings(
share_id=ShareId(figi),
account_id=account_id,
max_transaction_price=Decimal(10000),
candle_interval=CandleInterval.CANDLE_INTERVAL_1_MIN,
long_period=timedelta(minutes=100),
short_period=timedelta(minutes=20),
std_period=timedelta(minutes=30),
)
class TestMovingAverageStrategyTrader:
@pytest.mark.freeze_time()
def test_trade(
self,
moving_average_strategy_trader: MovingAverageStrategyTrader,
strategy: MovingAverageStrategy,
account_manager: AccountManager,
signal_executor: MovingAverageSignalExecutor,
plotter: MovingAverageStrategyPlotter,
supervisor: MovingAverageStrategySupervisor,
caplog,
freezer,
):
caplog.set_level(logging.DEBUG)
caplog.set_level(logging.INFO)
initial_balance = account_manager.get_current_balance()
for i in range(50):
logger.info("Trade %s", i)
moving_average_strategy_trader.trade()
current_balance = account_manager.get_current_balance()
assert initial_balance != current_balance
logger.info("Initial balance %s", initial_balance)
logger.info("Current balance %s", current_balance)
events = supervisor.get_events()
plotter.plot(events)
plt.show(block=False)
plt.pause(1)
plt.close()

View File

@@ -0,0 +1,216 @@
import logging
import os
from datetime import timedelta
from decimal import Decimal
from typing import Iterator
import matplotlib.pyplot as plt
import pytest
from t_tech.invest import (
CandleInterval,
Client,
GetMarginAttributesResponse,
InstrumentIdType,
MoneyValue,
OpenSandboxAccountResponse,
Quotation,
ShareResponse,
TradingSchedulesResponse,
)
from t_tech.invest.exceptions import UnauthenticatedError
from t_tech.invest.services import SandboxService, 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.moving_average.plotter import MovingAverageStrategyPlotter
from t_tech.invest.strategies.moving_average.signal_executor import (
MovingAverageSignalExecutor,
)
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.supervisor import (
MovingAverageStrategySupervisor,
)
from t_tech.invest.strategies.moving_average.trader import MovingAverageStrategyTrader
from t_tech.invest.typedefs import AccountId, ShareId
from t_tech.invest.utils import now
logging.basicConfig(format="%(asctime)s %(levelname)s:%(message)s", level=logging.DEBUG)
logger = logging.getLogger(__name__)
@pytest.fixture()
def token() -> str:
return os.environ["INVEST_SANDBOX_TOKEN"]
@pytest.fixture()
def account(
token: str, real_services: Services, balance: MoneyValue
) -> Iterator[OpenSandboxAccountResponse]:
sandbox: SandboxService = real_services.sandbox
account_response = sandbox.open_sandbox_account()
account_id = account_response.account_id
sandbox.sandbox_pay_in(account_id=account_id, amount=balance)
yield account_response
sandbox.close_sandbox_account(account_id=account_id)
@pytest.fixture()
def account_id(account: OpenSandboxAccountResponse) -> str:
return account.account_id
@pytest.fixture()
def mock_market_data_service(real_services: Services) -> Services:
return real_services
@pytest.fixture()
def mock_market_data_stream_service(real_services: Services) -> Services:
return real_services
@pytest.fixture()
def mock_operations_service(real_services: Services) -> Services:
real_services.operations.get_portfolio = real_services.sandbox.get_sandbox_portfolio
real_services.operations.get_operations = (
real_services.sandbox.get_sandbox_operations
)
return real_services
@pytest.fixture()
def mock_users_service(
real_services: Services,
mocker,
) -> Services:
real_services.users = mocker.Mock(wraps=real_services.users)
real_services.users.get_margin_attributes.return_value = (
GetMarginAttributesResponse(
liquid_portfolio=MoneyValue(currency="", units=0, nano=0),
starting_margin=MoneyValue(currency="", units=0, nano=0),
minimal_margin=MoneyValue(currency="", units=0, nano=0),
funds_sufficiency_level=Quotation(units=322, nano=0),
amount_of_missing_funds=MoneyValue(currency="", units=0, nano=0),
)
)
return real_services
@pytest.fixture()
def mock_orders_service(real_services: Services) -> Services:
real_services.orders.post_order = real_services.sandbox.post_sandbox_order
real_services.orders.get_orders = real_services.sandbox.get_sandbox_orders
real_services.orders.cancel_order = real_services.sandbox.cancel_sandbox_order
real_services.orders.get_order_state = real_services.sandbox.get_sandbox_order_state
return real_services
@pytest.fixture()
def mocked_services(
real_services: Services,
mock_market_data_service,
mock_market_data_stream_service,
mock_operations_service,
mock_users_service,
mock_orders_service,
) -> Services:
return real_services
@pytest.fixture()
def figi() -> str:
return "BBG004730N88"
@pytest.fixture()
def balance() -> MoneyValue:
return MoneyValue(currency="rub", units=20050, nano=690000000)
@pytest.fixture()
def settings(figi: str, account_id: AccountId) -> MovingAverageStrategySettings:
return MovingAverageStrategySettings(
share_id=ShareId(figi),
account_id=account_id,
max_transaction_price=Decimal(10000),
candle_interval=CandleInterval.CANDLE_INTERVAL_HOUR,
long_period=timedelta(hours=200),
short_period=timedelta(hours=50),
std_period=timedelta(hours=30),
)
@pytest.fixture(autouse=True)
def _ensure_is_market_active(
settings: MovingAverageStrategySettings,
):
token = os.environ.get("INVEST_SANDBOX_TOKEN")
if token is None:
pytest.skip("INVEST_SANDBOX_TOKEN should be specified")
with Client(token) as client:
share_response: ShareResponse = client.instruments.share_by(
id_type=InstrumentIdType.INSTRUMENT_ID_TYPE_FIGI, id=settings.share_id
)
logger.debug("Instrument = %s", share_response.instrument.name)
response: TradingSchedulesResponse = client.instruments.trading_schedules(
exchange=share_response.instrument.exchange,
from_=now(),
to=now(),
)
(exchange,) = response.exchanges
logger.debug("Exchange = %s", exchange.exchange)
(day,) = exchange.days
if day.is_trading_day and day.start_time < now() < day.end_time:
return
pytest.skip("test skipped because market is closed")
@pytest.mark.test_sandbox()
@pytest.mark.skipif(
os.environ.get("INVEST_SANDBOX_TOKEN") is None,
reason="INVEST_SANDBOX_TOKEN should be specified",
)
@pytest.mark.xfail(
raises=UnauthenticatedError,
reason="INVEST_SANDBOX_TOKEN is incorrect",
)
class TestMovingAverageStrategyTraderInSandbox:
def test_trade(
self,
moving_average_strategy_trader: MovingAverageStrategyTrader,
strategy: MovingAverageStrategy,
account_manager: AccountManager,
signal_executor: MovingAverageSignalExecutor,
plotter: MovingAverageStrategyPlotter,
supervisor: MovingAverageStrategySupervisor,
caplog,
):
caplog.set_level(logging.DEBUG)
caplog.set_level(logging.INFO)
initial_balance = account_manager.get_current_balance()
try:
for i in range(50):
logger.info("Trade %s", i)
moving_average_strategy_trader.trade()
except MarketDataNotAvailableError:
pass
current_balance = account_manager.get_current_balance()
logger.info("Initial balance %s", initial_balance)
logger.info("Current balance %s", current_balance)
events = supervisor.get_events()
plotter.plot(events)
plt.show(block=False)
plt.pause(1)
plt.close()

View File

@@ -0,0 +1,267 @@
import logging
import os
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Dict, Iterable, List
import pytest
from matplotlib import pyplot as plt
from t_tech.invest import (
Candle,
CandleInterval,
HistoricCandle,
MarketDataResponse,
MoneyValue,
PortfolioPosition,
PortfolioResponse,
Quotation,
)
from t_tech.invest.services import Services
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.moving_average.plotter import MovingAverageStrategyPlotter
from t_tech.invest.strategies.moving_average.signal_executor import (
MovingAverageSignalExecutor,
)
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.supervisor import (
MovingAverageStrategySupervisor,
)
from t_tech.invest.strategies.moving_average.trader import MovingAverageStrategyTrader
from t_tech.invest.typedefs import AccountId, ShareId
from t_tech.invest.utils import candle_interval_to_subscription_interval, now
logging.basicConfig(format="%(asctime)s %(levelname)s:%(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)
@pytest.fixture()
def token() -> str:
return os.environ["INVEST_SANDBOX_TOKEN"]
@pytest.fixture()
def real_market_data_test_from(request) -> datetime:
return request.param
@pytest.fixture()
def real_market_data_test_start(request) -> datetime:
return request.param
@pytest.fixture()
def real_market_data_test_end(request) -> datetime:
return request.param
@pytest.fixture()
def real_market_data(
real_services: Services,
real_market_data_test_from: datetime,
real_market_data_test_end: datetime,
figi: str,
settings: MovingAverageStrategySettings,
) -> Iterable[HistoricCandle]:
candles = []
for candle in real_services.get_all_candles(
figi=figi,
from_=real_market_data_test_from,
to=real_market_data_test_end,
interval=settings.candle_interval,
):
candles.append(candle)
return candles
@pytest.fixture()
def initial_candles(
real_market_data_test_start: datetime,
real_market_data: Iterable[HistoricCandle],
) -> Iterable[HistoricCandle]:
return [
candle
for candle in real_market_data
if candle.time < real_market_data_test_start
]
@pytest.fixture()
def after_start_candles(
real_market_data_test_start: datetime,
real_market_data: Iterable[HistoricCandle],
) -> Iterable[HistoricCandle]:
return [
candle
for candle in real_market_data
if candle.time >= real_market_data_test_start
]
@pytest.fixture()
def current_market_data() -> List[Candle]:
return []
@pytest.fixture()
def mock_market_data_stream_service(
real_services: Services,
mocker,
figi: str,
settings: MovingAverageStrategySettings,
current_market_data: List[Candle],
freezer,
after_start_candles: Iterable[HistoricCandle],
real_market_data_test_from: datetime,
real_market_data_test_start: datetime,
real_market_data_test_end: datetime,
) -> Services:
real_services.market_data_stream = mocker.Mock(
wraps=real_services.market_data_stream
)
freezer.move_to(real_market_data_test_start)
def _market_data_stream(*args, **kwargs):
yield MarketDataResponse(candle=None) # type: ignore
interval = candle_interval_to_subscription_interval(settings.candle_interval)
for historic_candle in after_start_candles:
candle = Candle(
figi=figi,
interval=interval,
open=historic_candle.open,
high=historic_candle.high,
low=historic_candle.low,
close=historic_candle.close,
volume=historic_candle.volume,
time=historic_candle.time,
)
current_market_data.append(candle)
yield MarketDataResponse(candle=candle)
freezer.move_to(now() + timedelta(minutes=1))
real_services.market_data_stream.market_data_stream = _market_data_stream
return real_services
@pytest.fixture()
def portfolio_positions() -> Dict[str, PortfolioPosition]:
return {
"BBG004730N88": PortfolioPosition(
figi="BBG004730N88",
instrument_type="share",
quantity=Quotation(units=110, nano=0),
average_position_price=MoneyValue(
currency="rub", units=261, nano=800000000
),
expected_yield=Quotation(units=-106, nano=-700000000),
current_nkd=MoneyValue(currency="", units=0, nano=0),
average_position_price_pt=Quotation(units=0, nano=0),
current_price=MoneyValue(currency="rub", units=260, nano=830000000),
)
}
@pytest.fixture()
def balance() -> MoneyValue:
return MoneyValue(currency="rub", units=20050, nano=690000000)
@pytest.fixture()
def portfolio_response(
portfolio_positions: Dict[str, PortfolioPosition],
balance: MoneyValue,
) -> PortfolioResponse:
return PortfolioResponse(
total_amount_shares=MoneyValue(currency="rub", units=28691, nano=300000000),
total_amount_bonds=MoneyValue(currency="rub", units=0, nano=0),
total_amount_etf=MoneyValue(currency="rub", units=0, nano=0),
total_amount_currencies=balance,
total_amount_futures=MoneyValue(currency="rub", units=0, nano=0),
expected_yield=Quotation(units=0, nano=-350000000),
positions=list(portfolio_positions.values()),
)
@pytest.fixture()
def figi() -> str:
return "BBG0047315Y7"
@pytest.fixture()
def account_id() -> str:
return AccountId("1337007228")
@pytest.fixture()
def settings(figi: str, account_id: AccountId) -> MovingAverageStrategySettings:
return MovingAverageStrategySettings(
share_id=ShareId(figi),
account_id=account_id,
max_transaction_price=Decimal(10000),
candle_interval=CandleInterval.CANDLE_INTERVAL_1_MIN,
long_period=timedelta(minutes=100),
short_period=timedelta(minutes=50),
std_period=timedelta(minutes=30),
)
def start_datetime() -> datetime:
return datetime(year=2022, month=2, day=16, hour=17, tzinfo=timezone.utc)
@pytest.mark.skipif(
os.environ.get("INVEST_SANDBOX_TOKEN") is None,
reason="INVEST_SANDBOX_TOKEN should be specified",
)
class TestMovingAverageStrategyTraderRealMarketData:
@pytest.mark.freeze_time()
@pytest.mark.parametrize(
(
"real_market_data_test_from",
"real_market_data_test_start",
"real_market_data_test_end",
),
[
(
start_datetime() - timedelta(days=1),
start_datetime(),
start_datetime() + timedelta(days=3),
)
],
indirect=True,
)
def test_trade(
self,
moving_average_strategy_trader: MovingAverageStrategyTrader,
strategy: MovingAverageStrategy,
account_manager: AccountManager,
signal_executor: MovingAverageSignalExecutor,
plotter: MovingAverageStrategyPlotter,
supervisor: MovingAverageStrategySupervisor,
caplog,
freezer,
):
caplog.set_level(logging.DEBUG)
caplog.set_level(logging.INFO)
initial_balance = account_manager.get_current_balance()
for i in range(50):
logger.info("Trade %s", i)
moving_average_strategy_trader.trade()
current_balance = account_manager.get_current_balance()
assert initial_balance != current_balance
logger.info("Initial balance %s", initial_balance)
logger.info("Current balance %s", current_balance)
events = supervisor.get_events()
plotter.plot(events)
plt.show(block=False)
plt.pause(1)
plt.close()