SimpleChatbot / hermes_overlay /tests /test_domain_layer.py
Amin
Deploy: HermesFace finalized project to HF Space
2e658e7
Raw
History Blame Contribute Delete
6.85 kB
"""Domain layer tests: schema, idempotency, order state, execution service, risk, sizing."""
from __future__ import annotations
import asyncio
import os
from pathlib import Path
import pytest
from trading.domain.schema import apply_migrations, validate_schema
from trading.domain.idempotency import claim_idempotency, acquire_lock, release_lock, LockNotAcquired
from trading.domain.order_state import transition, can_transition, InvalidTransition, is_terminal
from trading.domain.orders_repo import create_order, advance_order, get_order
from trading.domain.risk_ledger import record_realized_pnl, get_daily_pnl, check_daily_loss_limit
from trading.domain.risk_limits import assert_entry_allowed, RiskRejected
from trading.domain.sizing import validate_geometry, base_to_contracts, SizingError
from trading.domain.approvals import create_approval, consume_approval, ApprovalError
from trading.domain.plans_repo import create_plan
from trading.domain.security_utils import public_error, safe_extract_member_path
from trading.adapters.exchange_port import MarketMeta
from trading.adapters.paper_adapter import PaperExchangeAdapter
from trading.adapters.exchange_port import OrderRequest
from trading.domain.execution_service import ExecutionService
from trading.plan_integrity import bind_plan_hash
from trading.futures_execution import TradingError
@pytest.fixture(autouse=True)
def _hermes_home(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("TRADING_MODE", "paper")
monkeypatch.setenv("HERMES_EXECUTION_ENABLED", "true")
ok, msgs = apply_migrations()
assert ok, msgs
ok, msgs = validate_schema()
assert ok, msgs
yield
def test_migrations_idempotent():
ok, _ = apply_migrations()
assert ok
def test_idempotency_claim():
is_new, _ = claim_idempotency("k1", "entry")
assert is_new is True
is_new2, _ = claim_idempotency("k1", "entry")
assert is_new2 is False
def test_execution_lock():
h = acquire_lock("L1", holder="a")
assert h == "a"
with pytest.raises(LockNotAcquired):
acquire_lock("L1", holder="b", ttl_s=30)
release_lock("L1", "a")
acquire_lock("L1", holder="b")
def test_order_state_machine():
assert can_transition("created", "submitted")
assert transition("submitted", "filled") == "filled"
with pytest.raises(InvalidTransition):
transition("closed", "filled")
assert is_terminal("closed")
def test_orders_repo_lifecycle():
oid = create_order(client_order_id="c1", symbol="BTC/USDT:USDT", side="long", size=1)
advance_order(oid, "submitted")
advance_order(oid, "filled")
o = get_order(oid)
assert o["state"] == "filled"
def test_risk_ledger():
record_realized_pnl(-50)
pnl, count = get_daily_pnl()
assert pnl == -50
assert count >= 1
assert check_daily_loss_limit(1000) is True
assert check_daily_loss_limit(10) is False
def test_risk_limits(monkeypatch):
monkeypatch.setenv("HERMES_EXECUTION_ENABLED", "true")
assert_entry_allowed(leverage=5, notional=100, open_positions=0)
with pytest.raises(RiskRejected):
assert_entry_allowed(leverage=100, notional=100, open_positions=0)
def test_sizing():
meta = MarketMeta("BTC/USDT:USDT", 3, 1, 0.001, 1.0, 50)
s = validate_geometry(amount=0.01, price=100.0, meta=meta, stop_loss=95, take_profit=110, side="long")
assert s.amount > 0
with pytest.raises(SizingError):
validate_geometry(amount=0.01, price=100.0, meta=meta, stop_loss=105, take_profit=110, side="long")
assert base_to_contracts(2.0, meta) == 2.0
def test_approval_single_use():
plan = create_plan({
"symbol": "BTCUSDT", "decision": "LONG", "entry": 100.0,
"stop_loss": 95.0, "take_profit": 110.0, "leverage": 2,
"quantity": 1.0, "risk_profile": "moderate", "futuresVerified": True,
"noTradeGuard": False, "trading_readiness": "ready", "executable": True,
"expires_at": "2099-01-01T00:00:00Z",
}, owner_id="owner1")
aid = create_approval(plan["plan_id"], plan["plan_hash"], "owner1")
consume_approval(aid, plan["plan_id"], plan["plan_hash"], "owner1")
with pytest.raises(ApprovalError):
consume_approval(aid, plan["plan_id"], plan["plan_hash"], "owner1")
def test_paper_adapter():
async def _run():
ad = PaperExchangeAdapter()
markets = await ad.load_markets()
assert "BTC/USDT:USDT" in markets
r = await ad.create_order(OrderRequest("BTC/USDT:USDT", "buy", 0.01, 100.0, "cid1"))
assert r.filled == 0.01
await ad.close()
asyncio.run(_run())
def test_execution_service_paper_entry(monkeypatch):
monkeypatch.setenv("HERMES_EXECUTION_ENABLED", "true")
async def _run():
svc = ExecutionService()
plan = bind_plan_hash({
"symbol": "BTCUSDT", "decision": "LONG", "entry": 100, "stop_loss": 95,
"take_profit": 110, "leverage": 5, "quantity": 1, "risk_profile": "moderate",
"futuresVerified": True, "noTradeGuard": False, "trading_readiness": "ready",
"executable": True, "expires_at": "2099-01-01T00:00:00Z",
})
result = await svc._submit_entry(
symbol="BTC/USDT:USDT", side="long", size=0.1, price=100.0,
plan_id="plan1", plan=plan, idempotency_key="unique-entry-1",
)
assert result["status"] == "protected"
assert len(result["protective_order_ids"]) == 2
# exact replay is a duplicate; changed request content must not be accepted.
dup = await svc._submit_entry(
symbol="BTC/USDT:USDT", side="long", size=0.1, price=100.0,
plan_id="plan1", plan=plan, idempotency_key="unique-entry-1",
)
assert dup["status"] == "duplicate"
asyncio.run(_run())
def test_execution_service_kill_switch(monkeypatch):
monkeypatch.delenv("HERMES_EXECUTION_ENABLED", raising=False)
async def _run():
svc = ExecutionService()
with pytest.raises(TradingError, match="kill switch"):
await svc._submit_entry(symbol="BTC/USDT:USDT", side="long", size=0.01)
asyncio.run(_run())
def test_security_utils(tmp_path):
assert "REDACTED" in public_error("api_key=supersecret123456")
with pytest.raises(ValueError):
safe_extract_member_path(tmp_path, "../etc/passwd")
p = safe_extract_member_path(tmp_path, "ok/file.txt")
assert str(p).startswith(str(tmp_path.resolve()))
def test_recovery_gate_clear():
from trading.domain.recovery import startup_recovery_check
ok, msgs = startup_recovery_check()
assert ok is True
def test_smoke_backtest():
from trading.strategy_backtest import run_smoke_backtest
r = run_smoke_backtest()
assert r.trades >= 0
assert "strategyName" in r.strategy