From 33b14aaa2344b0fd95d1629627c3d135b24ae102 Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:56:35 +0900 Subject: feat: initial trading platform implementation Binance spot crypto trading platform with microservices architecture: - shared: Pydantic models, Redis Streams broker, asyncpg DB layer - data-collector: Binance WebSocket/REST market data collection - strategy-engine: Plugin-based strategy execution (RSI, Grid) - order-executor: Order execution with risk management - portfolio-manager: Position tracking and PnL calculation - backtester: Historical strategy testing with simulator - cli: Click-based CLI for all operations - Docker Compose orchestration with Redis and PostgreSQL - 24 test files covering all modules --- services/backtester/tests/test_engine.py | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 services/backtester/tests/test_engine.py (limited to 'services/backtester/tests/test_engine.py') diff --git a/services/backtester/tests/test_engine.py b/services/backtester/tests/test_engine.py new file mode 100644 index 0000000..1a25e1c --- /dev/null +++ b/services/backtester/tests/test_engine.py @@ -0,0 +1,74 @@ +"""Tests for the BacktestEngine.""" +from datetime import datetime, timezone +from decimal import Decimal +from unittest.mock import MagicMock + +import pytest + +from shared.models import Candle, Signal, OrderSide + +from backtester.engine import BacktestEngine, BacktestResult + + +def make_candle(symbol: str, price: float, timeframe: str = "1h") -> Candle: + return Candle( + symbol=symbol, + timeframe=timeframe, + open_time=datetime.now(timezone.utc), + open=Decimal(str(price)), + high=Decimal(str(price * 1.01)), + low=Decimal(str(price * 0.99)), + close=Decimal(str(price)), + volume=Decimal("100"), + ) + + +def make_candles(prices: list[float], symbol: str = "BTCUSDT") -> list[Candle]: + return [make_candle(symbol, p) for p in prices] + + +def make_signal(side: OrderSide, price: str, quantity: str = "0.1") -> Signal: + return Signal( + strategy="test", + symbol="BTCUSDT", + side=side, + price=Decimal(price), + quantity=Decimal(quantity), + reason="test", + ) + + +def test_backtest_engine_runs_strategy_over_candles(): + strategy = MagicMock() + strategy.name = "mock_strategy" + strategy.on_candle.return_value = None + + candles = make_candles([50000.0, 51000.0, 52000.0]) + engine = BacktestEngine(strategy, Decimal("10000")) + result = engine.run(candles) + + assert strategy.on_candle.call_count == 3 + assert result.total_trades == 0 + assert result.final_balance == Decimal("10000") + assert result.strategy_name == "mock_strategy" + + +def test_backtest_engine_executes_signals(): + buy_signal = make_signal(OrderSide.BUY, "50000", "0.1") + sell_signal = make_signal(OrderSide.SELL, "55000", "0.1") + + strategy = MagicMock() + strategy.name = "mock_strategy" + strategy.on_candle.side_effect = [buy_signal, None, sell_signal] + + candles = make_candles([50000.0, 52000.0, 55000.0]) + engine = BacktestEngine(strategy, Decimal("10000")) + result = engine.run(candles) + + assert result.total_trades == 2 + # Initial: 10000, bought 0.1 BTC @ 50000 (cost 5000) → balance 5000 + # Sold 0.1 BTC @ 55000 (proceeds 5500) → balance 10500 + expected_final = Decimal("10500") + assert result.final_balance == expected_final + expected_profit = Decimal("500") + assert result.profit == expected_profit -- cgit v1.2.3