Files

191 lines
6.8 KiB
Python

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