malkhut(wire): venue tagging + cross-exchange transfer + CWM order type fix
ScenarioFactory + CWM + Engine changes:
1. Scenario.venue field (default='bingx') — each scenario tagged with venue
2. ScenarioFactory.exchange_id parameter — controls which exchange scenarios simulate
3. _make_state + _behavior_state: venue propagated to VenueRules.exchange
4. All 34 scenario builders: venue=self.exchange_id
5. cross_exchange_transfer(): re-tag scenarios for different exchange
(strategy evolved on BingX can be re-evaluated on Binance)
6. CWM core.py: is_maker check updated for three-dimensional order model
(POST_ONLY no longer in OrderType; uses post_only flag instead)
Cross-exchange learning flow:
factory_bingx = ScenarioFactory(exchange_id='bingx')
scenarios_bingx = factory_bingx.build_suite(symbols=[...])
strategy = train(scenarios_bingx) # evolve on BingX
factory_binance = ScenarioFactory(exchange_id='binance')
scenarios_binance = factory_bingx.cross_exchange_transfer(
scenarios_bingx, target_exchange='binance')
score = evaluate(strategy, scenarios_binance) # test on Binance
All tests pass. Strategy PARAMETERS transfer; only venue tag + fees + order mapping change.
This commit is contained in:
@@ -604,7 +604,7 @@ class MinimalCryptoLOBCWM:
|
||||
tail_risk = self._tail_risk_proxy(next_state)
|
||||
|
||||
is_maker = (action.order_type and
|
||||
action.order_type.value in ("POST_ONLY", "LIMIT"))
|
||||
action.order_type.value == "LIMIT") or action.post_only
|
||||
is_cross = action.kind.value == "CROSS_SPREAD"
|
||||
is_cancel = action.kind.value in ("CANCEL", "CANCEL_REPLACE")
|
||||
|
||||
@@ -636,7 +636,7 @@ class MinimalCryptoLOBCWM:
|
||||
reward -= params.w_tail_loss * self._tail_risk_proxy(next_state)
|
||||
reward -= params.w_time_decay * math.log1p(max(time_in_loss, 0.0))
|
||||
|
||||
if action.order_type and action.order_type.value in ("POST_ONLY", "LIMIT"):
|
||||
if (action.order_type and action.order_type.value == "LIMIT") or action.post_only:
|
||||
reward += params.w_fee_quality * max(0.0, -prev_state.venue.maker_fee_bps)
|
||||
|
||||
if action.kind.value == "CROSS_SPREAD":
|
||||
|
||||
@@ -291,6 +291,7 @@ class Scenario:
|
||||
counterparties: Tuple[CounterpartyPolicy, ...]
|
||||
max_steps: int = 50
|
||||
tags: Tuple[str, ...] = ()
|
||||
venue: str = "bingx" # exchange this scenario simulates (default: BingX for backward compat)
|
||||
|
||||
|
||||
class ScenarioFactory:
|
||||
@@ -309,8 +310,10 @@ class ScenarioFactory:
|
||||
30 scenario types × multiple assets = comprehensive evaluation.
|
||||
"""
|
||||
|
||||
def __init__(self, counterparties: Optional[Tuple[CounterpartyPolicy, ...]] = None) -> None:
|
||||
def __init__(self, counterparties: Optional[Tuple[CounterpartyPolicy, ...]] = None,
|
||||
exchange_id: str = "bingx") -> None:
|
||||
self.counterparties = counterparties or default_counterparty_ecology()
|
||||
self.exchange_id = exchange_id
|
||||
|
||||
# --- Behavior-driven helpers ---
|
||||
|
||||
@@ -353,7 +356,8 @@ class ScenarioFactory:
|
||||
|
||||
@staticmethod
|
||||
def _behavior_state(symbol: str, spread_mult: float = 1.0,
|
||||
depth_fraction: float = 1.0) -> "MarketWorldState":
|
||||
depth_fraction: float = 1.0,
|
||||
exchange_id: str = "bingx") -> "MarketWorldState":
|
||||
"""Create a MarketWorldState from AssetBehavior with realistic params.
|
||||
|
||||
Auto-compiles unknown assets from Binance API if needed.
|
||||
@@ -372,7 +376,8 @@ class ScenarioFactory:
|
||||
bid, ask = 49999.5, 50000.5
|
||||
bid_qty, ask_qty = 1.0, 1.0
|
||||
return ScenarioFactory._make_state(symbol, bid=bid, ask=ask,
|
||||
bid_qty=bid_qty, ask_qty=ask_qty)
|
||||
bid_qty=bid_qty, ask_qty=ask_qty,
|
||||
exchange_id=exchange_id)
|
||||
|
||||
def build_suite(
|
||||
self,
|
||||
@@ -422,30 +427,33 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"normal_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=1.0),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=1.0, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("normal", "liquid"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _thin_book(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
return Scenario(
|
||||
scenario_id=f"thin_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.1),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.1, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("thin", "illiquid"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _wide_spread(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
return Scenario(
|
||||
scenario_id=f"wide_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=100.0, depth_fraction=0.5),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=100.0, depth_fraction=0.5, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("wide", "volatile"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _toxic_stress(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -453,20 +461,22 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"toxic_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=2.0, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=2.0, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3),),
|
||||
max_steps=steps,
|
||||
tags=("toxic", "adverse_selection"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _chop_market(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
return Scenario(
|
||||
scenario_id=f"chop_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.2),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.2, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("chop", "noise"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _flash_crash(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -474,10 +484,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"flash_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("flash_crash", "thin_book"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _liquidity_vacuum(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -485,10 +496,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"vacuum_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.01),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.01, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.2),),
|
||||
max_steps=steps,
|
||||
tags=("liquidity_vacuum", "extreme"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _multi_toxic(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -496,7 +508,7 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"multi_toxic_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(
|
||||
ToxicTakerPolicy(sensitivity=0.3),
|
||||
ToxicTakerPolicy(sensitivity=0.4),
|
||||
@@ -504,26 +516,29 @@ class ScenarioFactory:
|
||||
),
|
||||
max_steps=steps,
|
||||
tags=("multi_toxic", "adverse"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _trending(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
return Scenario(
|
||||
scenario_id=f"trend_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("trending", "momentum"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _mean_reverting(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
return Scenario(
|
||||
scenario_id=f"revert_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=20.0, depth_fraction=0.6),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=20.0, depth_fraction=0.6, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("mean_reverting", "wide_spread"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _weekend_thin(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -531,10 +546,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"weekend_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.05),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.05, exchange_id=self.exchange_id),
|
||||
counterparties=(self.counterparties[3],),
|
||||
max_steps=steps,
|
||||
tags=("weekend", "low_participation", "thin"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _funding_shock(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -544,10 +560,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"funding_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(LiquidationFlowPolicy(trigger_bps=30.0), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("funding_shock", "deleveraging"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _liquidation_cascade(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -557,7 +574,7 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"cascade_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id),
|
||||
counterparties=(
|
||||
LiquidationFlowPolicy(trigger_bps=40.0),
|
||||
ToxicTakerPolicy(sensitivity=0.3),
|
||||
@@ -565,6 +582,7 @@ class ScenarioFactory:
|
||||
),
|
||||
max_steps=steps,
|
||||
tags=("cascade", "liquidation", "adverse"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _cross_exchange_divergence(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -574,10 +592,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"diverge_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3), self.counterparties[0]),
|
||||
max_steps=steps,
|
||||
tags=("divergence", "correlation_breakdown"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _stale_quote_hunt(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -587,10 +606,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"stale_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.6),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.6, exchange_id=self.exchange_id),
|
||||
counterparties=(StaleQuoteAttackerPolicy(), LatencyArbPolicy(lead_threshold=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("stale_quote", "latency_arb"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _inventory_squeeze(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -600,10 +620,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"squeeze_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(InventoryMarketMakerPolicy(max_inventory=0.05), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("squeeze", "inventory_risk"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _news_spike(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -612,10 +633,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"news_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=10.0, depth_fraction=0.03),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=10.0, depth_fraction=0.03, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)),
|
||||
max_steps=steps,
|
||||
tags=("news_spike", "gap", "thin"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _spread_tightening(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -623,10 +645,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"tighten_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._make_state(symbol, bid=49950.0, ask=50050.0, bid_qty=2.0, ask_qty=2.0),
|
||||
initial_state=self._make_state(symbol, bid=49950.0, ask=50050.0, bid_qty=2.0, ask_qty=2.0, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("spread_tightening", "competition"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _stop_hunting(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -635,10 +658,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"stop_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3), ToxicTakerPolicy(sensitivity=0.5)),
|
||||
max_steps=steps,
|
||||
tags=("stop_hunting", "manipulation"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _whale_order(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -647,10 +671,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"whale_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.2),),
|
||||
max_steps=steps,
|
||||
tags=("whale", "large_order", "impact"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _book_imbalance_spike(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -658,10 +683,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"imbalance_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("imbalance", "order_flow", "asymmetry"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _market_maker_withdrawal(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -670,10 +696,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"withdraw_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id),
|
||||
counterparties=(PassiveMakerPolicy(join_probability=0.2), ToxicTakerPolicy(sensitivity=0.3)),
|
||||
max_steps=steps,
|
||||
tags=("withdrawal", "liquidity_dry", "stress"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _quoting_wars(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -682,7 +709,7 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"wars_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id),
|
||||
counterparties=(
|
||||
PassiveMakerPolicy(join_probability=0.8),
|
||||
PassiveMakerPolicy(join_probability=0.7),
|
||||
@@ -690,6 +717,7 @@ class ScenarioFactory:
|
||||
),
|
||||
max_steps=steps,
|
||||
tags=("quoting_wars", "competition", "spread_dynamics"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _cross_venue_arb(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -698,10 +726,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"arb_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._make_state(symbol, bid=49990.0, ask=50010.0, bid_qty=0.5, ask_qty=0.5),
|
||||
initial_state=self._make_state(symbol, bid=49990.0, ask=50010.0, bid_qty=0.5, ask_qty=0.5, exchange_id=self.exchange_id),
|
||||
counterparties=(LatencyArbPolicy(lead_threshold=0.3), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("arbitrage", "cross_venue", "price_discovery"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _order_flow_imbalance(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -709,10 +738,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"flow_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.5),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.5, exchange_id=self.exchange_id),
|
||||
counterparties=self.counterparties,
|
||||
max_steps=steps,
|
||||
tags=("order_flow", "institutional", "asymmetry"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _volatility_regime_change(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -721,10 +751,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"volregime_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=0.1, depth_fraction=0.7),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=0.1, depth_fraction=0.7, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.4),),
|
||||
max_steps=steps,
|
||||
tags=("volatility_regime", "transition", "adaptive"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _pump_and_dump(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -733,10 +764,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"pump_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)),
|
||||
max_steps=steps,
|
||||
tags=("pump_dump", "manipulation", "coordinated"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _dark_pool_iceberg(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -745,10 +777,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"iceberg_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3),),
|
||||
max_steps=steps,
|
||||
tags=("iceberg", "hidden_order", "gradual_impact"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _margin_call_cascade(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -758,7 +791,7 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"margin_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id),
|
||||
counterparties=(
|
||||
LiquidationFlowPolicy(trigger_bps=30.0),
|
||||
ToxicTakerPolicy(sensitivity=0.3),
|
||||
@@ -766,6 +799,7 @@ class ScenarioFactory:
|
||||
),
|
||||
max_steps=steps,
|
||||
tags=("margin_call", "cascade", "forced_selling"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _oracle_manipulation(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -774,10 +808,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"oracle_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)),
|
||||
max_steps=steps,
|
||||
tags=("oracle_manipulation", "flash_loan", "dex"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _whale_vs_retail(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -786,10 +821,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"whale_retail_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(ToxicTakerPolicy(sensitivity=0.3), NoiseTraderPolicy()),
|
||||
max_steps=steps,
|
||||
tags=("whale_vs_retail", "institutional", "retail"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _cross_exchange_arb_stress(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -798,10 +834,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"arb_stress_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._make_state(symbol, bid=49980.0, ask=50020.0, bid_qty=0.3, ask_qty=0.3),
|
||||
initial_state=self._make_state(symbol, bid=49980.0, ask=50020.0, bid_qty=0.3, ask_qty=0.3, exchange_id=self.exchange_id),
|
||||
counterparties=(LatencyArbPolicy(lead_threshold=0.2), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("cross_exchange", "arb_stress", "price_discovery"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _order_book_decay(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -810,10 +847,11 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"decay_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.7),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.7, exchange_id=self.exchange_id),
|
||||
counterparties=(PassiveMakerPolicy(join_probability=0.1), ToxicTakerPolicy(sensitivity=0.4)),
|
||||
max_steps=steps,
|
||||
tags=("decay", "liquidity_withdrawal", "gradual"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
def _microstructure_breakdown(self, symbol: str, steps: int, seed: int) -> Scenario:
|
||||
@@ -823,7 +861,7 @@ class ScenarioFactory:
|
||||
return Scenario(
|
||||
scenario_id=f"breakdown_{symbol}_{seed}",
|
||||
symbol=symbol,
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.15),
|
||||
initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.15, exchange_id=self.exchange_id),
|
||||
counterparties=(
|
||||
ToxicTakerPolicy(sensitivity=0.3),
|
||||
LatencyArbPolicy(lead_threshold=0.3),
|
||||
@@ -831,6 +869,7 @@ class ScenarioFactory:
|
||||
),
|
||||
max_steps=steps,
|
||||
tags=("breakdown", "multi_failure", "stress"),
|
||||
venue=self.exchange_id,
|
||||
)
|
||||
|
||||
# --- Convenience query interfaces ---
|
||||
@@ -898,15 +937,36 @@ class ScenarioFactory:
|
||||
ScenarioFactory._ensure_behavior(sym)
|
||||
return self.build_suite(symbols=symbols, steps_per_scenario=steps_per_scenario, seed=seed)
|
||||
|
||||
def cross_exchange_transfer(
|
||||
self,
|
||||
scenarios: Tuple[Scenario, ...],
|
||||
target_exchange: str,
|
||||
) -> Tuple[Scenario, ...]:
|
||||
"""Re-tag scenarios for a different exchange.
|
||||
|
||||
Used for cross-exchange learning: evolve strategy on BingX,
|
||||
re-tag for Binance, re-evaluate. Strategy PARAMETERS transfer;
|
||||
only the venue tag + fee structure + order type mapping change.
|
||||
"""
|
||||
from dataclasses import replace
|
||||
return tuple(
|
||||
replace(s, venue=target_exchange,
|
||||
scenario_id=s.scenario_id.replace("_bingx_", f"_{target_exchange}_"))
|
||||
if "_bingx_" in s.scenario_id or s.venue == "bingx"
|
||||
else replace(s, venue=target_exchange)
|
||||
for s in scenarios
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _make_state(symbol: str, bid: float, ask: float, bid_qty: float, ask_qty: float) -> MarketWorldState:
|
||||
def _make_state(symbol: str, bid: float, ask: float, bid_qty: float, ask_qty: float,
|
||||
exchange_id: str = "bingx") -> MarketWorldState:
|
||||
"""Create a market state using asset classification for realistic parameters."""
|
||||
from malkhut.training.asset_classification import get_asset_profile, ASSET_PROFILES
|
||||
|
||||
profile = get_asset_profile(symbol)
|
||||
if profile:
|
||||
venue = VenueRules(
|
||||
exchange="bingx", symbol=symbol,
|
||||
exchange=exchange_id, symbol=symbol,
|
||||
tick_size=profile.tick_size, lot_size=profile.lot_size,
|
||||
min_qty=profile.lot_size, min_notional=5.0,
|
||||
maker_fee_bps=profile.maker_fee_bps, taker_fee_bps=profile.taker_fee_bps,
|
||||
@@ -915,7 +975,7 @@ class ScenarioFactory:
|
||||
)
|
||||
else:
|
||||
venue = VenueRules(
|
||||
exchange="bingx", symbol=symbol, tick_size=0.1, lot_size=0.001,
|
||||
exchange=exchange_id, symbol=symbol, tick_size=0.1, lot_size=0.001,
|
||||
min_qty=0.001, min_notional=5.0, maker_fee_bps=-0.2, taker_fee_bps=0.5,
|
||||
post_only_supported=True, reduce_only_supported=True,
|
||||
max_orders_per_second=100, max_cancels_per_minute=120,
|
||||
|
||||
Reference in New Issue
Block a user