191 lines
6.8 KiB
Python
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"
|