# pylint: disable=redefined-outer-name,unused-variable import os import uuid from datetime import datetime import pytest from _decimal import Decimal from examples.sandbox.sandbox_cancel_stop_order import cancel_stop_order from examples.sandbox.sandbox_get_order_price import get_order_price from examples.sandbox.sandbox_get_stop_orders import get_stop_orders from examples.sandbox.sandbox_post_stop_order import post_stop_order from t_tech.invest import ( Account, CloseSandboxAccountResponse, MoneyValue, OperationState, OrderDirection, OrderType, Quotation, RequestError, utils, ) from t_tech.invest.sandbox.client import SandboxClient from t_tech.invest.schemas import ( GetOrderPriceResponse, OrderExecutionReportStatus, PostOrderAsyncRequest, StopOrderDirection, StopOrderStatusOption, ) from t_tech.invest.utils import money_to_decimal from tests.utils import skip_when @pytest.fixture() def sandbox_service(): with SandboxClient(token=os.environ["INVEST_SANDBOX_TOKEN"]) as client: yield client @pytest.fixture() def initial_balance_pay_in() -> MoneyValue: return MoneyValue(currency="rub", units=1000000, nano=0) @pytest.fixture() def account_id(sandbox_service, initial_balance_pay_in: MoneyValue): response = sandbox_service.sandbox.open_sandbox_account() sandbox_service.sandbox.sandbox_pay_in( account_id=response.account_id, amount=initial_balance_pay_in, ) yield response.account_id sandbox_service.sandbox.close_sandbox_account( account_id=response.account_id, ) @pytest.fixture() def figi() -> str: return "BBG333333333" @pytest.fixture() def instrument_id() -> str: return "f509af83-6e71-462f-901f-bcb073f6773b" @pytest.fixture() def quantity() -> int: return 10 @pytest.fixture() def price() -> Quotation: return Quotation(units=6, nano=500000000) @pytest.fixture() def direction() -> OrderDirection: return OrderDirection.ORDER_DIRECTION_BUY @pytest.fixture() def stop_order_direction() -> StopOrderDirection: return StopOrderDirection.STOP_ORDER_DIRECTION_BUY @pytest.fixture() def stop_order_status() -> StopOrderStatusOption: return StopOrderStatusOption.STOP_ORDER_STATUS_ACTIVE @pytest.fixture() def order_type() -> OrderType: return OrderType.ORDER_TYPE_LIMIT @pytest.fixture() def order_id() -> str: return "" @pytest.fixture() def async_order_id() -> str: return str(uuid.uuid4()) @pytest.fixture() def order(instrument_id, quantity, price, direction, account_id, order_type, order_id): return { "instrument_id": instrument_id, "quantity": quantity, "price": price, "direction": direction, "account_id": account_id, "order_type": order_type, "order_id": order_id, } @pytest.fixture() def async_order( instrument_id, quantity, price, direction, account_id, order_type, async_order_id ): return { "instrument_id": instrument_id, "quantity": quantity, "price": price, "direction": direction, "account_id": account_id, "order_type": order_type, "order_id": async_order_id, } skip_when_exchange_closed = skip_when( RequestError, lambda msg: "Instrument is not available for trading" in msg, reason="Skipping during closed exchange", ) @pytest.mark.skipif( os.environ.get("INVEST_SANDBOX_TOKEN") is None, reason="INVEST_SANDBOX_TOKEN should be specified", ) class TestSandboxOperations: def test_open_sandbox_account(self, sandbox_service): response = sandbox_service.sandbox.open_sandbox_account() assert isinstance(response.account_id, str) sandbox_service.sandbox.close_sandbox_account( account_id=response.account_id, ) def test_get_sandbox_accounts(self, sandbox_service, account_id): response = sandbox_service.users.get_accounts() assert isinstance(response.accounts, list) assert isinstance(response.accounts[0], Account) assert ( len( [ _account for _account in response.accounts if _account.id == account_id ] ) == 1 ) def test_close_sandbox_account(self, sandbox_service): response = sandbox_service.sandbox.open_sandbox_account() response = sandbox_service.sandbox.close_sandbox_account( account_id=response.account_id, ) assert isinstance(response, CloseSandboxAccountResponse) @skip_when_exchange_closed def test_post_sandbox_order( self, sandbox_service, order, instrument_id, direction, quantity ): response = sandbox_service.orders.post_order(**order) assert isinstance(response.order_id, str) assert response.instrument_uid == instrument_id assert response.direction == direction assert response.lots_requested == quantity @skip_when_exchange_closed def test_post_sandbox_order_async( self, sandbox_service, async_order, instrument_id, direction, quantity, async_order_id, ): request = PostOrderAsyncRequest(**async_order) response = sandbox_service.orders.post_order_async(request) assert isinstance(response.order_request_id, str) assert ( response.execution_report_status == OrderExecutionReportStatus.EXECUTION_REPORT_STATUS_NEW ) assert response.order_request_id == async_order_id @skip_when_exchange_closed def test_get_sandbox_orders(self, sandbox_service, order, account_id): response = sandbox_service.orders.post_order(**order) assert response @skip_when_exchange_closed @pytest.mark.skip(reason="Order executes faster than cancel") def test_cancel_sandbox_order(self, sandbox_service, order, account_id): response = sandbox_service.orders.post_order(**order) response = sandbox_service.orders.cancel_order( account_id=account_id, order_id=response.order_id, ) assert isinstance(response.time, datetime) @skip_when_exchange_closed def test_get_sandbox_order_state( self, sandbox_service, order, account_id, instrument_id, direction, quantity ): response = sandbox_service.orders.post_order(**order) response = sandbox_service.orders.get_order_state( account_id=account_id, order_id=response.order_id, ) assert response.instrument_uid == instrument_id assert response.direction == direction assert response.lots_requested == quantity @pytest.mark.parametrize("order_type", [OrderType.ORDER_TYPE_MARKET]) @skip_when_exchange_closed def test_get_sandbox_positions( self, sandbox_service, account_id, order, order_type ): _ = sandbox_service.orders.post_order(**order) response = sandbox_service.operations.get_positions(account_id=account_id) assert isinstance(response.money[0], MoneyValue) assert response.money[0].currency == "rub" def test_get_sandbox_operations(self, sandbox_service, account_id, order, figi): response = sandbox_service.operations.get_operations( account_id=account_id, from_=datetime(2000, 2, 2), to=datetime(2022, 2, 2), state=OperationState.OPERATION_STATE_EXECUTED, figi=figi, ) assert isinstance(response.operations, list) def test_get_sandbox_portfolio( self, sandbox_service, account_id, initial_balance_pay_in: MoneyValue ): response = sandbox_service.operations.get_portfolio( account_id=account_id, ) assert str(response.total_amount_bonds) == str( MoneyValue(currency="rub", units=0, nano=0) ) assert str(response.total_amount_currencies) == str( initial_balance_pay_in, ) assert str(response.total_amount_etf) == str( MoneyValue(currency="rub", units=0, nano=0) ) assert str(response.total_amount_futures) == str( MoneyValue(currency="rub", units=0, nano=0) ) assert str(response.total_amount_shares) == str( MoneyValue(currency="rub", units=0, nano=0) ) def test_sandbox_pay_in( self, sandbox_service, account_id, initial_balance_pay_in: MoneyValue ): amount = MoneyValue(currency="rub", units=1234, nano=0) response = sandbox_service.sandbox.sandbox_pay_in( account_id=account_id, amount=amount, ) assert money_to_decimal(response.balance) == ( money_to_decimal(initial_balance_pay_in) + money_to_decimal(amount) ) @skip_when_exchange_closed def test_sandbox_post_stop_order( self, sandbox_service, account_id, instrument_id, stop_order_direction, quantity, price, ): response = post_stop_order( sandbox_service, account_id, instrument_id, stop_order_direction, quantity, price, ) assert response.order_request_id is not None assert response.stop_order_id is not None def test_sandbox_get_stop_orders( self, sandbox_service, account_id, stop_order_status ): response = get_stop_orders(sandbox_service, account_id, stop_order_status) assert isinstance(response.stop_orders, list) def test_sandbox_cancel_stop_order(self, sandbox_service, account_id): stop_orders = get_stop_orders( sandbox_service, account_id, StopOrderStatusOption.STOP_ORDER_STATUS_ACTIVE ) if len(stop_orders.stop_orders) > 0: stop_order_id = stop_orders.stop_orders[0].stop_order_id response = cancel_stop_order(sandbox_service, account_id, stop_order_id) assert isinstance(response.time, datetime) def test_sandbox_get_order_prices(self, sandbox_service, account_id, instrument_id): response = get_order_price(sandbox_service, account_id, instrument_id, 100) assert isinstance(response, GetOrderPriceResponse) assert utils.money_to_decimal(response.total_order_amount) == Decimal(100)