100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
from crypto_spot_bot.learning import TradeLearner
|
|
from crypto_spot_bot.models import Trade, utc_now
|
|
from crypto_spot_bot.storage import Storage
|
|
|
|
|
|
def test_trade_learner_penalizes_losing_symbol_pattern(make_settings, tmp_path) -> None:
|
|
settings = make_settings(tmp_path, learning_min_samples=2)
|
|
storage = Storage(settings.database_path)
|
|
for value in (-0.4, -0.2):
|
|
storage.insert_trade(
|
|
Trade(
|
|
id=None,
|
|
symbol="BTCUSDT",
|
|
side="SELL",
|
|
qty=1,
|
|
entry_price=100,
|
|
exit_price=99,
|
|
net_pnl=value,
|
|
reason="test",
|
|
entry_pattern="пробой вниз",
|
|
entry_confidence=0.7,
|
|
opened_at=utc_now(),
|
|
closed_at=utc_now(),
|
|
)
|
|
)
|
|
|
|
learner = TradeLearner(settings, storage)
|
|
learner.refresh()
|
|
adjustment = learner.adjustment_for("BTCUSDT", "пробой вниз")
|
|
|
|
assert adjustment.sample_size >= 4
|
|
assert adjustment.confidence_adjustment < 0
|
|
assert "убыточными" in adjustment.reason
|
|
|
|
|
|
def test_trade_learner_builds_adaptive_rules_for_losing_ema_exit(make_settings, tmp_path) -> None:
|
|
settings = make_settings(tmp_path, learning_min_samples=3)
|
|
storage = Storage(settings.database_path)
|
|
for value in (-0.05, -0.04, -0.03):
|
|
storage.insert_trade(
|
|
Trade(
|
|
id=None,
|
|
symbol="BTCUSDT",
|
|
side="SELL",
|
|
qty=1,
|
|
entry_price=100,
|
|
exit_price=99.9,
|
|
net_pnl=value,
|
|
reason="краткосрочный тренд ослаб ниже EMA50",
|
|
entry_pattern="нейтрально",
|
|
entry_confidence=0.7,
|
|
opened_at=utc_now(),
|
|
closed_at=utc_now(),
|
|
)
|
|
)
|
|
|
|
learner = TradeLearner(settings, storage)
|
|
state = learner.refresh()
|
|
rules = learner.rules_for("BTCUSDT", "нейтрально")
|
|
|
|
assert state.adaptive_rules["risk_mode"] == "defensive"
|
|
assert rules["ema_exit_mode"] == "profit_only"
|
|
assert rules["effective_entry_threshold_adjustment"] > 0
|
|
assert rules["min_hold_seconds"] > settings.min_hold_seconds
|
|
|
|
|
|
def test_trade_learner_enters_capital_protection_and_validates_rules(make_settings, tmp_path) -> None:
|
|
settings = make_settings(tmp_path, learning_min_samples=3, starting_balance_usdt=100, max_total_exposure_usdt=80)
|
|
storage = Storage(settings.database_path)
|
|
for value in (-0.12, -0.10, -0.08):
|
|
storage.insert_trade(
|
|
Trade(
|
|
id=None,
|
|
symbol="ETHUSDT",
|
|
side="SELL",
|
|
qty=1,
|
|
entry_price=100,
|
|
exit_price=99,
|
|
net_pnl=value,
|
|
reason="краткосрочный тренд ослаб ниже EMA50",
|
|
entry_pattern="нейтрально",
|
|
entry_confidence=0.7,
|
|
opened_at=utc_now(),
|
|
closed_at=utc_now(),
|
|
)
|
|
)
|
|
|
|
learner = TradeLearner(settings, storage)
|
|
state = learner.refresh()
|
|
rules = state.adaptive_rules
|
|
|
|
assert rules["trade_permission"] == "capital_protection"
|
|
assert rules["reduce_exposure"] is True
|
|
assert rules["bad_market_entry_block"] is True
|
|
assert rules["target_total_exposure_usdt"] == 35.0
|
|
assert rules["validation"]["status"] == "accepted"
|
|
assert rules["validation"]["avoided_loss_usdt"] > 0
|