"""Tests for account, positions, and orders.""" import responses import pytest import mt5bridge_ccxt class TestFetchBalance: @responses.activate def test_returns_ccxt_balance(self, exchange_config, mock_account): responses.add( responses.GET, "http://mock-mt5bridge:8080/account", json=mock_account, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) bal = ex.fetch_balance() assert "info" in bal assert bal["free"]["USD"] == 9600.0 assert bal["used"]["USD"] == 500.0 assert bal["total"]["USD"] == 10000.0 assert "debt" in bal assert bal["timestamp"] > 0 class TestFetchPositions: @responses.activate def test_returns_long_position(self, exchange_config, mock_positions): responses.add( responses.GET, "http://mock-mt5bridge:8080/positions", json=mock_positions, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) positions = ex.fetch_positions() assert len(positions) == 1 p = positions[0] assert p["symbol"] == "XAU/USD" assert p["side"] == "long" assert p["contracts"] == 0.01 assert p["entryPrice"] == 4180.0 assert p["markPrice"] == 4185.0 assert p["unrealizedPnl"] == 0.5 assert p["stopLossPrice"] == 4170.0 assert p["takeProfitPrice"] == 4190.0 @responses.activate def test_filter_by_symbol(self, exchange_config, mock_positions): responses.add( responses.GET, "http://mock-mt5bridge:8080/positions", json=mock_positions, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) # Filter to a symbol we don't have positions = ex.fetch_positions(["BTC/USD"]) assert len(positions) == 0 @responses.activate def test_short_position(self, exchange_config): responses.add( responses.GET, "http://mock-mt5bridge:8080/positions", json={"data": [{ "ticket": 2002, "symbol": "XAUUSDc", "type": 1, "volume": 0.02, "price_open": 4200.0, "sl": 0, "tp": 0, "price_current": 4195.0, "swap": 0.0, "profit": 1.0, "comment": "", "magic": 0, }], "count": 1, "format": "json"}, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) positions = ex.fetch_positions() assert positions[0]["side"] == "short" class TestCreateOrder: @responses.activate def test_market_buy_succeeds(self, exchange_config, mock_order_result): responses.add( responses.POST, "http://mock-mt5bridge:8080/order/send", json=mock_order_result, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) order = ex.create_order("XAU/USD", "market", "buy", 0.01) assert order["id"] == "2001" assert order["symbol"] == "XAU/USD" assert order["side"] == "buy" assert order["type"] == "market" assert order["amount"] == 0.01 assert order["status"] == "open" # Verify request body call = responses.calls[0] import json body = json.loads(call.request.body) assert body["request"]["symbol"] == "XAUUSDc" assert body["request"]["volume"] == 0.01 assert body["request"]["order_type"] == 0 # market buy assert body["request"]["action"] == 1 @responses.activate def test_limit_order_requires_price(self, exchange_config): ex = mt5bridge_ccxt.mt5bridge(exchange_config) from ccxt.base.errors import ArgumentsRequired with pytest.raises(ArgumentsRequired): ex.create_order("XAU/USD", "limit", "buy", 0.01) @responses.activate def test_rejected_order_raises(self, exchange_config): responses.add( responses.POST, "http://mock-mt5bridge:8080/order/send", json={"data": {"retcode": 10019, "order": 0, "comment": "No money"}, "count": 1, "format": "json"}, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) from ccxt.base.errors import InvalidOrder with pytest.raises(InvalidOrder): ex.create_order("XAU/USD", "market", "buy", 0.01) @responses.activate def test_insufficient_funds_raises(self, exchange_config): responses.add( responses.POST, "http://mock-mt5bridge:8080/order/send", json={"data": {"retcode": 10019, "order": 0, "comment": "Not enough money"}, "count": 1, "format": "json"}, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) from ccxt.base.errors import InsufficientFunds with pytest.raises(InsufficientFunds): ex.create_order("XAU/USD", "market", "buy", 0.01) @responses.activate def test_invalid_order_type(self, exchange_config): ex = mt5bridge_ccxt.mt5bridge(exchange_config) from ccxt.base.errors import InvalidOrder with pytest.raises(InvalidOrder): ex.create_order("XAU/USD", "weird_type", "buy", 0.01) @responses.activate def test_check_first_option(self, exchange_config, mock_order_result): responses.add( responses.POST, "http://mock-mt5bridge:8080/order/check", json={"data": {"retcode": 0, "balance": 10000, "equity": 10100, "profit": 0, "margin": 0, "margin_free": 10100, "margin_level": 0, "comment": "OK"}, "count": 1, "format": "json"}, status=200, ) responses.add( responses.POST, "http://mock-mt5bridge:8080/order/send", json=mock_order_result, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) order = ex.create_order("XAU/USD", "market", "buy", 0.01, params={"check_first": True}) assert order["status"] == "open" class TestCancelOrder: @responses.activate def test_cancel_sends_gvar(self, exchange_config): responses.add( responses.POST, "http://mock-mt5bridge:8080/gvar/MT5BRIDGE_CMD_CANCEL_12345", json={"name": "MT5BRIDGE_CMD_CANCEL_12345", "value": 0.0}, status=200, ) responses.add( responses.POST, "http://mock-mt5bridge:8080/gvar/MT5BRIDGE_LAST_CANCEL", json={"name": "MT5BRIDGE_LAST_CANCEL", "value": 12345.0}, status=200, ) ex = mt5bridge_ccxt.mt5bridge(exchange_config) result = ex.cancel_order("12345") assert result["id"] == "12345" assert result["status"] == "canceling" assert result["info"]["method"] == "gvar_signal"