summaryrefslogtreecommitdiff
path: root/services/order-executor/tests
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:44:43 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:44:43 +0900
commitb9d21e2e2f7ae096c2f8a01bb142a685683b5b90 (patch)
treea031989228ded9ff1e6d47840124ea5dcc9a9a3c /services/order-executor/tests
parentbb2e387f870495703fd663ca8f525028c3a8ced5 (diff)
feat: add market sentiment filters (Fear & Greed, CryptoPanic, CryptoQuant)
- SentimentProvider: fetches Fear & Greed Index (free, no key), CryptoPanic news sentiment (free key), CryptoQuant exchange netflow (free key) - SentimentData: aggregated should_buy/should_block logic - Fear < 30 = buy opportunity, Greed > 80 = block buying - Negative news < -0.5 = block buying - Exchange outflow = bullish, inflow = bearish - Integrated into Asian Session RSI strategy as entry filter - All providers optional — disabled when API key missing - 14 sentiment tests + 386 total tests passing
Diffstat (limited to 'services/order-executor/tests')
-rw-r--r--services/order-executor/tests/test_risk_manager.py108
1 files changed, 94 insertions, 14 deletions
diff --git a/services/order-executor/tests/test_risk_manager.py b/services/order-executor/tests/test_risk_manager.py
index a8fe37f..00a9ab4 100644
--- a/services/order-executor/tests/test_risk_manager.py
+++ b/services/order-executor/tests/test_risk_manager.py
@@ -204,21 +204,49 @@ def test_position_size_without_scaling():
def test_portfolio_exposure_check_passes():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_portfolio_exposure=0.8)
- positions = {"BTCUSDT": Position(symbol="BTCUSDT", quantity=Decimal("0.01"), avg_entry_price=Decimal("50000"), current_price=Decimal("50000"))}
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ max_portfolio_exposure=0.8,
+ )
+ positions = {
+ "BTCUSDT": Position(
+ symbol="BTCUSDT",
+ quantity=Decimal("0.01"),
+ avg_entry_price=Decimal("50000"),
+ current_price=Decimal("50000"),
+ )
+ }
result = rm.check_portfolio_exposure(positions, Decimal("10000"))
assert result.allowed # 500/10000 = 5% < 80%
def test_portfolio_exposure_check_rejects():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_portfolio_exposure=0.3)
- positions = {"BTCUSDT": Position(symbol="BTCUSDT", quantity=Decimal("1"), avg_entry_price=Decimal("50000"), current_price=Decimal("50000"))}
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ max_portfolio_exposure=0.3,
+ )
+ positions = {
+ "BTCUSDT": Position(
+ symbol="BTCUSDT",
+ quantity=Decimal("1"),
+ avg_entry_price=Decimal("50000"),
+ current_price=Decimal("50000"),
+ )
+ }
result = rm.check_portfolio_exposure(positions, Decimal("10000"))
assert not result.allowed # 50000/10000 = 500% > 30%
def test_correlation_calculation():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"))
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ )
# Feed identical price histories — correlation should be ~1.0
for i in range(20):
rm.update_price("A", Decimal(str(100 + i)))
@@ -229,10 +257,21 @@ def test_correlation_calculation():
def test_var_calculation():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"))
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ )
for i in range(30):
rm.update_price("BTCUSDT", Decimal(str(100 + (i % 5) - 2)))
- positions = {"BTCUSDT": Position(symbol="BTCUSDT", quantity=Decimal("1"), avg_entry_price=Decimal("100"), current_price=Decimal("100"))}
+ positions = {
+ "BTCUSDT": Position(
+ symbol="BTCUSDT",
+ quantity=Decimal("1"),
+ avg_entry_price=Decimal("100"),
+ current_price=Decimal("100"),
+ )
+ }
var = rm.calculate_portfolio_var(positions, Decimal("10000"))
assert var >= 0 # Non-negative
@@ -241,28 +280,52 @@ def test_var_calculation():
def test_drawdown_position_scale_full():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ drawdown_reduction_threshold=0.1,
+ drawdown_halt_threshold=0.2,
+ )
rm.update_balance(Decimal("10000"))
scale = rm.get_position_scale(Decimal("10000"))
assert scale == 1.0 # No drawdown
def test_drawdown_position_scale_reduced():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ drawdown_reduction_threshold=0.1,
+ drawdown_halt_threshold=0.2,
+ )
rm.update_balance(Decimal("10000"))
scale = rm.get_position_scale(Decimal("8500")) # 15% drawdown (between 10% and 20%)
assert 0.25 < scale < 1.0
def test_drawdown_halt():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ drawdown_reduction_threshold=0.1,
+ drawdown_halt_threshold=0.2,
+ )
rm.update_balance(Decimal("10000"))
scale = rm.get_position_scale(Decimal("7500")) # 25% drawdown
assert scale == 0.0
def test_consecutive_losses_pause():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_consecutive_losses=3, loss_pause_minutes=60)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ max_consecutive_losses=3,
+ loss_pause_minutes=60,
+ )
rm.record_trade_result(False)
rm.record_trade_result(False)
assert not rm.is_paused()
@@ -271,7 +334,12 @@ def test_consecutive_losses_pause():
def test_consecutive_losses_reset_on_win():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_consecutive_losses=3)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ max_consecutive_losses=3,
+ )
rm.record_trade_result(False)
rm.record_trade_result(False)
rm.record_trade_result(True) # Win resets counter
@@ -280,9 +348,21 @@ def test_consecutive_losses_reset_on_win():
def test_drawdown_check_rejects_in_check():
- rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_halt_threshold=0.15)
+ rm = RiskManager(
+ max_position_size=Decimal("0.5"),
+ stop_loss_pct=Decimal("5"),
+ daily_loss_limit_pct=Decimal("10"),
+ drawdown_halt_threshold=0.15,
+ )
rm.update_balance(Decimal("10000"))
- signal = Signal(strategy="test", symbol="BTC/USDT", side=OrderSide.BUY, price=Decimal("50000"), quantity=Decimal("0.01"), reason="test")
+ signal = Signal(
+ strategy="test",
+ symbol="BTC/USDT",
+ side=OrderSide.BUY,
+ price=Decimal("50000"),
+ quantity=Decimal("0.01"),
+ reason="test",
+ )
result = rm.check(signal, Decimal("8000"), {}, Decimal("0")) # 20% dd > 15%
assert not result.allowed
assert "halted" in result.reason.lower()