Shift adaptive learning to growth sizing
This commit is contained in:
@@ -38,6 +38,7 @@ def make_settings():
|
||||
learning_lookback_trades=120,
|
||||
learning_min_samples=3,
|
||||
learning_max_adjustment=0.12,
|
||||
learning_max_position_multiplier=1.6,
|
||||
llm_advisor_enabled=False,
|
||||
ollama_base_url="http://192.168.0.210:11434",
|
||||
ollama_model="gemma4:e4b",
|
||||
|
||||
+46
-9
@@ -60,16 +60,18 @@ def test_trade_learner_builds_adaptive_rules_for_losing_ema_exit(make_settings,
|
||||
state = learner.refresh()
|
||||
rules = learner.rules_for("BTCUSDT", "нейтрально")
|
||||
|
||||
assert state.adaptive_rules["risk_mode"] == "defensive"
|
||||
assert state.adaptive_rules["risk_mode"] == "selective"
|
||||
assert state.adaptive_rules["trade_permission"] == "selective_growth"
|
||||
assert state.adaptive_rules["reduce_exposure"] is False
|
||||
assert rules["ema_exit_mode"] == "profit_only"
|
||||
assert rules["effective_entry_threshold_adjustment"] > 0
|
||||
assert rules["min_hold_seconds"] > settings.min_hold_seconds
|
||||
assert rules["min_hold_seconds"] == settings.min_hold_seconds
|
||||
|
||||
|
||||
def test_trade_learner_enters_capital_protection_and_validates_rules(make_settings, tmp_path) -> None:
|
||||
def test_trade_learner_blocks_only_bad_symbol_pattern(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):
|
||||
for _ in range(9):
|
||||
storage.insert_trade(
|
||||
Trade(
|
||||
id=None,
|
||||
@@ -78,7 +80,7 @@ def test_trade_learner_enters_capital_protection_and_validates_rules(make_settin
|
||||
qty=1,
|
||||
entry_price=100,
|
||||
exit_price=99,
|
||||
net_pnl=value,
|
||||
net_pnl=-0.10,
|
||||
reason="краткосрочный тренд ослаб ниже EMA50",
|
||||
entry_pattern="нейтрально",
|
||||
entry_confidence=0.7,
|
||||
@@ -91,9 +93,44 @@ def test_trade_learner_enters_capital_protection_and_validates_rules(make_settin
|
||||
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["trade_permission"] == "selective_growth"
|
||||
assert rules["reduce_exposure"] is False
|
||||
assert rules["bad_market_entry_block"] is False
|
||||
assert rules["target_total_exposure_usdt"] == settings.max_total_exposure_usdt
|
||||
assert rules["blocked_symbols"] == ["ETHUSDT"]
|
||||
assert rules["blocked_patterns"] == ["нейтрально"]
|
||||
assert rules["validation"]["status"] == "accepted"
|
||||
assert rules["validation"]["avoided_loss_usdt"] > 0
|
||||
|
||||
|
||||
def test_trade_learner_scales_positive_expectancy_setups(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, learning_min_samples=3, learning_max_position_multiplier=1.5)
|
||||
storage = Storage(settings.database_path)
|
||||
for _ in range(4):
|
||||
storage.insert_trade(
|
||||
Trade(
|
||||
id=None,
|
||||
symbol="SOLUSDT",
|
||||
side="SELL",
|
||||
qty=1,
|
||||
entry_price=100,
|
||||
exit_price=101,
|
||||
net_pnl=0.08,
|
||||
reason="test",
|
||||
entry_pattern="пробой вверх",
|
||||
entry_confidence=0.8,
|
||||
opened_at=utc_now(),
|
||||
closed_at=utc_now(),
|
||||
)
|
||||
)
|
||||
|
||||
learner = TradeLearner(settings, storage)
|
||||
state = learner.refresh()
|
||||
rules = learner.rules_for("SOLUSDT", "пробой вверх")
|
||||
|
||||
assert state.adaptive_rules["trade_permission"] == "positive_expectancy"
|
||||
assert state.adaptive_rules["risk_mode"] == "growth"
|
||||
assert rules["effective_entry_threshold_adjustment"] < 0
|
||||
assert rules["symbol_position_size_multiplier"] > 1.0
|
||||
assert rules["pattern_position_size_multiplier"] > 1.0
|
||||
assert 1.0 < rules["effective_position_size_multiplier"] <= settings.learning_max_position_multiplier
|
||||
|
||||
@@ -229,6 +229,40 @@ def test_strategy_uses_fractional_kelly_position_size(make_settings, tmp_path) -
|
||||
assert signal.diagnostics["position_notional_usdt"] == settings.max_position_usdt
|
||||
|
||||
|
||||
def test_strategy_scales_kelly_with_positive_learning_multiplier(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, max_position_usdt=50, kelly_fraction=0.25, kelly_max_fraction=0.20)
|
||||
strategy = SpotStrategy(settings)
|
||||
ticker = Ticker(
|
||||
symbol="BTCUSDT",
|
||||
last_price=101,
|
||||
bid=100.99,
|
||||
ask=101.01,
|
||||
turnover_24h=10_000_000,
|
||||
volume_24h=1000,
|
||||
change_24h=1.0,
|
||||
)
|
||||
common = dict(
|
||||
symbol="BTCUSDT",
|
||||
candles=_ready_candles(),
|
||||
ticker=ticker,
|
||||
open_positions_for_symbol=0,
|
||||
forecast={"usable": True, "probability_up": 0.62, "volatility_percent": 0.2},
|
||||
account={"equity": 200.0},
|
||||
)
|
||||
|
||||
neutral = strategy.entry_signal(**common)
|
||||
scaled = strategy.entry_signal(
|
||||
**common,
|
||||
learning={"adaptive_rules": {"effective_position_size_multiplier": 1.5}},
|
||||
)
|
||||
|
||||
assert neutral.action == "BUY"
|
||||
assert scaled.action == "BUY"
|
||||
assert scaled.diagnostics["position_sizing"]["risk_multiplier"] > neutral.diagnostics["position_sizing"]["risk_multiplier"]
|
||||
assert scaled.diagnostics["position_notional_usdt"] > neutral.diagnostics["position_notional_usdt"]
|
||||
assert scaled.diagnostics["position_notional_usdt"] <= settings.max_position_usdt
|
||||
|
||||
|
||||
def test_strategy_buys_probabilistic_rebound_after_stabilized_drop(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, rebound_entry_confidence=0.58, rebound_min_probability=0.58)
|
||||
strategy = SpotStrategy(settings)
|
||||
|
||||
Reference in New Issue
Block a user