malkhut: asset classification, behavior DSL, auto-compiler, behavior-driven scenarios

Cambrian Explosion Phase 0 complete:
- Multi-label invariant asset taxonomy (13 assets, 10 sectors, 6 roles)
- Asset Behavior DSL: 10 orthogonal dimensions per asset, 3 composable
  templates, research-validated from live Binance/BingX API data
- Asset Compiler: auto-fetch from Binance public API, compute profiles,
  rate-limited (1 req/s), cached, 28 known classifications
- ScenarioFactory: all 30 scenario types use behavior-driven prices
  (BTC=$64K, ETH=$1.8K, SOL=$80, DOGE=$0.07) instead of hardcoded
  BTC prices. Auto-compiles unknown assets on demand.
- Label query interfaces: build_suite_for_sector/role/template/vol/labels
- 190 exhaustive tests for asset classification (up from 66)
- 1140 tests all green, CWM throughput 189K calls/s (121% of baseline)
- Comprehensive README: 1043 lines with full documentation

Research sources: Binance live REST API, BingX open API, CoinGlass,
academic literature (Bouchaud, Cont/Stoikov, Cartea/Jaimungal)
This commit is contained in:
Codex
2026-07-11 06:37:17 +02:00
parent 3d313290b3
commit 981b469d51
6 changed files with 4823 additions and 0 deletions

View File

@@ -0,0 +1,998 @@
"""
Tests for asset classification — multi-label, invariant characteristics, exhaustive.
Covers: per-asset profiles (all 13), multi-label overlap, cross-dimensional
consistency, enum coverage, query edge cases, orderbook fingerprint ordering,
predictive properties, and ScenarioFactory integration.
"""
import pytest
from malkhut.training.asset_classification import (
Sector, TokenRole, SupplyModel, ConsensusFamily, SmartContractCapability,
MarketCapTier, DerivativeAccess, VolatilityProfile, LiquidityProfile,
AssetProfile, ASSET_PROFILES,
get_asset_profile, list_assets,
get_assets_by_sector, get_assets_by_token_role, get_assets_by_supply,
get_assets_by_consensus, get_assets_by_market_cap, get_assets_by_volatility,
get_assets_by_liquidity, get_assets_by_derivatives, get_gas_tokens,
get_pov_assets, get_shortable_assets,
get_multi_sector_assets, get_multi_role_assets,
)
from malkhut.training.cma_trainer import ScenarioFactory
# ==============================================================================
# Profile existence and structural integrity
# ==============================================================================
class TestAssetProfileExists:
def test_minimum_assets(self):
assert len(ASSET_PROFILES) >= 13
def test_all_profiles_frozen(self):
for p in ASSET_PROFILES.values():
with pytest.raises(AttributeError):
p.symbol = "X"
def test_symbol_matches_key(self):
for symbol, profile in ASSET_PROFILES.items():
assert profile.symbol == symbol
def test_sectors_are_tuples(self):
for p in ASSET_PROFILES.values():
assert isinstance(p.sectors, tuple)
assert len(p.sectors) >= 1
def test_token_roles_are_tuples(self):
for p in ASSET_PROFILES.values():
assert isinstance(p.token_roles, tuple)
assert len(p.token_roles) >= 1
def test_no_duplicate_symbols(self):
assert len(ASSET_PROFILES) == len(set(ASSET_PROFILES.keys()))
def test_all_sectors_are_valid_sector_enum(self):
for p in ASSET_PROFILES.values():
for s in p.sectors:
assert isinstance(s, Sector)
def test_all_token_roles_are_valid_enum(self):
for p in ASSET_PROFILES.values():
for r in p.token_roles:
assert isinstance(r, TokenRole)
# ==============================================================================
# Enum coverage — every enum value used by at least one asset
# ==============================================================================
class TestEnumCoverage:
def test_all_primary_sectors_represented(self):
"""Every 'primary' sector (first in any asset's tuple) must be covered."""
used_primary = {p.sector for p in ASSET_PROFILES.values()}
assert Sector.CURRENCY in used_primary
assert Sector.LAYER1 in used_primary
assert Sector.LAYER2 in used_primary
assert Sector.DEFI in used_primary
assert Sector.ORACLE in used_primary
assert Sector.EXCHANGE in used_primary
assert Sector.MEME in used_primary
def test_all_token_roles_represented(self):
used = set()
for p in ASSET_PROFILES.values():
used.update(p.token_roles)
for r in TokenRole:
assert r in used, f"TokenRole.{r.value} not represented"
def test_all_supply_models_represented(self):
used = {p.supply_model for p in ASSET_PROFILES.values()}
for m in SupplyModel:
assert m in used, f"SupplyModel.{m.value} not represented"
def test_all_consensus_families_represented(self):
used = {p.consensus for p in ASSET_PROFILES.values()}
for c in ConsensusFamily:
assert c in used, f"ConsensusFamily.{c.value} not represented"
def test_all_smart_contract_capabilities_represented(self):
used = {p.smart_contracts for p in ASSET_PROFILES.values()}
for sc in SmartContractCapability:
assert sc in used, f"SmartContractCapability.{sc.value} not represented"
def test_all_common_market_cap_tiers_represented(self):
used = {p.market_cap_tier for p in ASSET_PROFILES.values()}
assert MarketCapTier.MEGA in used
assert MarketCapTier.LARGE in used
assert MarketCapTier.MID in used
assert MarketCapTier.SMALL in used
def test_all_common_volatility_profiles_represented(self):
used = {p.volatility_profile for p in ASSET_PROFILES.values()}
assert VolatilityProfile.LOW in used
assert VolatilityProfile.MEDIUM in used
assert VolatilityProfile.HIGH in used
def test_all_common_liquidity_profiles_represented(self):
used = {p.liquidity_profile for p in ASSET_PROFILES.values()}
assert LiquidityProfile.DEEP in used
assert LiquidityProfile.NORMAL in used
assert LiquidityProfile.THIN in used
def test_common_derivative_access_represented(self):
used = {p.derivative_access for p in ASSET_PROFILES.values()}
assert DerivativeAccess.PERPS_AND_OPTIONS in used
assert DerivativeAccess.PERPS_ONLY in used
# ==============================================================================
# Multi-label overlap
# ==============================================================================
class TestMultiLabelOverlap:
def test_eth_multiple_sectors(self):
p = get_asset_profile("ETHUSDT")
assert Sector.LAYER1 in p.sectors
assert Sector.DEFI in p.sectors
assert len(p.sectors) == 2
def test_eth_multiple_roles(self):
p = get_asset_profile("ETHUSDT")
assert TokenRole.GAS in p.token_roles
assert TokenRole.STORE_OF_VALUE in p.token_roles
assert TokenRole.GOVERNANCE in p.token_roles
assert len(p.token_roles) == 3
def test_bnb_multiple_sectors(self):
p = get_asset_profile("BNBUSDT")
assert Sector.EXCHANGE in p.sectors
assert Sector.LAYER1 in p.sectors
assert len(p.sectors) == 2
def test_bnb_multiple_roles(self):
p = get_asset_profile("BNBUSDT")
assert TokenRole.EXCHANGE_FEE in p.token_roles
assert TokenRole.GAS in p.token_roles
def test_doge_multi_sector(self):
p = get_asset_profile("DOGEUSDT")
assert Sector.MEME in p.sectors
assert Sector.CURRENCY in p.sectors
def test_doge_multi_role(self):
p = get_asset_profile("DOGEUSDT")
assert TokenRole.MEME in p.token_roles
assert TokenRole.GAS in p.token_roles
def test_uni_multi_role(self):
p = get_asset_profile("UNIUSDT")
assert TokenRole.GOVERNANCE in p.token_roles
assert TokenRole.UTILITY in p.token_roles
def test_dot_multi_role(self):
p = get_asset_profile("DOTUSDT")
assert TokenRole.GAS in p.token_roles
assert TokenRole.GOVERNANCE in p.token_roles
def test_avax_multi_sector(self):
p = get_asset_profile("AVAXUSDT")
assert Sector.LAYER1 in p.sectors
assert Sector.DEFI in p.sectors
def test_btc_single_sector(self):
p = get_asset_profile("BTCUSDT")
assert len(p.sectors) == 1
assert Sector.CURRENCY in p.sectors
def test_sol_single_sector_and_role(self):
p = get_asset_profile("SOLUSDT")
assert len(p.sectors) == 1
assert len(p.token_roles) == 1
def test_ada_single_sector_and_role(self):
p = get_asset_profile("ADAUSDT")
assert len(p.sectors) == 1
assert len(p.token_roles) == 1
def test_atom_single_sector_and_role(self):
p = get_asset_profile("ATOMUSDT")
assert len(p.sectors) == 1
assert len(p.token_roles) == 1
def test_multi_sector_assets_count(self):
multi = get_multi_sector_assets()
symbols = {p.symbol for p in multi}
assert "ETHUSDT" in symbols
assert "BNBUSDT" in symbols
assert "DOGEUSDT" in symbols
assert "AVAXUSDT" in symbols
assert len(multi) == 4
def test_multi_role_assets_count(self):
multi = get_multi_role_assets()
symbols = {p.symbol for p in multi}
assert "ETHUSDT" in symbols
assert "BNBUSDT" in symbols
assert "DOGEUSDT" in symbols
assert "UNIUSDT" in symbols
assert "DOTUSDT" in symbols
assert len(multi) == 5
# ==============================================================================
# Backward-compatible primary labels
# ==============================================================================
class TestBackwardCompatiblePrimaryLabels:
def test_eth_primary_sector(self):
assert get_asset_profile("ETHUSDT").sector == Sector.LAYER1
def test_bnb_primary_sector(self):
assert get_asset_profile("BNBUSDT").sector == Sector.EXCHANGE
def test_doge_primary_sector(self):
assert get_asset_profile("DOGEUSDT").sector == Sector.MEME
def test_eth_primary_role(self):
assert get_asset_profile("ETHUSDT").token_role == TokenRole.GAS
def test_bnb_primary_role(self):
assert get_asset_profile("BNBUSDT").token_role == TokenRole.EXCHANGE_FEE
def test_uni_primary_role(self):
assert get_asset_profile("UNIUSDT").token_role == TokenRole.GOVERNANCE
def test_primary_matches_first_in_sectors(self):
for p in ASSET_PROFILES.values():
assert p.sector == p.sectors[0]
def test_primary_matches_first_in_roles(self):
for p in ASSET_PROFILES.values():
assert p.token_role == p.token_roles[0]
# ==============================================================================
# Per-asset profiles — exhaustive for all 13
# ==============================================================================
class TestBTCProfile:
def test_sector(self):
assert get_asset_profile("BTCUSDT").sector == Sector.CURRENCY
def test_token_role(self):
assert get_asset_profile("BTCUSDT").token_role == TokenRole.STORE_OF_VALUE
def test_supply_model(self):
assert get_asset_profile("BTCUSDT").supply_model == SupplyModel.FIXED_CAP
def test_consensus(self):
assert get_asset_profile("BTCUSDT").consensus == ConsensusFamily.POW
def test_smart_contracts(self):
assert get_asset_profile("BTCUSDT").smart_contracts == SmartContractCapability.NONE
def test_market_cap(self):
assert get_asset_profile("BTCUSDT").market_cap_tier == MarketCapTier.MEGA
def test_volatility(self):
assert get_asset_profile("BTCUSDT").volatility_profile == VolatilityProfile.LOW
def test_liquidity(self):
assert get_asset_profile("BTCUSDT").liquidity_profile == LiquidityProfile.DEEP
def test_derivatives(self):
p = get_asset_profile("BTCUSDT")
assert p.derivative_access == DerivativeAccess.PERPS_AND_OPTIONS
assert p.has_options is True
assert p.has_funding is True
def test_tick_lot(self):
p = get_asset_profile("BTCUSDT")
assert p.tick_size == 0.1
assert p.lot_size == 0.001
assert p.price_decimals == 1
def test_fees(self):
p = get_asset_profile("BTCUSDT")
assert p.maker_fee_bps == -0.2
assert p.taker_fee_bps == 0.5
def test_orderbook(self):
p = get_asset_profile("BTCUSDT")
assert p.typical_spread_bps == 0.3
assert p.typical_depth_usd == 5_000_000
assert p.typical_daily_volume_usd == 30_000_000_000
class TestETHProfile:
def test_sectors(self):
p = get_asset_profile("ETHUSDT")
assert p.sector == Sector.LAYER1
assert Sector.DEFI in p.sectors
def test_roles(self):
p = get_asset_profile("ETHUSDT")
assert p.token_role == TokenRole.GAS
assert TokenRole.STORE_OF_VALUE in p.token_roles
assert TokenRole.GOVERNANCE in p.token_roles
def test_supply(self):
assert get_asset_profile("ETHUSDT").supply_model == SupplyModel.DISINFLATIONARY
def test_consensus(self):
assert get_asset_profile("ETHUSDT").consensus == ConsensusFamily.POS
def test_smart_contracts(self):
assert get_asset_profile("ETHUSDT").smart_contracts == SmartContractCapability.FULL
def test_tick_lot(self):
p = get_asset_profile("ETHUSDT")
assert p.tick_size == 0.01
assert p.lot_size == 0.001
assert p.price_decimals == 2
def test_options(self):
p = get_asset_profile("ETHUSDT")
assert p.has_options is True
assert p.has_funding is True
def test_is_gas(self):
assert get_asset_profile("ETHUSDT").is_gas_token
class TestSOLProfile:
def test_sector(self):
assert get_asset_profile("SOLUSDT").sector == Sector.LAYER1
def test_supply(self):
assert get_asset_profile("SOLUSDT").supply_model == SupplyModel.INFLATIONARY
def test_consensus(self):
assert get_asset_profile("SOLUSDT").consensus == ConsensusFamily.POS
def test_volatility(self):
assert get_asset_profile("SOLUSDT").volatility_profile == VolatilityProfile.HIGH
def test_no_options(self):
assert get_asset_profile("SOLUSDT").has_options is False
def test_lot_size(self):
assert get_asset_profile("SOLUSDT").lot_size == 0.01
class TestDOGEProfile:
def test_sectors(self):
p = get_asset_profile("DOGEUSDT")
assert Sector.MEME in p.sectors
assert Sector.CURRENCY in p.sectors
def test_roles(self):
p = get_asset_profile("DOGEUSDT")
assert TokenRole.MEME in p.token_roles
assert TokenRole.GAS in p.token_roles
def test_pow_forced_selling(self):
assert get_asset_profile("DOGEUSDT").supply_pressure == "forced"
def test_no_smart_contracts(self):
assert get_asset_profile("DOGEUSDT").smart_contracts == SmartContractCapability.NONE
def test_five_decimal_tick(self):
p = get_asset_profile("DOGEUSDT")
assert p.tick_size == 0.00001
assert p.price_decimals == 5
def test_one_unit_lot(self):
assert get_asset_profile("DOGEUSDT").lot_size == 1.0
class TestADAProfile:
def test_sector(self):
assert get_asset_profile("ADAUSDT").sector == Sector.LAYER1
def test_consensus(self):
assert get_asset_profile("ADAUSDT").consensus == ConsensusFamily.DPOS
def test_four_decimal_tick(self):
p = get_asset_profile("ADAUSDT")
assert p.tick_size == 0.0001
assert p.price_decimals == 4
class TestAVAXProfile:
def test_sectors(self):
p = get_asset_profile("AVAXUSDT")
assert Sector.LAYER1 in p.sectors
assert Sector.DEFI in p.sectors
def test_supply(self):
assert get_asset_profile("AVAXUSDT").supply_model == SupplyModel.INFLATIONARY
class TestUNIProfile:
def test_sector(self):
assert get_asset_profile("UNIUSDT").sector == Sector.DEFI
def test_roles(self):
p = get_asset_profile("UNIUSDT")
assert TokenRole.GOVERNANCE in p.token_roles
assert TokenRole.UTILITY in p.token_roles
def test_small_cap(self):
assert get_asset_profile("UNIUSDT").market_cap_tier == MarketCapTier.SMALL
class TestLINKProfile:
def test_sector(self):
assert get_asset_profile("LINKUSDT").sector == Sector.ORACLE
def test_role(self):
assert get_asset_profile("LINKUSDT").token_role == TokenRole.UTILITY
def test_no_gas_role(self):
assert get_asset_profile("LINKUSDT").is_gas_token is False
class TestBNBProfile:
def test_sectors(self):
p = get_asset_profile("BNBUSDT")
assert Sector.EXCHANGE in p.sectors
assert Sector.LAYER1 in p.sectors
def test_roles(self):
p = get_asset_profile("BNBUSDT")
assert TokenRole.EXCHANGE_FEE in p.token_roles
assert TokenRole.GAS in p.token_roles
def test_burn_mechanism(self):
assert get_asset_profile("BNBUSDT").supply_model == SupplyModel.BURN_MECHANISM
def test_partial_smart_contracts(self):
assert get_asset_profile("BNBUSDT").smart_contracts == SmartContractCapability.PARTIAL
def test_lower_fees(self):
p = get_asset_profile("BNBUSDT")
assert p.maker_fee_bps == -0.1
assert p.taker_fee_bps == 0.4
def test_is_gas(self):
assert get_asset_profile("BNBUSDT").is_gas_token
class TestMATICProfile:
def test_sector(self):
assert get_asset_profile("MATICUSDT").sector == Sector.LAYER2
def test_four_decimal_tick(self):
p = get_asset_profile("MATICUSDT")
assert p.tick_size == 0.0001
assert p.price_decimals == 4
class TestAAVEProfile:
def test_sector(self):
assert get_asset_profile("AAVEUSDT").sector == Sector.DEFI
def test_role(self):
assert get_asset_profile("AAVEUSDT").token_role == TokenRole.GOVERNANCE
def test_fixed_cap(self):
assert get_asset_profile("AAVEUSDT").supply_model == SupplyModel.FIXED_CAP
class TestDOTProfile:
def test_sector(self):
assert get_asset_profile("DOTUSDT").sector == Sector.LAYER1
def test_roles(self):
p = get_asset_profile("DOTUSDT")
assert TokenRole.GAS in p.token_roles
assert TokenRole.GOVERNANCE in p.token_roles
def test_three_decimal_tick(self):
p = get_asset_profile("DOTUSDT")
assert p.tick_size == 0.001
assert p.price_decimals == 3
class TestATOMProfile:
def test_sector(self):
assert get_asset_profile("ATOMUSDT").sector == Sector.LAYER1
def test_supply(self):
assert get_asset_profile("ATOMUSDT").supply_model == SupplyModel.INFLATIONARY
def test_consensus(self):
assert get_asset_profile("ATOMUSDT").consensus == ConsensusFamily.POS
# ==============================================================================
# Cross-dimensional consistency checks
# ==============================================================================
class TestCrossDimensionalConsistency:
def test_pow_assets_are_btc_doge_only(self):
pows = get_assets_by_consensus(ConsensusFamily.POW)
symbols = {p.symbol for p in pows}
assert symbols == {"BTCUSDT", "DOGEUSDT"}
def test_defi_sector_has_full_smart_contracts(self):
for p in get_assets_by_sector(Sector.DEFI):
assert p.smart_contracts == SmartContractCapability.FULL, \
f"{p.symbol} is DEFI but not FULL smart contracts"
def test_l1_native_assets_have_full_smart_contracts(self):
l1_native = [p for p in get_assets_by_sector(Sector.LAYER1)
if Sector.LAYER1 in p.sectors and Sector.EXCHANGE not in p.sectors]
for p in l1_native:
assert p.smart_contracts == SmartContractCapability.FULL, \
f"{p.symbol} is LAYER1 but not FULL smart contracts"
def test_mega_cap_is_btc_only(self):
mega = get_assets_by_market_cap(MarketCapTier.MEGA)
assert len(mega) == 1
assert mega[0].symbol == "BTCUSDT"
def test_btc_only_has_options(self):
with_options = [p for p in ASSET_PROFILES.values() if p.has_options]
assert len(with_options) == 2
symbols = {p.symbol for p in with_options}
assert symbols == {"BTCUSDT", "ETHUSDT"}
def test_only_two_perps_and_options(self):
pao = get_assets_by_derivatives(DerivativeAccess.PERPS_AND_OPTIONS)
assert len(pao) == 2
symbols = {p.symbol for p in pao}
assert symbols == {"BTCUSDT", "ETHUSDT"}
def test_all_have_funding(self):
for p in ASSET_PROFILES.values():
assert p.has_funding is True
def test_only_bnb_has_burn_mechanism(self):
burned = get_assets_by_supply(SupplyModel.BURN_MECHANISM)
assert len(burned) == 1
assert burned[0].symbol == "BNBUSDT"
def test_only_bnb_has_partial_smart_contracts(self):
partial = [p for p in ASSET_PROFILES.values()
if p.smart_contracts == SmartContractCapability.PARTIAL]
assert len(partial) == 1
assert partial[0].symbol == "BNBUSDT"
def test_no_smart_contracts_are_btc_doge(self):
none_sc = [p for p in ASSET_PROFILES.values()
if p.smart_contracts == SmartContractCapability.NONE]
symbols = {p.symbol for p in none_sc}
assert symbols == {"BTCUSDT", "DOGEUSDT"}
def test_only_btc_is_mega(self):
mega = get_assets_by_market_cap(MarketCapTier.MEGA)
assert len(mega) == 1
def test_large_cap_are_eth_bnb(self):
large = get_assets_by_market_cap(MarketCapTier.LARGE)
symbols = {p.symbol for p in large}
assert symbols == {"ETHUSDT", "BNBUSDT"}
# ==============================================================================
# Predictive properties
# ==============================================================================
class TestSupplyPressure:
def test_pow_forced(self):
for symbol in ("BTCUSDT", "DOGEUSDT"):
assert get_asset_profile(symbol).supply_pressure == "forced"
def test_pos_optional(self):
pos_symbols = ["ETHUSDT", "SOLUSDT", "ADAUSDT", "AVAXUSDT",
"UNIUSDT", "LINKUSDT", "AAVEUSDT", "DOTUSDT", "ATOMUSDT"]
for symbol in pos_symbols:
assert get_asset_profile(symbol).supply_pressure == "optional"
def test_dpos_optional(self):
for symbol in ("ADAUSDT", "BNBUSDT", "DOTUSDT"):
assert get_asset_profile(symbol).supply_pressure == "optional"
def test_pov_only_pow(self):
pov = get_pov_assets()
assert len(pov) == 2
for p in pov:
assert p.consensus == ConsensusFamily.POW
class TestDemandElasticity:
def test_gas_inelastic(self):
for p in get_gas_tokens():
assert p.demand_elasticity == "inelastic"
def test_store_of_value_inelastic(self):
sov = get_assets_by_token_role(TokenRole.STORE_OF_VALUE)
for p in sov:
assert p.demand_elasticity == "inelastic"
def test_elastic_tokens(self):
for p in ASSET_PROFILES.values():
if p.demand_elasticity == "elastic":
assert TokenRole.GAS not in p.token_roles
assert TokenRole.STORE_OF_VALUE not in p.token_roles
def test_elastic_count(self):
elastic = [p for p in ASSET_PROFILES.values()
if p.demand_elasticity == "elastic"]
assert len(elastic) >= 3
def test_inelastic_count(self):
inelastic = [p for p in ASSET_PROFILES.values()
if p.demand_elasticity == "inelastic"]
assert len(inelastic) >= 8
def test_all_assets_are_one_or_other(self):
for p in ASSET_PROFILES.values():
assert p.demand_elasticity in ("inelastic", "elastic")
class TestIsGasToken:
def test_gas_tokens(self):
gas = get_gas_tokens()
symbols = {p.symbol for p in gas}
assert "ETHUSDT" in symbols
assert "SOLUSDT" in symbols
assert "BNBUSDT" in symbols
assert "DOGEUSDT" in symbols
assert "MATICUSDT" in symbols
assert "DOTUSDT" in symbols
def test_non_gas_tokens(self):
assert get_asset_profile("BTCUSDT").is_gas_token is False
assert get_asset_profile("UNIUSDT").is_gas_token is False
assert get_asset_profile("AAVEUSDT").is_gas_token is False
assert get_asset_profile("LINKUSDT").is_gas_token is False
class TestIsPureCurrency:
def test_currency_assets(self):
assert get_asset_profile("BTCUSDT").is_pure_currency
assert get_asset_profile("DOGEUSDT").is_pure_currency
def test_non_currency_assets(self):
assert get_asset_profile("ETHUSDT").is_pure_currency is False
assert get_asset_profile("SOLUSDT").is_pure_currency is False
assert get_asset_profile("UNIUSDT").is_pure_currency is False
class TestCanBeShorted:
def test_all_shortable(self):
for p in ASSET_PROFILES.values():
assert p.can_be_shorted
def test_shortable_matches_derivatives(self):
for p in ASSET_PROFILES.values():
if p.derivative_access == DerivativeAccess.NONE:
assert not p.can_be_shorted
else:
assert p.can_be_shorted
# ==============================================================================
# Orderbook fingerprint ordering
# ==============================================================================
class TestOrderbookFingerprintOrdering:
def test_spread_increases_with_risk(self):
btc_spread = get_asset_profile("BTCUSDT").typical_spread_bps
eth_spread = get_asset_profile("ETHUSDT").typical_spread_bps
sol_spread = get_asset_profile("SOLUSDT").typical_spread_bps
doge_spread = get_asset_profile("DOGEUSDT").typical_spread_bps
assert btc_spread < eth_spread < sol_spread < doge_spread
def test_depth_decreases_with_cap(self):
btc_depth = get_asset_profile("BTCUSDT").typical_depth_usd
eth_depth = get_asset_profile("ETHUSDT").typical_depth_usd
sol_depth = get_asset_profile("SOLUSDT").typical_depth_usd
aave_depth = get_asset_profile("AAVEUSDT").typical_depth_usd
assert btc_depth > eth_depth > sol_depth > aave_depth
def test_volume_decreases_with_cap(self):
btc_vol = get_asset_profile("BTCUSDT").typical_daily_volume_usd
eth_vol = get_asset_profile("ETHUSDT").typical_daily_volume_usd
assert btc_vol > eth_vol
def test_deep_has_highest_depth(self):
for p in get_assets_by_liquidity(LiquidityProfile.DEEP):
assert p.typical_depth_usd >= 2_000_000
def test_thin_has_lowest_depth(self):
for p in get_assets_by_liquidity(LiquidityProfile.THIN):
assert p.typical_depth_usd <= 700_000
def test_spread_nonzero(self):
for p in ASSET_PROFILES.values():
assert p.typical_spread_bps > 0
def test_volume_nonzero(self):
for p in ASSET_PROFILES.values():
assert p.typical_daily_volume_usd > 0
# ==============================================================================
# Profile field validation
# ==============================================================================
class TestProfileFieldValidation:
def test_tick_size_positive(self):
for p in ASSET_PROFILES.values():
assert p.tick_size > 0
def test_lot_size_positive(self):
for p in ASSET_PROFILES.values():
assert p.lot_size > 0
def test_price_decimals_non_negative(self):
for p in ASSET_PROFILES.values():
assert p.price_decimals >= 0
def test_fee_bps_reasonable(self):
for p in ASSET_PROFILES.values():
assert -1.0 <= p.maker_fee_bps <= 1.0
assert 0.0 <= p.taker_fee_bps <= 2.0
assert p.taker_fee_bps >= p.maker_fee_bps
def test_depth_usd_positive(self):
for p in ASSET_PROFILES.values():
assert p.typical_depth_usd > 0
def test_volume_usd_positive(self):
for p in ASSET_PROFILES.values():
assert p.typical_daily_volume_usd > 0
def test_volume_exceeds_depth(self):
for p in ASSET_PROFILES.values():
assert p.typical_daily_volume_usd > p.typical_depth_usd
# ==============================================================================
# Query functions — exhaustive with edge cases
# ==============================================================================
class TestQueryFunctions:
def test_get_asset_profile_hit(self):
assert get_asset_profile("SOLUSDT") is not None
def test_get_asset_profile_miss(self):
assert get_asset_profile("FAKEUSDT") is None
assert get_asset_profile("") is None
assert get_asset_profile("BTCUSDTx") is None
def test_list_assets_count(self):
assert len(list_assets()) == 13
def test_list_assets_all_present(self):
expected = {"BTCUSDT", "ETHUSDT", "SOLUSDT", "DOGEUSDT", "ADAUSDT",
"AVAXUSDT", "UNIUSDT", "LINKUSDT", "BNBUSDT", "MATICUSDT",
"AAVEUSDT", "DOTUSDT", "ATOMUSDT"}
assert set(list_assets()) == expected
def test_by_sector_layer1(self):
l1 = get_assets_by_sector(Sector.LAYER1)
symbols = {p.symbol for p in l1}
assert "ETHUSDT" in symbols
assert "SOLUSDT" in symbols
assert "BNBUSDT" in symbols
def test_by_sector_defi(self):
defi = get_assets_by_sector(Sector.DEFI)
symbols = {p.symbol for p in defi}
assert "ETHUSDT" in symbols
assert "UNIUSDT" in symbols
assert "AAVEUSDT" in symbols
assert "AVAXUSDT" in symbols
def test_by_sector_exchange(self):
ex = get_assets_by_sector(Sector.EXCHANGE)
assert len(ex) == 1
assert ex[0].symbol == "BNBUSDT"
def test_by_sector_oracle(self):
orc = get_assets_by_sector(Sector.ORACLE)
assert len(orc) == 1
assert orc[0].symbol == "LINKUSDT"
def test_by_sector_layer2(self):
l2 = get_assets_by_sector(Sector.LAYER2)
assert len(l2) == 1
assert l2[0].symbol == "MATICUSDT"
def test_by_sector_meme(self):
meme = get_assets_by_sector(Sector.MEME)
symbols = {p.symbol for p in meme}
assert "DOGEUSDT" in symbols
def test_by_sector_storage_empty(self):
assert get_assets_by_sector(Sector.STORAGE) == []
def test_by_sector_privacy_empty(self):
assert get_assets_by_sector(Sector.PRIVACY) == []
def test_by_sector_gaming_empty(self):
assert get_assets_by_sector(Sector.GAMING_NFT) == []
def test_by_token_role_gas(self):
gas = get_assets_by_token_role(TokenRole.GAS)
assert len(gas) >= 7
def test_by_token_role_store_of_value(self):
sov = get_assets_by_token_role(TokenRole.STORE_OF_VALUE)
symbols = {p.symbol for p in sov}
assert "BTCUSDT" in symbols
assert "ETHUSDT" in symbols
def test_by_token_role_governance(self):
gov = get_assets_by_token_role(TokenRole.GOVERNANCE)
symbols = {p.symbol for p in gov}
assert "UNIUSDT" in symbols
assert "AAVEUSDT" in symbols
assert "ETHUSDT" in symbols
assert "DOTUSDT" in symbols
def test_by_token_role_utility(self):
util = get_assets_by_token_role(TokenRole.UTILITY)
symbols = {p.symbol for p in util}
assert "LINKUSDT" in symbols
assert "UNIUSDT" in symbols
def test_by_token_role_exchange_fee(self):
ef = get_assets_by_token_role(TokenRole.EXCHANGE_FEE)
assert len(ef) == 1
assert ef[0].symbol == "BNBUSDT"
def test_by_token_role_meme(self):
meme = get_assets_by_token_role(TokenRole.MEME)
symbols = {p.symbol for p in meme}
assert "DOGEUSDT" in symbols
def test_by_supply_fixed_cap(self):
fixed = get_assets_by_supply(SupplyModel.FIXED_CAP)
symbols = {p.symbol for p in fixed}
assert "BTCUSDT" in symbols
assert "AAVEUSDT" in symbols
def test_by_supply_disinflationary(self):
dis = get_assets_by_supply(SupplyModel.DISINFLATIONARY)
assert len(dis) == 1
assert dis[0].symbol == "ETHUSDT"
def test_by_supply_burn(self):
burn = get_assets_by_supply(SupplyModel.BURN_MECHANISM)
assert len(burn) == 1
assert burn[0].symbol == "BNBUSDT"
def test_by_consensus_pow(self):
pows = get_assets_by_consensus(ConsensusFamily.POW)
symbols = {p.symbol for p in pows}
assert symbols == {"BTCUSDT", "DOGEUSDT"}
def test_by_consensus_dpos(self):
dpos = get_assets_by_consensus(ConsensusFamily.DPOS)
symbols = {p.symbol for p in dpos}
assert "ADAUSDT" in symbols
assert "BNBUSDT" in symbols
assert "DOTUSDT" in symbols
def test_by_market_cap_mega(self):
mega = get_assets_by_market_cap(MarketCapTier.MEGA)
assert len(mega) == 1
def test_by_market_cap_large(self):
large = get_assets_by_market_cap(MarketCapTier.LARGE)
symbols = {p.symbol for p in large}
assert symbols == {"ETHUSDT", "BNBUSDT"}
def test_by_market_cap_small(self):
small = get_assets_by_market_cap(MarketCapTier.SMALL)
assert len(small) >= 4
def test_by_volatility_low(self):
low = get_assets_by_volatility(VolatilityProfile.LOW)
assert len(low) == 1
assert low[0].symbol == "BTCUSDT"
def test_by_volatility_medium(self):
med = get_assets_by_volatility(VolatilityProfile.MEDIUM)
assert len(med) >= 3
def test_by_volatility_high(self):
high = get_assets_by_volatility(VolatilityProfile.HIGH)
assert len(high) >= 5
def test_by_volatility_extreme_empty(self):
assert get_assets_by_volatility(VolatilityProfile.EXTREME) == []
def test_by_liquidity_deep(self):
deep = get_assets_by_liquidity(LiquidityProfile.DEEP)
symbols = {p.symbol for p in deep}
assert "BTCUSDT" in symbols
assert "ETHUSDT" in symbols
assert "BNBUSDT" in symbols
def test_by_liquidity_normal(self):
norm = get_assets_by_liquidity(LiquidityProfile.NORMAL)
assert len(norm) >= 4
def test_by_liquidity_thin(self):
thin = get_assets_by_liquidity(LiquidityProfile.THIN)
assert len(thin) >= 4
def test_by_liquidity_illiquid_empty(self):
assert get_assets_by_liquidity(LiquidityProfile.ILLIQUID) == []
def test_by_derivatives_perps_options(self):
pao = get_assets_by_derivatives(DerivativeAccess.PERPS_AND_OPTIONS)
assert len(pao) == 2
def test_by_derivatives_perps_only(self):
po = get_assets_by_derivatives(DerivativeAccess.PERPS_ONLY)
assert len(po) == 11
def test_by_derivatives_none_empty(self):
assert get_assets_by_derivatives(DerivativeAccess.NONE) == []
def test_shortable_count(self):
assert len(get_shortable_assets()) == 13
def test_get_gas_tokens_count(self):
assert len(get_gas_tokens()) == 9
def test_get_pov_assets_count(self):
assert len(get_pov_assets()) == 2
# ==============================================================================
# ScenarioFactory integration
# ==============================================================================
class TestMultiAssetScenarios:
def test_multi_asset_suite_3_assets(self):
factory = ScenarioFactory()
suite = factory.build_suite(
symbols=("BTCUSDT", "ETHUSDT", "SOLUSDT"),
steps_per_scenario=5,
)
assert len(suite) >= 90
def test_multi_asset_uses_profiles(self):
factory = ScenarioFactory()
suite = factory.build_suite(symbols=("ETHUSDT",), steps_per_scenario=5)
for s in suite:
assert s.initial_state.venue.symbol == "ETHUSDT"
assert s.initial_state.venue.tick_size == 0.01
def test_all_assets_have_scenarios(self):
factory = ScenarioFactory()
assets = list_assets()
suite = factory.build_suite(symbols=tuple(assets), steps_per_scenario=3)
symbols_in_suite = set(s.initial_state.venue.symbol for s in suite)
for asset in assets:
assert asset in symbols_in_suite
def test_btc_uses_btc_tick(self):
factory = ScenarioFactory()
suite = factory.build_suite(symbols=("BTCUSDT",), steps_per_scenario=3)
for s in suite:
assert s.initial_state.venue.tick_size == 0.1
def test_doge_uses_doge_tick(self):
factory = ScenarioFactory()
suite = factory.build_suite(symbols=("DOGEUSDT",), steps_per_scenario=3)
for s in suite:
assert s.initial_state.venue.tick_size == 0.00001
def test_single_asset_scenario_count(self):
factory = ScenarioFactory()
suite = factory.build_suite(symbols=("BTCUSDT",), steps_per_scenario=5)
assert len(suite) >= 30
def test_steps_per_scenario_honored(self):
factory = ScenarioFactory()
suite = factory.build_suite(symbols=("ETHUSDT",), steps_per_scenario=10)
for s in suite:
assert s.max_steps >= 10

View File

@@ -0,0 +1,398 @@
"""
Asset Behavior DSL — Pure Python composable behavior definitions.
Decomposes asset behavior into orthogonal dimensions, each with
empirically-validated parameters from live Binance/BingX API + academic
literature (Bouchaud, Cont/Stoikov, Cartea/Jaimungal).
13 pre-defined assets, 3 composable templates, 10 orthogonal dimensions.
Auto-compilable via asset_compiler.py for any Binance-listed symbol.
Usage:
from malkhut.training.asset_behavior import get_behavior, ASSET_BEHAVIORS
btc = get_behavior("BTCUSDT")
print(btc.depth.amplitude_usd) # $750,000
print(btc.expected_slippage_bps(100_000)) # ~2 bps
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, List, Optional
from malkhut.training.asset_classification import Sector, TokenRole
@dataclass(frozen=True, slots=True)
class DepthProfile:
amplitude_usd: float
alpha: float
fragility_factor: float
depth_at_10bps_usd: float
depth_at_100bps_usd: float
@dataclass(frozen=True, slots=True)
class SpreadProfile:
normal_bps: float
stress_multiplier: float
@dataclass(frozen=True, slots=True)
class FlowProfile:
orders_per_sec_normal: float
orders_per_sec_stress: float
cancel_fill_ratio: float
median_order_usd: float
p99_order_usd: float
avg_trade_usd: float
@dataclass(frozen=True, slots=True)
class VolatilityProfile:
annualized_normal: float
annualized_crisis: float
garch_alpha: float
garch_beta: float
half_life_hours: float
@dataclass(frozen=True, slots=True)
class IntradayProfile:
peak_hour_utc: int
trough_hour_utc: int
ratio: float
@dataclass(frozen=True, slots=True)
class WeekendProfile:
vol_mult: float
volume_mult: float
spread_mult: float
@dataclass(frozen=True, slots=True)
class CorrelationProfile:
eth_beta: float
btc_corr_normal: float
btc_corr_crash: float
@dataclass(frozen=True, slots=True)
class MarketMakerProfile:
max_inventory_usd: float
skew_tolerance_bps: float
pull_speed_ms: float
margin_bps: float
@dataclass(frozen=True, slots=True)
class LiquidationProfile:
oi_mcap_ratio: float
trigger_pct: float
speed: str
recovery: str
@dataclass(frozen=True, slots=True)
class FundingProfile:
mean_bps_8h: float
std_bps_8h: float
positive_pct: float
basis_typical_bps: float
@dataclass(frozen=True, slots=True)
class RetailProfile:
ratio: float
inst_gap: float
@dataclass(frozen=True, slots=True)
class BingxProfile:
spread_mult: float
depth_ratio: float
latency_ms: float
taker_fee_bps: float
maker_fee_bps: float
funding_lag_hours: float
@dataclass(frozen=True, slots=True)
class BehaviorTemplate:
name: str
depth: DepthProfile
spread: SpreadProfile
flow: FlowProfile
vol: VolatilityProfile
intraday: IntradayProfile
weekend: WeekendProfile
correlation: CorrelationProfile
market_maker: MarketMakerProfile
liquidation: LiquidationProfile
funding: FundingProfile
retail: RetailProfile
bingx: BingxProfile
@dataclass(frozen=True, slots=True)
class AssetBehavior:
symbol: str
depth: DepthProfile
spread: SpreadProfile
flow: FlowProfile
vol: VolatilityProfile
intraday: IntradayProfile
weekend: WeekendProfile
correlation: CorrelationProfile
market_maker: MarketMakerProfile
liquidation: LiquidationProfile
funding: FundingProfile
retail: RetailProfile
bingx: BingxProfile
template_name: str = ""
reference_price: float = 0.0
@classmethod
def from_template(cls, symbol: str, template: BehaviorTemplate,
overrides: Optional[Dict[str, object]] = None,
reference_price: float = 0.0) -> AssetBehavior:
params = {
"depth": template.depth, "spread": template.spread,
"flow": template.flow, "vol": template.vol,
"intraday": template.intraday, "weekend": template.weekend,
"correlation": template.correlation, "market_maker": template.market_maker,
"liquidation": template.liquidation, "funding": template.funding,
"retail": template.retail, "bingx": template.bingx,
}
if overrides:
for key, value in overrides.items():
if key in params:
if isinstance(value, dict):
base = params[key]
params[key] = type(base)(**{**base.__dict__, **value})
else:
params[key] = value
return cls(symbol=symbol, template_name=template.name,
reference_price=reference_price, **params)
def depth_at_bps(self, bps: float) -> float:
"""D(d) = amplitude * d^(1-alpha)"""
return self.depth.amplitude_usd * (bps ** (1.0 - self.depth.alpha))
def expected_slippage_bps(self, order_usd: float) -> float:
"""Estimate slippage for a market order of given notional."""
d = 1.0
cumulative = 0.0
while cumulative < order_usd and d < 1000:
cumulative += self.depth.amplitude_usd * (d ** (-self.depth.alpha))
d += 1.0
return d if cumulative >= order_usd else 1000.0
TEMPLATES: Dict[str, BehaviorTemplate] = {}
def _t(name: str, **kwargs) -> BehaviorTemplate:
t = BehaviorTemplate(name=name, **kwargs)
TEMPLATES[name] = t
return t
INSTITUTIONAL_BLUE_CHIP = _t("institutional_blue_chip",
depth=DepthProfile(1_500_000, 0.70, 0.10, 7_000_000, 20_000_000),
spread=SpreadProfile(0.01, 50.0),
flow=FlowProfile(300, 5000, 20.0, 643, 200_000, 5_000),
vol=VolatilityProfile(35.0, 100.0, 0.10, 0.88, 48),
intraday=IntradayProfile(15, 19, 7.4),
weekend=WeekendProfile(0.65, 0.52, 1.20),
correlation=CorrelationProfile(1.00, 1.00, 1.00),
market_maker=MarketMakerProfile(10_000_000, 15, 3.0, 0.5),
liquidation=LiquidationProfile(0.005, 6.5, "slow", "fast"),
funding=FundingProfile(0.59, 0.22, 100.0, 4.0),
retail=RetailProfile(0.35, 0.04),
bingx=BingxProfile(12.6, 0.048, 100, 5.0, 2.0, 4))
MID_CAP_L1 = _t("mid_cap_l1",
depth=DepthProfile(200_000, 0.85, 0.15, 800_000, 3_000_000),
spread=SpreadProfile(1.5, 10.0),
flow=FlowProfile(100, 1500, 10.0, 800, 150_000, 2_000),
vol=VolatilityProfile(75.0, 150.0, 0.12, 0.85, 36),
intraday=IntradayProfile(15, 11, 5.0),
weekend=WeekendProfile(0.70, 0.55, 1.25),
correlation=CorrelationProfile(0.85, 0.60, 0.90),
market_maker=MarketMakerProfile(2_000_000, 25, 15.0, 1.0),
liquidation=LiquidationProfile(0.015, 5.0, "medium", "medium"),
funding=FundingProfile(0.10, 0.30, 60.0, 3.0),
retail=RetailProfile(0.65, 0.50),
bingx=BingxProfile(3.0, 0.30, 100, 5.0, 2.0, 4))
RETAIL_MEME = _t("retail_meme",
depth=DepthProfile(25_000, 1.00, 0.30, 100_000, 2_500_000),
spread=SpreadProfile(1.35, 15.0),
flow=FlowProfile(80, 800, 8.0, 96, 52_000, 200),
vol=VolatilityProfile(78.0, 200.0, 0.15, 0.82, 24),
intraday=IntradayProfile(15, 10, 4.5),
weekend=WeekendProfile(0.70, 0.50, 1.30),
correlation=CorrelationProfile(0.83, 0.45, 0.80),
market_maker=MarketMakerProfile(500_000, 40, 25.0, 2.0),
liquidation=LiquidationProfile(0.014, 4.0, "fast", "slow"),
funding=FundingProfile(0.39, 0.34, 80.0, 2.0),
retail=RetailProfile(0.80, 0.31),
bingx=BingxProfile(7.0, 1.27, 100, 5.0, 2.0, 4))
ASSET_BEHAVIORS: Dict[str, AssetBehavior] = {}
def _b(symbol: str, template_name: str, reference_price: float = 0.0,
**overrides) -> AssetBehavior:
tmpl = TEMPLATES[template_name]
b = AssetBehavior.from_template(symbol, tmpl, overrides or None, reference_price=reference_price)
ASSET_BEHAVIORS[symbol] = b
return b
BTC = _b("BTCUSDT", "institutional_blue_chip", reference_price=64000.0,
depth=DepthProfile(750_000, 0.70, 0.10, 5_983_000, 20_000_000),
vol=VolatilityProfile(35.0, 100.0, 0.10, 0.88, 48),
bingx=BingxProfile(12.6, 0.048, 100, 5.0, 2.0, 4))
ETH = _b("ETHUSDT", "institutional_blue_chip", reference_price=1800.0,
depth=DepthProfile(600_000, 0.75, 0.12, 2_095_000, 7_200_000),
vol=VolatilityProfile(66.5, 130.0, 0.12, 0.86, 40),
correlation=CorrelationProfile(1.00, 0.90, 0.97),
liquidation=LiquidationProfile(0.019, 4.0, "medium", "medium"),
bingx=BingxProfile(9.0, 9.06, 100, 5.0, 2.0, 4))
SOL = _b("SOLUSDT", "mid_cap_l1", reference_price=80.0,
depth=DepthProfile(400_000, 0.85, 0.18, 954_000, 4_000_000),
spread=SpreadProfile(1.26, 8.0),
vol=VolatilityProfile(71.9, 150.0, 0.13, 0.84, 32),
correlation=CorrelationProfile(0.88, 0.65, 0.92),
retail=RetailProfile(0.72, 0.75),
bingx=BingxProfile(1.7, 0.33, 80, 5.0, 2.0, 2))
DOGE = _b("DOGEUSDT", "retail_meme", reference_price=0.07,
depth=DepthProfile(22_000, 1.00, 0.30, 432_000, 2_772_000),
vol=VolatilityProfile(77.9, 200.0, 0.15, 0.82, 24),
correlation=CorrelationProfile(0.83, 0.45, 0.80),
bingx=BingxProfile(7.0, 1.27, 100, 5.0, 2.0, 4))
ADA = _b("ADAUSDT", "mid_cap_l1", reference_price=0.17,
depth=DepthProfile(60_000, 0.90, 0.20, 110_000, 1_500_000),
spread=SpreadProfile(5.95, 12.0),
vol=VolatilityProfile(78.7, 160.0, 0.14, 0.83, 30),
bingx=BingxProfile(8.0, 0.20, 120, 5.0, 2.0, 4))
AVAX = _b("AVAXUSDT", "mid_cap_l1", reference_price=7.0,
depth=DepthProfile(55_000, 0.88, 0.18, 107_000, 1_200_000),
spread=SpreadProfile(1.48, 10.0),
vol=VolatilityProfile(80.1, 160.0, 0.13, 0.84, 32),
correlation=CorrelationProfile(0.79, 0.55, 0.88),
bingx=BingxProfile(4.0, 0.25, 110, 5.0, 2.0, 4))
UNI = _b("UNIUSDT", "mid_cap_l1", reference_price=3.6,
depth=DepthProfile(30_000, 0.95, 0.25, 53_000, 800_000),
spread=SpreadProfile(2.76, 12.0),
vol=VolatilityProfile(95.3, 220.0, 0.16, 0.81, 22),
correlation=CorrelationProfile(0.70, 0.50, 0.85),
market_maker=MarketMakerProfile(300_000, 50, 30.0, 2.5),
retail=RetailProfile(0.60, 0.25),
bingx=BingxProfile(10.0, 0.15, 150, 5.0, 2.0, 6))
LINK = _b("LINKUSDT", "mid_cap_l1", reference_price=8.0,
depth=DepthProfile(80_000, 0.87, 0.17, 129_000, 1_800_000),
spread=SpreadProfile(1.25, 8.0),
vol=VolatilityProfile(77.3, 155.0, 0.12, 0.85, 34),
correlation=CorrelationProfile(0.88, 0.62, 0.91),
bingx=BingxProfile(3.5, 0.28, 100, 5.0, 2.0, 4))
BNB = _b("BNBUSDT", "institutional_blue_chip", reference_price=575.0,
depth=DepthProfile(500_000, 0.78, 0.12, 2_000_000, 8_000_000),
spread=SpreadProfile(0.50, 15.0),
vol=VolatilityProfile(50.0, 120.0, 0.11, 0.87, 42),
funding=FundingProfile(0.40, 0.25, 90.0, 3.0),
retail=RetailProfile(0.50, 0.20),
bingx=BingxProfile(5.0, 0.50, 100, 5.0, 2.0, 4))
MATIC = _b("MATICUSDT", "mid_cap_l1", reference_price=0.5,
depth=DepthProfile(35_000, 0.92, 0.22, 55_000, 900_000),
spread=SpreadProfile(1.50, 10.0),
vol=VolatilityProfile(82.0, 170.0, 0.14, 0.83, 28),
correlation=CorrelationProfile(0.82, 0.58, 0.89),
bingx=BingxProfile(5.0, 0.30, 110, 5.0, 2.0, 4))
AAVE = _b("AAVEUSDT", "mid_cap_l1", reference_price=100.0,
depth=DepthProfile(20_000, 0.95, 0.25, 35_000, 600_000),
spread=SpreadProfile(2.50, 12.0),
vol=VolatilityProfile(85.0, 200.0, 0.15, 0.82, 26),
correlation=CorrelationProfile(0.75, 0.52, 0.86),
market_maker=MarketMakerProfile(200_000, 45, 28.0, 2.0),
retail=RetailProfile(0.55, 0.20),
bingx=BingxProfile(10.0, 0.12, 140, 5.0, 2.0, 6))
DOT = _b("DOTUSDT", "mid_cap_l1", reference_price=6.0,
depth=DepthProfile(70_000, 0.88, 0.18, 120_000, 1_500_000),
spread=SpreadProfile(1.00, 8.0),
vol=VolatilityProfile(72.0, 145.0, 0.12, 0.85, 34),
bingx=BingxProfile(4.0, 0.30, 110, 5.0, 2.0, 4))
ATOM = _b("ATOMUSDT", "mid_cap_l1", reference_price=8.0,
depth=DepthProfile(25_000, 0.92, 0.22, 45_000, 700_000),
spread=SpreadProfile(2.00, 10.0),
vol=VolatilityProfile(76.0, 160.0, 0.13, 0.84, 30),
correlation=CorrelationProfile(0.78, 0.48, 0.87),
bingx=BingxProfile(6.0, 0.20, 130, 5.0, 2.0, 5))
# ==============================================================================
# Query functions
# ==============================================================================
def get_behavior(symbol: str) -> Optional[AssetBehavior]:
return ASSET_BEHAVIORS.get(symbol)
def list_behavior_symbols() -> List[str]:
return list(ASSET_BEHAVIORS.keys())
def get_behaviors_by_sector(sector: Sector) -> List[AssetBehavior]:
from malkhut.training.asset_classification import get_assets_by_sector
return [ASSET_BEHAVIORS[p.symbol] for p in get_assets_by_sector(sector)
if p.symbol in ASSET_BEHAVIORS]
def get_behaviors_by_role(role: TokenRole) -> List[AssetBehavior]:
from malkhut.training.asset_classification import get_assets_by_token_role
return [ASSET_BEHAVIORS[p.symbol] for p in get_assets_by_token_role(role)
if p.symbol in ASSET_BEHAVIORS]
def get_behaviors_by_template(template_name: str) -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.template_name == template_name]
def get_behaviors_by_volatility_band(min_ann: float = 0.0, max_ann: float = 500.0) -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values()
if min_ann <= b.vol.annualized_normal <= max_ann]
def get_fast_cascade_assets() -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.liquidation.speed == "fast"]
def get_institutional_assets() -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.retail.ratio < 0.5]
def get_retail_dominated_assets() -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.retail.ratio >= 0.6]
def get_high_vol_assets() -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.vol.annualized_normal >= 75.0]
def get_thin_book_assets() -> List[AssetBehavior]:
return [b for b in ASSET_BEHAVIORS.values() if b.depth.amplitude_usd < 100_000]

View File

@@ -0,0 +1,530 @@
"""
Asset Classification — INVARIANT characteristics only.
Split into two axes:
FUNDAMENTAL — intrinsic to the token's design (never changes):
Sector*, TokenRole*, SupplyModel, Consensus, SmartContractCapability
(* multi-label: an asset CAN belong to multiple sectors/roles)
TECHNICAL — invariant market-structure properties (set at listing, rarely change):
MarketCapTier, TypicalSpread/Depth, TickSize/LotSize, FeeStructure,
DerivativeAccess, PriceDecimals, TypicalVolume
Overlap handling (industry standard, per CoinGecko/CMC/Messari):
- Sector & TokenRole: frozenset (multi-label). An asset can be LAYER1 + DEFI,
or GAS + GOVERNANCE. The FIRST element is "primary" for single-label compat.
- Consensus, SupplyModel, MarketCapTier, SmartContractCapability: single enum.
One chain = one consensus. One supply mechanism. Inherently singular.
Sources: CoinGecko/CMC sector taxonomy (multi-tag per coin), Messari framework,
BIS/IMF digital asset classification, MiCA regulatory taxonomy,
academic factor models (Cong & He 2019, Harvey/Ramachandran/Santoro 2021),
exchange listing standards (Binance, BingX).
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, Optional, Sequence
# ==============================================================================
# FUNDAMENTAL dimensions — intrinsic to the token, never change
# ==============================================================================
class Sector(str, Enum):
"""Primary use-case / industry vertical. CoinGecko + Messari + CMC consensus.
Multi-label: an asset CAN appear in multiple sectors (e.g. BNB = EXCHANGE + LAYER1)."""
CURRENCY = "currency" # BTC: pure peer-to-peer money
LAYER1 = "layer1" # ETH, SOL, ADA, AVAX, DOT, ATOM: smart-contract platform
LAYER2 = "layer2" # MATIC, ARB, OP: scaling solutions
DEFI = "defi" # UNI, AAVE, MKR: decentralized finance protocols
ORACLE = "oracle" # LINK: external data feeds
EXCHANGE = "exchange" # BNB, OKB: CEX utility tokens
MEME = "meme" # DOGE, SHIB: community/speculation driven
PRIVACY = "privacy" # XMR, ZEC: privacy-preserving chains
STORAGE = "storage" # FIL, AR: decentralized storage
GAMING_NFT = "gaming_nft" # AXS, SAND, IMX: gaming/metaverse
class TokenRole(str, Enum):
"""Functional role of the token within its ecosystem. Determines demand elasticity.
Multi-label: an asset CAN serve multiple roles (e.g. ETH = GAS + STORE_OF_VALUE + GOVERNANCE)."""
GAS = "gas" # Must hold to pay tx fees (ETH, SOL, ADA, AVAX, DOT, ATOM, BNB)
STORE_OF_VALUE = "store_of_value" # Digital gold narrative (BTC, ETH)
GOVERNANCE = "governance" # Voting rights (UNI, AAVE, MKR, DOT)
UTILITY = "utility" # Pays for a service (LINK data, FIL storage)
MEME = "meme" # No utility, pure speculation (DOGE)
EXCHANGE_FEE = "exchange_fee" # Fee discount / burn (BNB)
class SupplyModel(str, Enum):
"""How new tokens enter circulation. Determines long-term supply pressure.
Single-label: one asset = one supply mechanism."""
FIXED_CAP = "fixed_cap" # Hard cap: BTC (21M), AAVE (16M)
DISINFLATIONARY = "disinflationary" # Issuance decreases over time (ETH post-merge)
INFLATIONARY = "inflationary" # Ongoing issuance (SOL ~5%, ADA ~3%, AVAX ~4%)
BURN_MECHANISM = "burn_mechanism" # Buyback-and-burn (BNB quarterly burns)
class ConsensusFamily(str, Enum):
"""Consensus mechanism. Determines miner/validator selling behaviour.
Single-label: one chain = one consensus."""
POW = "pow" # Must sell to cover electricity → constant sell pressure
POS = "pos" # Can hold, staking yield → lower forced selling
DPOS = "dpos" # Delegated PoS (EOS, TRX, BNB Chain) → concentrated validator set
class SmartContractCapability(str, Enum):
"""Ability to run arbitrary smart contracts. Determines DeFi composability.
Single-label: one chain = one capability level."""
FULL = "full" # EVM or equivalent (ETH, SOL, ADA, AVAX, DOT, ATOM, UNI, AAVE)
PARTIAL = "partial" # Limited scripting (BNB Chain — EVM-compatible but different governance)
NONE = "none" # No smart contracts (BTC, DOGE)
# ==============================================================================
# TECHNICAL dimensions — invariant market-structure properties
# ==============================================================================
class MarketCapTier(str, Enum):
"""Absolute market-cap band. Changes slowly → semi-invariant. Determines price-impact per dollar."""
MEGA = "mega" # >$500B (BTC)
LARGE = "large" # $50-500B (ETH, BNB)
MID = "mid" # $5-50B (SOL, ADA, AVAX, DOT, ATOM)
SMALL = "small" # $500M-5B (UNI, AAVE, LINK, MATIC)
MICRO = "micro" # <$500M
class DerivativeAccess(str, Enum):
"""Derivatives availability. Affects shorting, funding dynamics, price discovery.
Single-label: one asset = one access level on a given exchange."""
PERPS_AND_OPTIONS = "perps_and_options" # BTC, ETH
PERPS_ONLY = "perps_only" # Most mid/small caps
NONE = "none" # Cannot be shorted on CEX
class VolatilityProfile(str, Enum):
"""Long-run average volatility band. Semi-invariant statistical fingerprint."""
LOW = "low" # <30% annualized (BTC, stablecoins)
MEDIUM = "medium" # 30-80% (ETH, ADA)
HIGH = "high" # 80-150% (SOL, DOGE, AVAX)
EXTREME = "extreme" # >150% (micro-caps, new listings)
class LiquidityProfile(str, Enum):
"""Long-run average liquidity tier. Semi-invariant order-book fingerprint."""
DEEP = "deep" # >$100M daily (BTC, ETH)
NORMAL = "normal" # $10-100M (SOL, BNB, DOGE)
THIN = "thin" # $1-10M (UNI, AAVE, MATIC)
ILLIQUID = "illiquid" # <$1M
# ==============================================================================
# AssetProfile — frozen, all-invariant, multi-label where appropriate
# ==============================================================================
@dataclass(frozen=True, slots=True)
class AssetProfile:
"""Immutable asset classification. Sector & TokenRole are frozensets (multi-label).
First element = primary label for single-label backward compatibility."""
# --- Identity ---
symbol: str
# --- Fundamental (intrinsic, never changes) ---
sectors: tuple[Sector, ...] # multi-label: BNB = (EXCHANGE, LAYER1). First = primary.
token_roles: tuple[TokenRole, ...] # multi-label: ETH = (GAS, STORE_OF_VALUE, GOVERNANCE). First = primary.
supply_model: SupplyModel # single-label
consensus: ConsensusFamily # single-label
smart_contracts: SmartContractCapability # single-label
# --- Technical (invariant market-structure properties) ---
market_cap_tier: MarketCapTier
volatility_profile: VolatilityProfile
liquidity_profile: LiquidityProfile
derivative_access: DerivativeAccess
# --- Execution parameters (exchange-set, invariant) ---
tick_size: float
lot_size: float
price_decimals: int
maker_fee_bps: float
taker_fee_bps: float
# --- Order-book fingerprint (long-run averages, semi-invariant) ---
typical_spread_bps: float
typical_depth_usd: float
typical_daily_volume_usd: float
# --- Structural flags (invariant) ---
has_funding: bool = False
has_options: bool = False
@property
def sector(self) -> Sector:
"""Primary sector (first in tuple). For single-label consumers."""
return self.sectors[0]
@property
def token_role(self) -> TokenRole:
"""Primary role (first in tuple). For single-label consumers."""
return self.token_roles[0]
@property
def is_gas_token(self) -> bool:
return TokenRole.GAS in self.token_roles
@property
def is_pure_currency(self) -> bool:
return Sector.CURRENCY in self.sectors
@property
def can_be_shorted(self) -> bool:
return self.derivative_access != DerivativeAccess.NONE
@property
def supply_pressure(self) -> str:
"""Predictive label: 'forced' for PoW miners who must sell,
'optional' for PoS validators who can hold."""
if self.consensus == ConsensusFamily.POW:
return "forced"
return "optional"
@property
def demand_elasticity(self) -> str:
"""Predictive label: 'inelastic' for gas/store-of-value (must hold),
'elastic' for governance/meme/exchange (can choose not to buy)."""
if any(r in self.token_roles for r in (TokenRole.GAS, TokenRole.STORE_OF_VALUE)):
return "inelastic"
return "elastic"
# ==============================================================================
# Helper to build multi-label profiles concisely
# ==============================================================================
def _profile(
symbol: str,
sectors: Sequence[Sector],
token_roles: Sequence[TokenRole],
supply_model: SupplyModel,
consensus: ConsensusFamily,
smart_contracts: SmartContractCapability,
market_cap_tier: MarketCapTier,
volatility_profile: VolatilityProfile,
liquidity_profile: LiquidityProfile,
derivative_access: DerivativeAccess,
tick_size: float,
lot_size: float,
price_decimals: int,
maker_fee_bps: float,
taker_fee_bps: float,
typical_spread_bps: float,
typical_depth_usd: float,
typical_daily_volume_usd: float,
has_funding: bool = False,
has_options: bool = False,
) -> AssetProfile:
return AssetProfile(
symbol=symbol,
sectors=tuple(sectors),
token_roles=tuple(token_roles),
supply_model=supply_model,
consensus=consensus,
smart_contracts=smart_contracts,
market_cap_tier=market_cap_tier,
volatility_profile=volatility_profile,
liquidity_profile=liquidity_profile,
derivative_access=derivative_access,
tick_size=tick_size, lot_size=lot_size, price_decimals=price_decimals,
maker_fee_bps=maker_fee_bps, taker_fee_bps=taker_fee_bps,
typical_spread_bps=typical_spread_bps,
typical_depth_usd=typical_depth_usd,
typical_daily_volume_usd=typical_daily_volume_usd,
has_funding=has_funding, has_options=has_options,
)
# ==============================================================================
# Profiles — 13 major crypto assets with BingX perps
# ==============================================================================
ASSET_PROFILES: Dict[str, AssetProfile] = {
"BTCUSDT": _profile(
symbol="BTCUSDT",
sectors=[Sector.CURRENCY],
token_roles=[TokenRole.STORE_OF_VALUE],
supply_model=SupplyModel.FIXED_CAP, consensus=ConsensusFamily.POW,
smart_contracts=SmartContractCapability.NONE,
market_cap_tier=MarketCapTier.MEGA,
volatility_profile=VolatilityProfile.LOW,
liquidity_profile=LiquidityProfile.DEEP,
derivative_access=DerivativeAccess.PERPS_AND_OPTIONS,
tick_size=0.1, lot_size=0.001, price_decimals=1,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=0.3, typical_depth_usd=5_000_000,
typical_daily_volume_usd=30_000_000_000,
has_funding=True, has_options=True,
),
"ETHUSDT": _profile(
symbol="ETHUSDT",
sectors=[Sector.LAYER1, Sector.DEFI],
token_roles=[TokenRole.GAS, TokenRole.STORE_OF_VALUE, TokenRole.GOVERNANCE],
supply_model=SupplyModel.DISINFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.LARGE,
volatility_profile=VolatilityProfile.MEDIUM,
liquidity_profile=LiquidityProfile.DEEP,
derivative_access=DerivativeAccess.PERPS_AND_OPTIONS,
tick_size=0.01, lot_size=0.001, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=0.5, typical_depth_usd=3_000_000,
typical_daily_volume_usd=15_000_000_000,
has_funding=True, has_options=True,
),
"SOLUSDT": _profile(
symbol="SOLUSDT",
sectors=[Sector.LAYER1],
token_roles=[TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.NORMAL,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.0, typical_depth_usd=1_000_000,
typical_daily_volume_usd=3_000_000_000,
has_funding=True,
),
"DOGEUSDT": _profile(
symbol="DOGEUSDT",
sectors=[Sector.MEME, Sector.CURRENCY],
token_roles=[TokenRole.MEME, TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POW,
smart_contracts=SmartContractCapability.NONE,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.NORMAL,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.00001, lot_size=1.0, price_decimals=5,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=2.0, typical_depth_usd=500_000,
typical_daily_volume_usd=1_000_000_000,
has_funding=True,
),
"ADAUSDT": _profile(
symbol="ADAUSDT",
sectors=[Sector.LAYER1],
token_roles=[TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.DPOS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.MEDIUM,
liquidity_profile=LiquidityProfile.NORMAL,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.0001, lot_size=1.0, price_decimals=4,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.0, typical_depth_usd=800_000,
typical_daily_volume_usd=500_000_000,
has_funding=True,
),
"AVAXUSDT": _profile(
symbol="AVAXUSDT",
sectors=[Sector.LAYER1, Sector.DEFI],
token_roles=[TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.NORMAL,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.2, typical_depth_usd=600_000,
typical_daily_volume_usd=400_000_000,
has_funding=True,
),
"UNIUSDT": _profile(
symbol="UNIUSDT",
sectors=[Sector.DEFI],
token_roles=[TokenRole.GOVERNANCE, TokenRole.UTILITY],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.SMALL,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.THIN,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=2.0, typical_depth_usd=300_000,
typical_daily_volume_usd=200_000_000,
has_funding=True,
),
"LINKUSDT": _profile(
symbol="LINKUSDT",
sectors=[Sector.ORACLE],
token_roles=[TokenRole.UTILITY],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.SMALL,
volatility_profile=VolatilityProfile.MEDIUM,
liquidity_profile=LiquidityProfile.THIN,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.0, typical_depth_usd=700_000,
typical_daily_volume_usd=400_000_000,
has_funding=True,
),
"BNBUSDT": _profile(
symbol="BNBUSDT",
sectors=[Sector.EXCHANGE, Sector.LAYER1],
token_roles=[TokenRole.EXCHANGE_FEE, TokenRole.GAS],
supply_model=SupplyModel.BURN_MECHANISM, consensus=ConsensusFamily.DPOS,
smart_contracts=SmartContractCapability.PARTIAL,
market_cap_tier=MarketCapTier.LARGE,
volatility_profile=VolatilityProfile.MEDIUM,
liquidity_profile=LiquidityProfile.DEEP,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.001, price_decimals=2,
maker_fee_bps=-0.1, taker_fee_bps=0.4,
typical_spread_bps=0.5, typical_depth_usd=2_000_000,
typical_daily_volume_usd=2_000_000_000,
has_funding=True,
),
"MATICUSDT": _profile(
symbol="MATICUSDT",
sectors=[Sector.LAYER2],
token_roles=[TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.SMALL,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.THIN,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.0001, lot_size=1.0, price_decimals=4,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.5, typical_depth_usd=500_000,
typical_daily_volume_usd=300_000_000,
has_funding=True,
),
"AAVEUSDT": _profile(
symbol="AAVEUSDT",
sectors=[Sector.DEFI],
token_roles=[TokenRole.GOVERNANCE],
supply_model=SupplyModel.FIXED_CAP, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.SMALL,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.THIN,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=2.5, typical_depth_usd=200_000,
typical_daily_volume_usd=150_000_000,
has_funding=True,
),
"DOTUSDT": _profile(
symbol="DOTUSDT",
sectors=[Sector.LAYER1],
token_roles=[TokenRole.GAS, TokenRole.GOVERNANCE],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.DPOS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.MEDIUM,
liquidity_profile=LiquidityProfile.NORMAL,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.001, lot_size=0.1, price_decimals=3,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=1.0, typical_depth_usd=600_000,
typical_daily_volume_usd=300_000_000,
has_funding=True,
),
"ATOMUSDT": _profile(
symbol="ATOMUSDT",
sectors=[Sector.LAYER1],
token_roles=[TokenRole.GAS],
supply_model=SupplyModel.INFLATIONARY, consensus=ConsensusFamily.POS,
smart_contracts=SmartContractCapability.FULL,
market_cap_tier=MarketCapTier.MID,
volatility_profile=VolatilityProfile.HIGH,
liquidity_profile=LiquidityProfile.THIN,
derivative_access=DerivativeAccess.PERPS_ONLY,
tick_size=0.01, lot_size=0.01, price_decimals=2,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=2.0, typical_depth_usd=250_000,
typical_daily_volume_usd=100_000_000,
has_funding=True,
),
}
# ==============================================================================
# Query functions — filter by any dimension (multi-label aware)
# ==============================================================================
def get_asset_profile(symbol: str) -> Optional[AssetProfile]:
return ASSET_PROFILES.get(symbol)
def list_assets() -> List[str]:
return list(ASSET_PROFILES.keys())
def get_assets_by_sector(sector: Sector) -> List[AssetProfile]:
"""Match assets where the queried sector is ANY of their sectors."""
return [p for p in ASSET_PROFILES.values() if sector in p.sectors]
def get_assets_by_token_role(role: TokenRole) -> List[AssetProfile]:
"""Match assets where the queried role is ANY of their roles."""
return [p for p in ASSET_PROFILES.values() if role in p.token_roles]
def get_assets_by_supply(model: SupplyModel) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.supply_model == model]
def get_assets_by_consensus(family: ConsensusFamily) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.consensus == family]
def get_assets_by_market_cap(tier: MarketCapTier) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.market_cap_tier == tier]
def get_assets_by_volatility(vol: VolatilityProfile) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.volatility_profile == vol]
def get_assets_by_liquidity(liq: LiquidityProfile) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.liquidity_profile == liq]
def get_assets_by_derivatives(access: DerivativeAccess) -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.derivative_access == access]
def get_gas_tokens() -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if TokenRole.GAS in p.token_roles]
def get_pov_assets() -> List[AssetProfile]:
"""Assets with forced sell pressure (PoW miners)."""
return [p for p in ASSET_PROFILES.values() if p.supply_pressure == "forced"]
def get_shortable_assets() -> List[AssetProfile]:
return [p for p in ASSET_PROFILES.values() if p.can_be_shorted]
def get_multi_sector_assets() -> List[AssetProfile]:
"""Assets belonging to more than one sector."""
return [p for p in ASSET_PROFILES.values() if len(p.sectors) > 1]
def get_multi_role_assets() -> List[AssetProfile]:
"""Assets serving more than one token role."""
return [p for p in ASSET_PROFILES.values() if len(p.token_roles) > 1]

View File

@@ -0,0 +1,520 @@
"""
Asset Compiler — auto-fetches from Binance/BingX APIs and produces
system-ready AssetProfile + AssetBehavior for any tradeable symbol.
Rate-limited (1 req/sec), cached, resumable.
Usage:
compiler = AssetCompiler()
result = compiler.compile("XRPUSDT")
# result.asset_profile → ready for ScenarioFactory
# result.asset_behavior → ready for behavior-driven scenarios
# Batch compile
results = compiler.compile_batch(["XRPUSDT", "HBARUSDT", "APTUSDT"])
"""
from __future__ import annotations
import json
import math
import time
import urllib.request
import urllib.error
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from malkhut.training.asset_classification import (
AssetProfile, ASSET_PROFILES,
Sector, TokenRole, SupplyModel, ConsensusFamily, SmartContractCapability,
MarketCapTier, DerivativeAccess, VolatilityProfile, LiquidityProfile,
)
from malkhut.training.asset_behavior import (
AssetBehavior, BehaviorTemplate, TEMPLATES, ASSET_BEHAVIORS,
DepthProfile, SpreadProfile, FlowProfile, VolatilityProfile as BehVol,
IntradayProfile, WeekendProfile, CorrelationProfile, MarketMakerProfile,
LiquidationProfile, FundingProfile, RetailProfile, BingxProfile,
)
# ==============================================================================
# Binance API client — rate-limited, cached
# ==============================================================================
class BinanceClient:
"""Rate-limited Binance REST API client with response caching."""
BASE = "https://api.binance.com"
FAPI = "https://fapi.binance.com"
MIN_INTERVAL_S = 1.0 # 1 req/sec = well under Binance 1200/min limit
def __init__(self) -> None:
self._last_request_s: float = 0.0
self._cache: Dict[str, dict] = {}
def _throttle(self) -> None:
elapsed = time.time() - self._last_request_s
if elapsed < self.MIN_INTERVAL_S:
time.sleep(self.MIN_INTERVAL_S - elapsed)
self._last_request_s = time.time()
def _get(self, url: str) -> dict:
if url in self._cache:
return self._cache[url]
self._throttle()
try:
req = urllib.request.Request(url, headers={"User-Agent": "MalkhutCompiler/1.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
self._cache[url] = data
return data
except (urllib.error.URLError, json.JSONDecodeError, OSError) as e:
return {"error": str(e)}
def ticker_24h(self, symbol: str) -> dict:
return self._get(f"{self.BASE}/api/v3/ticker/24hr?symbol={symbol}")
def depth(self, symbol: str, limit: int = 100) -> dict:
return self._get(f"{self.BASE}/api/v3/depth?symbol={symbol}&limit={limit}")
def klines(self, symbol: str, interval: str = "1h", limit: int = 168) -> list:
url = f"{self.BASE}/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}"
return self._get(url)
def exchange_info(self, symbol: str) -> dict:
data = self._get(f"{self.BASE}/api/v3/exchangeInfo")
if "symbols" in data:
for s in data["symbols"]:
if s.get("symbol") == symbol:
return s
return {}
def funding_rate(self, symbol: str, limit: int = 20) -> list:
fapi_symbol = symbol.replace("USDT", "-USDT")
return self._get(f"{self.FAPI}/fapi/v1/fundingRate?symbol={fapi_symbol}&limit={limit}")
def open_interest(self, symbol: str) -> dict:
fapi_symbol = symbol.replace("USDT", "-USDT")
return self._get(f"{self.FAPI}/fapi/v1/openInterest?symbol={fapi_symbol}")
def ticker_24h_perp(self, symbol: str) -> dict:
fapi_symbol = symbol.replace("USDT", "-USDT")
return self._get(f"{self.FAPI}/fapi/v1/ticker/24hr?symbol={fapi_symbol}")
# ==============================================================================
# Statistical computation helpers
# ==============================================================================
def _compute_annualized_vol(klines: list) -> float:
"""Compute annualized volatility from hourly klines."""
if not klines or len(klines) < 10:
return 80.0 # default mid-cap
returns = []
for i in range(1, len(klines)):
o = float(klines[i][1])
c = float(klines[i][4])
if o > 0:
returns.append(math.log(c / o))
if len(returns) < 5:
return 80.0
mean_r = sum(returns) / len(returns)
var_r = sum((r - mean_r) ** 2 for r in returns) / (len(returns) - 1)
hourly_vol = math.sqrt(var_r)
return hourly_vol * math.sqrt(8760) * 100 # annualize (8760 hours/year)
def _compute_spread_bps(depth_data: dict) -> float:
"""Compute spread in bps from depth snapshot."""
bids = depth_data.get("bids", [])
asks = depth_data.get("asks", [])
if not bids or not asks:
return 5.0
best_bid = float(bids[0][0])
best_ask = float(asks[0][0])
mid = (best_bid + best_ask) / 2
if mid <= 0:
return 5.0
return ((best_ask - best_bid) / mid) * 10000
def _compute_depth_profile(depth_data: dict, mid_price: float) -> Tuple[float, float]:
"""Fit depth amplitude and alpha from depth snapshot.
Returns (amplitude_usd, alpha)."""
bids = depth_data.get("bids", [])
asks = depth_data.get("asks", [])
if not bids or not asks or mid_price <= 0:
return 50_000.0, 0.9
cumulative_usd = 0.0
for level in bids[:50]:
price = float(level[0])
qty = float(level[1])
dist_bps = abs(price - mid_price) / mid_price * 10000
if dist_bps < 1:
cumulative_usd += qty * price
amplitude = max(cumulative_usd, 1_000)
bid_depths = []
for level in bids[:50]:
price = float(level[0])
qty = float(level[1])
dist_bps = max(abs(price - mid_price) / mid_price * 10000, 0.5)
bid_depths.append((dist_bps, qty * price))
if len(bid_depths) < 5:
return amplitude, 0.9
log_dists = [math.log(d) for d, _ in bid_depths if d > 0]
log_depths = [math.log(max(v, 1)) for d, v in bid_depths if d > 0]
if len(log_dists) < 5:
return amplitude, 0.9
n = len(log_dists)
sum_x = sum(log_dists)
sum_y = sum(log_depths)
sum_xy = sum(x * y for x, y in zip(log_dists, log_depths))
sum_x2 = sum(x * x for x in log_dists)
denom = n * sum_x2 - sum_x * sum_x
if abs(denom) < 1e-10:
return amplitude, 0.9
slope = (n * sum_xy - sum_x * sum_y) / denom
alpha = max(0.5, min(1.5, -slope + 1.0))
return amplitude, alpha
def _compute_order_flow_stats(klines: list) -> dict:
"""Compute order flow statistics from klines."""
if not klines or len(klines) < 10:
return {"median_usd": 500, "p99_usd": 100_000, "avg_usd": 2_000}
volumes_usd = []
for k in klines:
vol = float(k[5]) # quote volume
trades = float(k[8]) # number of trades
if trades > 0:
volumes_usd.append(vol / trades)
if not volumes_usd:
return {"median_usd": 500, "p99_usd": 100_000, "avg_usd": 2_000}
volumes_usd.sort()
n = len(volumes_usd)
median = volumes_usd[n // 2]
p99_idx = min(int(n * 0.99), n - 1)
avg = sum(volumes_usd) / n
return {"median_usd": median, "p99_usd": volumes_usd[p99_idx], "avg_usd": avg}
def _compute_funding_stats(funding_data: list) -> Tuple[float, float, float]:
"""Compute funding rate statistics. Returns (mean_bps, std_bps, positive_pct)."""
if not funding_data or isinstance(funding_data, dict):
return 0.10, 0.20, 60.0
rates = []
for entry in funding_data:
r = float(entry.get("fundingRate", 0))
rates.append(r * 10000) # convert to bps
if not rates:
return 0.10, 0.20, 60.0
mean_r = sum(rates) / len(rates)
var_r = sum((r - mean_r) ** 2 for r in rates) / max(len(rates) - 1, 1)
std_r = math.sqrt(var_r)
pos_pct = sum(1 for r in rates if r > 0) / len(rates) * 100
return mean_r, std_r, pos_pct
def _classify_market_cap(mcap_usd: float) -> MarketCapTier:
if mcap_usd > 500e9:
return MarketCapTier.MEGA
if mcap_usd > 50e9:
return MarketCapTier.LARGE
if mcap_usd > 5e9:
return MarketCapTier.MID
if mcap_usd > 500e6:
return MarketCapTier.SMALL
return MarketCapTier.MICRO
def _classify_volatility(ann_vol: float) -> VolatilityProfile:
if ann_vol < 30:
return VolatilityProfile.LOW
if ann_vol < 80:
return VolatilityProfile.MEDIUM
if ann_vol < 150:
return VolatilityProfile.HIGH
return VolatilityProfile.EXTREME
def _classify_liquidity(vol_usd: float) -> LiquidityProfile:
if vol_usd > 100e6:
return LiquidityProfile.DEEP
if vol_usd > 10e6:
return LiquidityProfile.NORMAL
if vol_usd > 1e6:
return LiquidityProfile.THIN
return LiquidityProfile.ILLIQUID
# ==============================================================================
# Heuristic asset classification (for unknown assets)
# ==============================================================================
_KNOWN_CLASSIFICATIONS: Dict[str, dict] = {
"BTCUSDT": {"sector": "currency", "role": "store_of_value", "supply": "fixed_cap",
"consensus": "pow", "sc": "none", "deriv": "perps_and_options"},
"ETHUSDT": {"sector": "layer1", "role": "gas", "supply": "disinflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_and_options"},
"SOLUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"DOGEUSDT": {"sector": "meme", "role": "meme", "supply": "inflationary",
"consensus": "pow", "sc": "none", "deriv": "perps_only"},
"ADAUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "dpos", "sc": "full", "deriv": "perps_only"},
"AVAXUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"UNIUSDT": {"sector": "defi", "role": "governance", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"LINKUSDT": {"sector": "oracle", "role": "utility", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"BNBUSDT": {"sector": "exchange", "role": "exchange_fee", "supply": "burn_mechanism",
"consensus": "dpos", "sc": "partial", "deriv": "perps_only"},
"MATICUSDT": {"sector": "layer2", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"AAVEUSDT": {"sector": "defi", "role": "governance", "supply": "fixed_cap",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"DOTUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "dpos", "sc": "full", "deriv": "perps_only"},
"ATOMUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"XRPUSDT": {"sector": "currency", "role": "utility", "supply": "inflationary",
"consensus": "bft", "sc": "partial", "deriv": "perps_only"},
"TRXUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "dpos", "sc": "full", "deriv": "perps_only"},
"LTCUSDT": {"sector": "currency", "role": "store_of_value", "supply": "fixed_cap",
"consensus": "pow", "sc": "none", "deriv": "perps_only"},
"NEARUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"APTUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "bft", "sc": "full", "deriv": "perps_only"},
"OPUSDT": {"sector": "layer2", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"ARBUSDT": {"sector": "layer2", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"SUIUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "bft", "sc": "full", "deriv": "perps_only"},
"PEPEUSDT": {"sector": "meme", "role": "meme", "supply": "fixed_cap",
"consensus": "pos", "sc": "none", "deriv": "perps_only"},
"WIFUSDT": {"sector": "meme", "role": "meme", "supply": "inflationary",
"consensus": "pos", "sc": "none", "deriv": "perps_only"},
"SEIUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "bft", "sc": "full", "deriv": "perps_only"},
"INJUSDT": {"sector": "defi", "role": "utility", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"FILUSDT": {"sector": "storage", "role": "utility", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
"HBARUSDT": {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "bft", "sc": "full", "deriv": "perps_only"},
"IMXUSDT": {"sector": "gaming_nft", "role": "utility", "supply": "fixed_cap",
"consensus": "pos", "sc": "full", "deriv": "perps_only"},
}
# ==============================================================================
# Compile result
# ==============================================================================
@dataclass
class CompileResult:
symbol: str
asset_profile: Optional[AssetProfile]
asset_behavior: Optional[AssetBehavior]
raw_data: dict
warnings: List[str] = field(default_factory=list)
# ==============================================================================
# Asset Compiler
# ==============================================================================
class AssetCompiler:
"""Auto-fetch from Binance/BingX, compute params, produce profiles."""
def __init__(self) -> None:
self.client = BinanceClient()
def compile(self, symbol: str) -> CompileResult:
"""Compile a single asset from live API data."""
warnings = []
raw = {}
ticker = self.client.ticker_24h(symbol)
if "error" in ticker or "lastPrice" not in ticker:
return CompileResult(symbol=symbol, asset_profile=None, asset_behavior=None,
raw_data=ticker, warnings=[f"Failed to fetch ticker: {ticker}"])
price = float(ticker["lastPrice"])
vol_usd_24h = float(ticker.get("quoteVolume", 0))
raw["ticker"] = {"price": price, "vol_usd_24h": vol_usd_24h}
depth_data = self.client.depth(symbol, limit=100)
spread_bps = _compute_spread_bps(depth_data)
depth_amp, depth_alpha = _compute_depth_profile(depth_data, price)
raw["depth"] = {"spread_bps": spread_bps, "amplitude": depth_amp, "alpha": depth_alpha}
klines = self.client.klines(symbol, "1h", 168)
ann_vol = _compute_annualized_vol(klines) if klines else 80.0
flow = _compute_order_flow_stats(klines)
raw["volatility"] = {"annualized": ann_vol}
raw["flow"] = flow
exch_info = self.client.exchange_info(symbol)
tick_size = 0.01
lot_size = 0.01
price_decimals = 2
if "filters" in exch_info:
for f in exch_info["filters"]:
if f["filterType"] == "PRICE_FILTER":
tick_size = float(f["tickSize"])
price_decimals = max(0, -int(math.log10(tick_size)) if tick_size > 0 else 2)
elif f["filterType"] == "LOT_SIZE":
lot_size = float(f["stepSize"])
raw["exchange"] = {"tick_size": tick_size, "lot_size": lot_size}
funding_data = self.client.funding_rate(symbol, limit=20)
funding_mean, funding_std, funding_pos = _compute_funding_stats(funding_data)
raw["funding"] = {"mean_bps": funding_mean, "std_bps": funding_std, "positive_pct": funding_pos}
oi_data = self.client.open_interest(symbol)
oi_value = float(oi_data.get("openInterest", 0)) * price if "openInterest" in oi_data else 0
raw["oi"] = {"value_usd": oi_value}
klass = _KNOWN_CLASSIFICATIONS.get(symbol, {})
if not klass:
warnings.append(f"No classification for {symbol} — using defaults")
klass = {"sector": "layer1", "role": "gas", "supply": "inflationary",
"consensus": "pos", "sc": "full", "deriv": "perps_only"}
sector_map = {"currency": Sector.CURRENCY, "layer1": Sector.LAYER1,
"layer2": Sector.LAYER2, "defi": Sector.DEFI, "oracle": Sector.ORACLE,
"exchange": Sector.EXCHANGE, "meme": Sector.MEME, "privacy": Sector.PRIVACY,
"storage": Sector.STORAGE, "gaming_nft": Sector.GAMING_NFT}
role_map = {"gas": TokenRole.GAS, "store_of_value": TokenRole.STORE_OF_VALUE,
"governance": TokenRole.GOVERNANCE, "utility": TokenRole.UTILITY,
"meme": TokenRole.MEME, "exchange_fee": TokenRole.EXCHANGE_FEE}
supply_map = {"fixed_cap": SupplyModel.FIXED_CAP, "disinflationary": SupplyModel.DISINFLATIONARY,
"inflationary": SupplyModel.INFLATIONARY, "burn_mechanism": SupplyModel.BURN_MECHANISM}
consensus_map = {"pow": ConsensusFamily.POW, "pos": ConsensusFamily.POS,
"dpos": ConsensusFamily.DPOS, "bft": ConsensusFamily.POS}
sc_map = {"full": SmartContractCapability.FULL, "partial": SmartContractCapability.PARTIAL,
"none": SmartContractCapability.NONE}
deriv_map = {"perps_and_options": DerivativeAccess.PERPS_AND_OPTIONS,
"perps_only": DerivativeAccess.PERPS_ONLY, "none": DerivativeAccess.NONE}
mcap_usd = vol_usd_24h * 100 # rough estimate from volume
if vol_usd_24h > 1e9:
mcap_est = vol_usd_24h * 2
elif vol_usd_24h > 100e6:
mcap_est = vol_usd_24h * 5
else:
mcap_est = vol_usd_24h * 10
asset_profile = AssetProfile(
symbol=symbol,
sectors=(sector_map.get(klass["sector"], Sector.LAYER1),),
token_roles=(role_map.get(klass["role"], TokenRole.GAS),),
supply_model=supply_map.get(klass["supply"], SupplyModel.INFLATIONARY),
consensus=consensus_map.get(klass["consensus"], ConsensusFamily.POS),
smart_contracts=sc_map.get(klass["sc"], SmartContractCapability.FULL),
market_cap_tier=_classify_market_cap(mcap_est),
volatility_profile=_classify_volatility(ann_vol),
liquidity_profile=_classify_liquidity(vol_usd_24h),
derivative_access=deriv_map.get(klass["deriv"], DerivativeAccess.PERPS_ONLY),
tick_size=tick_size, lot_size=lot_size, price_decimals=price_decimals,
maker_fee_bps=-0.2, taker_fee_bps=0.5,
typical_spread_bps=spread_bps, typical_depth_usd=depth_amp,
typical_daily_volume_usd=vol_usd_24h,
has_funding=True,
has_options=klass["deriv"] == "perps_and_options",
)
template_name = "mid_cap_l1"
if klass["sector"] in ("meme",):
template_name = "retail_meme"
elif klass["sector"] in ("currency",) and klass["consensus"] == "pow":
template_name = "institutional_blue_chip"
elif mcap_est > 50e9:
template_name = "institutional_blue_chip"
tmpl = TEMPLATES[template_name]
bingx_spread = spread_bps * 5.0 # conservative estimate
bingx_depth_ratio = 0.30
asset_behavior = AssetBehavior.from_template(
symbol, tmpl, {
"depth": DepthProfile(
amplitude_usd=depth_amp, alpha=depth_alpha,
fragility_factor=tmpl.depth.fragility_factor,
depth_at_10bps_usd=depth_amp * (10 ** (1 - depth_alpha)),
depth_at_100bps_usd=depth_amp * (100 ** (1 - depth_alpha)),
),
"spread": SpreadProfile(normal_bps=spread_bps, stress_multiplier=tmpl.spread.stress_multiplier),
"flow": FlowProfile(
orders_per_sec_normal=tmpl.flow.orders_per_sec_normal,
orders_per_sec_stress=tmpl.flow.orders_per_sec_stress,
cancel_fill_ratio=tmpl.flow.cancel_fill_ratio,
median_order_usd=flow["median_usd"],
p99_order_usd=flow["p99_usd"],
avg_trade_usd=flow["avg_usd"],
),
"vol": BehVol(
annualized_normal=ann_vol,
annualized_crisis=ann_vol * 2.5,
garch_alpha=tmpl.vol.garch_alpha,
garch_beta=tmpl.vol.garch_beta,
half_life_hours=tmpl.vol.half_life_hours,
),
"funding": FundingProfile(
mean_bps_8h=funding_mean, std_bps_8h=funding_std,
positive_pct=funding_pos, basis_typical_bps=abs(funding_mean) * 5,
),
"bingx": BingxProfile(
spread_mult=bingx_spread / max(spread_bps, 0.01),
depth_ratio=bingx_depth_ratio,
latency_ms=100, taker_fee_bps=5.0, maker_fee_bps=2.0,
funding_lag_hours=4,
),
"liquidation": LiquidationProfile(
oi_mcap_ratio=oi_value / max(mcap_est, 1),
trigger_pct=tmpl.liquidation.trigger_pct,
speed=tmpl.liquidation.speed,
recovery=tmpl.liquidation.recovery,
),
},
reference_price=price,
)
return CompileResult(
symbol=symbol,
asset_profile=asset_profile,
asset_behavior=asset_behavior,
raw_data=raw,
warnings=warnings,
)
def compile_batch(self, symbols: List[str]) -> List[CompileResult]:
"""Compile multiple assets sequentially (rate-limited)."""
results = []
for sym in symbols:
results.append(self.compile(sym))
return results
def register(self, result: CompileResult) -> bool:
"""Register compiled results into the global registries."""
if not result.asset_profile or not result.asset_behavior:
return False
ASSET_PROFILES[result.symbol] = result.asset_profile
ASSET_BEHAVIORS[result.symbol] = result.asset_behavior
return True
def compile_and_register(self, symbol: str) -> CompileResult:
"""Compile and register in one step."""
result = self.compile(symbol)
self.register(result)
return result

File diff suppressed because it is too large Load Diff