deploy: 2026-03-20 07:49
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
60
tests/test_ict_engine.py
Normal file
60
tests/test_ict_engine.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Tests for the ICT indicator engine."""
|
||||
|
||||
import pytest
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
from indicators.ict_engine import ICTEngine, ICTSignals
|
||||
|
||||
|
||||
def generate_sample_ohlc(n: int = 200) -> pd.DataFrame:
|
||||
"""Generate synthetic OHLC data for testing."""
|
||||
np.random.seed(42)
|
||||
dates = pd.date_range("2025-01-01", periods=n, freq="1h")
|
||||
close = 50000 + np.cumsum(np.random.randn(n) * 100)
|
||||
high = close + np.abs(np.random.randn(n) * 50)
|
||||
low = close - np.abs(np.random.randn(n) * 50)
|
||||
open_ = close + np.random.randn(n) * 30
|
||||
volume = np.random.randint(100, 10000, n).astype(float)
|
||||
|
||||
return pd.DataFrame(
|
||||
{"open": open_, "high": high, "low": low, "close": close, "volume": volume},
|
||||
index=dates,
|
||||
)
|
||||
|
||||
|
||||
class TestICTEngine:
|
||||
"""Tests for ICTEngine."""
|
||||
|
||||
def test_init_default_swing_length(self):
|
||||
engine = ICTEngine()
|
||||
assert engine.swing_length == 50
|
||||
|
||||
def test_init_custom_swing_length(self):
|
||||
engine = ICTEngine(swing_length=30)
|
||||
assert engine.swing_length == 30
|
||||
|
||||
def test_analyze_returns_ict_signals(self):
|
||||
engine = ICTEngine(swing_length=10)
|
||||
df = generate_sample_ohlc(100)
|
||||
try:
|
||||
result = engine.analyze(df)
|
||||
assert isinstance(result, ICTSignals)
|
||||
assert isinstance(result.swing_highs_lows, pd.DataFrame)
|
||||
assert isinstance(result.fvg, pd.DataFrame)
|
||||
assert isinstance(result.bos_choch, pd.DataFrame)
|
||||
assert isinstance(result.order_blocks, pd.DataFrame)
|
||||
except ImportError:
|
||||
pytest.skip("smartmoneyconcepts not installed")
|
||||
|
||||
def test_analyze_too_few_candles(self):
|
||||
engine = ICTEngine(swing_length=50)
|
||||
df = generate_sample_ohlc(10)
|
||||
with pytest.raises(ValueError, match="Need at least"):
|
||||
engine.analyze(df)
|
||||
|
||||
def test_analyze_empty_dataframe(self):
|
||||
engine = ICTEngine(swing_length=10)
|
||||
df = pd.DataFrame()
|
||||
with pytest.raises(ValueError):
|
||||
engine.analyze(df)
|
||||
37
tests/test_order_manager.py
Normal file
37
tests/test_order_manager.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Tests for the order manager."""
|
||||
|
||||
import pytest
|
||||
|
||||
from execution.order_manager import Order, OrderStatus
|
||||
|
||||
|
||||
class TestOrder:
|
||||
"""Tests for Order dataclass."""
|
||||
|
||||
def test_order_defaults(self):
|
||||
order = Order(
|
||||
id="test-001",
|
||||
symbol="BTC/USDT",
|
||||
side="buy",
|
||||
order_type="market",
|
||||
amount=0.01,
|
||||
)
|
||||
assert order.status == OrderStatus.PENDING
|
||||
assert order.filled_amount == 0.0
|
||||
assert order.fee == 0.0
|
||||
assert order.exchange_order_id is None
|
||||
|
||||
def test_order_status_transitions(self):
|
||||
order = Order(
|
||||
id="test-002",
|
||||
symbol="ETH/USDT",
|
||||
side="sell",
|
||||
order_type="limit",
|
||||
amount=0.5,
|
||||
price=3000.0,
|
||||
)
|
||||
order.status = OrderStatus.FILLED
|
||||
assert order.status == OrderStatus.FILLED
|
||||
|
||||
order.status = OrderStatus.CANCELLED
|
||||
assert order.status == OrderStatus.CANCELLED
|
||||
70
tests/test_risk_manager.py
Normal file
70
tests/test_risk_manager.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Tests for the risk management engine."""
|
||||
|
||||
import pytest
|
||||
from risk.risk_manager import RiskManager
|
||||
|
||||
|
||||
class TestRiskManager:
|
||||
"""Tests for RiskManager."""
|
||||
|
||||
def setup_method(self):
|
||||
self.rm = RiskManager(
|
||||
max_risk_per_trade=0.02,
|
||||
max_daily_loss=0.05,
|
||||
max_concurrent_positions=3,
|
||||
max_leverage=3,
|
||||
max_drawdown=0.15,
|
||||
)
|
||||
|
||||
def test_calculate_position_size(self):
|
||||
size = self.rm.calculate_position_size(
|
||||
balance=1000, entry_price=50000, stop_loss=49000, risk_pct=0.02
|
||||
)
|
||||
# risk = 1000 * 0.02 = 20, price_risk = 1000
|
||||
# size = 20 / 1000 = 0.02
|
||||
assert size == 0.02
|
||||
|
||||
def test_position_size_zero_risk(self):
|
||||
size = self.rm.calculate_position_size(
|
||||
balance=1000, entry_price=50000, stop_loss=50000
|
||||
)
|
||||
assert size == 0.0
|
||||
|
||||
def test_approve_trade_success(self):
|
||||
approval = self.rm.approve_trade(
|
||||
entry_price=50000, stop_loss=49000, balance=1000
|
||||
)
|
||||
assert approval.approved
|
||||
assert approval.position_size > 0
|
||||
|
||||
def test_approve_trade_max_positions(self):
|
||||
for _ in range(3):
|
||||
self.rm.on_position_opened()
|
||||
approval = self.rm.approve_trade(
|
||||
entry_price=50000, stop_loss=49000, balance=1000
|
||||
)
|
||||
assert not approval.approved
|
||||
assert "concurrent" in approval.reason.lower()
|
||||
|
||||
def test_emergency_stop(self):
|
||||
self.rm.emergency_stop()
|
||||
assert self.rm.is_stopped
|
||||
approval = self.rm.approve_trade(
|
||||
entry_price=50000, stop_loss=49000, balance=1000
|
||||
)
|
||||
assert not approval.approved
|
||||
|
||||
def test_reset_emergency(self):
|
||||
self.rm.emergency_stop()
|
||||
self.rm.reset_emergency()
|
||||
assert not self.rm.is_stopped
|
||||
|
||||
def test_daily_pnl_tracking(self):
|
||||
self.rm.update_daily_pnl(50)
|
||||
self.rm.update_daily_pnl(-30)
|
||||
assert self.rm.get_daily_pnl() == 20
|
||||
|
||||
def test_drawdown_check(self):
|
||||
self.rm._peak_equity = 1000
|
||||
assert not self.rm.check_drawdown(900) # 10% < 15%
|
||||
assert self.rm.check_drawdown(800) # 20% >= 15%
|
||||
52
tests/test_signal_generator.py
Normal file
52
tests/test_signal_generator.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Tests for the signal generator module."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
|
||||
from indicators.multi_timeframe import TradeDirection
|
||||
from strategy.signal_generator import TradeSignal
|
||||
|
||||
|
||||
class TestTradeSignal:
|
||||
"""Tests for TradeSignal dataclass."""
|
||||
|
||||
def test_risk_reward_ratio(self):
|
||||
signal = TradeSignal(
|
||||
symbol="BTC/USDT",
|
||||
direction=TradeDirection.LONG,
|
||||
entry_price=50000,
|
||||
stop_loss=49000,
|
||||
take_profit=52000,
|
||||
confidence=4,
|
||||
timeframe="15m",
|
||||
)
|
||||
assert signal.risk_reward_ratio == 2.0
|
||||
|
||||
def test_risk_reward_zero_risk(self):
|
||||
signal = TradeSignal(
|
||||
symbol="BTC/USDT",
|
||||
direction=TradeDirection.LONG,
|
||||
entry_price=50000,
|
||||
stop_loss=50000,
|
||||
take_profit=52000,
|
||||
confidence=3,
|
||||
timeframe="15m",
|
||||
)
|
||||
assert signal.risk_reward_ratio == 0.0
|
||||
|
||||
def test_to_dict(self):
|
||||
signal = TradeSignal(
|
||||
symbol="ETH/USDT",
|
||||
direction=TradeDirection.SHORT,
|
||||
entry_price=3000,
|
||||
stop_loss=3100,
|
||||
take_profit=2800,
|
||||
confidence=5,
|
||||
timeframe="1h",
|
||||
reasons=["BOS", "OB", "FVG"],
|
||||
)
|
||||
d = signal.to_dict()
|
||||
assert d["symbol"] == "ETH/USDT"
|
||||
assert d["direction"] == "SHORT"
|
||||
assert d["confidence"] == 5
|
||||
assert len(d["reasons"]) == 3
|
||||
Reference in New Issue
Block a user