""" Asset Classification — INVARIANT characteristics only. System-wide asset universe. 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, Plus: EXCHANGE — multi-exchange support: ExchangeProfile (per-exchange metadata), exchange-asset mapping (which exchanges trade which assets, with exchange-specific parameters). 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 # ============================================================================== # ExchangeProfile — multi-exchange support # ============================================================================== @dataclass(frozen=True, slots=True) class ExchangeProfile: """Metadata for a trading venue. System-wide — used by BLUE/VIOLET/UV/MALKHUT.""" exchange_id: str # canonical key: "binance", "bingx", "bybit", etc. display_name: str # human-readable: "Binance", "BingX" has_spot: bool has_perps: bool has_options: bool api_base_url: str # REST API root ws_base_url: str # WebSocket root ("" if not applicable) default_taker_fee_bps: float default_maker_fee_bps: float # UNVERIFIED — no maker fills on record as of 2026-07-13 typical_latency_ms: float available_order_types: tuple[str, ...] = ("limit", "market") # normalized types this venue supports EXCHANGE_PROFILES: Dict[str, ExchangeProfile] = {} BINANCE = ExchangeProfile( exchange_id="binance", display_name="Binance", has_spot=True, has_perps=True, has_options=True, api_base_url="https://api.binance.com", ws_base_url="wss://stream.binance.com:9443", default_taker_fee_bps=4.5, default_maker_fee_bps=-0.2, # UNVERIFIED — no maker fills on record as of 2026-07-13 typical_latency_ms=40, available_order_types=("limit", "market", "stop_market", "stop_limit", "post_only", "ioc", "fok", "trailing_stop", "reduce_only"), ) EXCHANGE_PROFILES["binance"] = BINANCE BINGX = ExchangeProfile( exchange_id="bingx", display_name="BingX", has_spot=True, has_perps=True, has_options=False, api_base_url="https://open-api.bingx.com", ws_base_url="wss://open-api.bingx.com/swapMarket", default_taker_fee_bps=5.0, default_maker_fee_bps=2.0, # BingX maker is POSITIVE typical_latency_ms=100, available_order_types=("limit", "market", "stop_market", "stop_limit", "post_only", "ioc", "fok", "trailing_stop", "reduce_only"), ) EXCHANGE_PROFILES["bingx"] = BINGX BYBIT = ExchangeProfile( exchange_id="bybit", display_name="Bybit", has_spot=True, has_perps=True, has_options=True, api_base_url="https://api.bybit.com", ws_base_url="wss://stream.bybit.com/v5/public/linear", default_taker_fee_bps=5.5, default_maker_fee_bps=-0.1, # UNVERIFIED — no maker fills on record as of 2026-07-13 typical_latency_ms=50, available_order_types=("limit", "market", "stop_market", "stop_limit", "post_only", "ioc", "fok", "trailing_stop", "reduce_only"), ) EXCHANGE_PROFILES["bybit"] = BYBIT def get_exchange(exchange_id: str) -> Optional[ExchangeProfile]: return EXCHANGE_PROFILES.get(exchange_id) def list_exchanges() -> List[str]: return list(EXCHANGE_PROFILES.keys()) # ============================================================================== # AssetProfile — frozen, all-invariant, multi-label where appropriate # ============================================================================== @dataclass(frozen=True, slots=True) class AssetProfile: """Immutable asset classification. Three-layer identifier architecture: Layer 1 — Canonical identity (venue-independent): symbol, base_asset, name Layer 2 — Cross-system identifiers: coingecko_id, cmc_id, blockchain, contract_address Layer 3 — Exchange market mapping: unified_symbol, quote_currency, exchanges Sector & TokenRole are tuples (multi-label). First element = primary label.""" # --- Layer 1: Canonical identity --- symbol: str # Internal canonical ID: "BTCUSDT" base_asset: str # Asset without quote: "BTC" name: str # Human-readable: "Bitcoin" unified_symbol: str # CCXT format: "BTC/USDT" quote_currency: str # Settlement: "USDT" # --- Layer 2: Cross-system identifiers --- coingecko_id: str # "bitcoin" — most widely used in crypto-native cmc_id: int # 1 — CoinMarketCap numeric ID blockchain: str # Native chain: "bitcoin", "ethereum", "solana" contract_address: str # "0x..." for ERC-20, "" for native chain coins # --- 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 # --- Exchange membership (which venues trade this asset) --- exchanges: tuple[str, ...] = ("binance",) # tuple of exchange_ids @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, base_asset: str = "", name: str = "", unified_symbol: str = "", quote_currency: str = "USDT", coingecko_id: str = "", cmc_id: int = 0, blockchain: str = "", contract_address: str = "", has_funding: bool = False, has_options: bool = False, exchanges: Sequence[str] = ("binance",), ) -> AssetProfile: # Auto-derive from symbol if not provided if not base_asset: # Strip quote currency suffix to get base asset for q in ("USDT", "USDC", "BUSD", "BTC", "ETH", "USD"): if symbol.endswith(q): base_asset = symbol[:-len(q)] break if not base_asset: base_asset = symbol if not name: name = base_asset if not unified_symbol: unified_symbol = f"{base_asset}/{quote_currency}" if not blockchain: blockchain = base_asset.lower() return AssetProfile( symbol=symbol, base_asset=base_asset, name=name, unified_symbol=unified_symbol, quote_currency=quote_currency, coingecko_id=coingecko_id or name.lower(), cmc_id=cmc_id, blockchain=blockchain, contract_address=contract_address, 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, exchanges=tuple(exchanges), ) # ============================================================================== # 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=2.0, taker_fee_bps=5.0, typical_spread_bps=0.3, typical_depth_usd=5_000_000, typical_daily_volume_usd=30_000_000_000, coingecko_id="bitcoin", cmc_id=1, blockchain="bitcoin", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=0.5, typical_depth_usd=3_000_000, typical_daily_volume_usd=15_000_000_000, coingecko_id="ethereum", cmc_id=1027, blockchain="ethereum", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.0, typical_depth_usd=1_000_000, typical_daily_volume_usd=3_000_000_000, coingecko_id="solana", cmc_id=5426, blockchain="solana", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=2.0, typical_depth_usd=500_000, typical_daily_volume_usd=1_000_000_000, coingecko_id="dogecoin", cmc_id=74, blockchain="dogecoin", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.0, typical_depth_usd=800_000, typical_daily_volume_usd=500_000_000, coingecko_id="cardano", cmc_id=2010, blockchain="cardano", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.2, typical_depth_usd=600_000, typical_daily_volume_usd=400_000_000, coingecko_id="avalanche-2", cmc_id=5805, blockchain="avalanche", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=2.0, typical_depth_usd=300_000, typical_daily_volume_usd=200_000_000, coingecko_id="uniswap", cmc_id=7083, blockchain="ethereum", contract_address="0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.0, typical_depth_usd=700_000, typical_daily_volume_usd=400_000_000, coingecko_id="chainlink", cmc_id=1975, blockchain="ethereum", contract_address="0x514910771af9ca656af840dff83e8264ecf986ca", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=0.5, typical_depth_usd=2_000_000, typical_daily_volume_usd=2_000_000_000, coingecko_id="binancecoin", cmc_id=1839, blockchain="binance", contract_address="0xfb6312216814d55f56bee942ae2c4d04f1241e6b", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.5, typical_depth_usd=500_000, typical_daily_volume_usd=300_000_000, coingecko_id="matic-network", cmc_id=3890, blockchain="ethereum", contract_address="0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=2.5, typical_depth_usd=200_000, typical_daily_volume_usd=150_000_000, coingecko_id="aave", cmc_id=7278, blockchain="ethereum", contract_address="0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=1.0, typical_depth_usd=600_000, typical_daily_volume_usd=300_000_000, coingecko_id="polkadot", cmc_id=6636, blockchain="polkadot", contract_address="", 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=2.0, taker_fee_bps=5.0, typical_spread_bps=2.0, typical_depth_usd=250_000, typical_daily_volume_usd=100_000_000, coingecko_id="cosmos", cmc_id=3794, blockchain="cosmos", contract_address="", 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] def get_assets_on_exchange(exchange_id: str) -> List[AssetProfile]: """Assets traded on a given exchange.""" return [p for p in ASSET_PROFILES.values() if exchange_id in p.exchanges] def get_common_assets(exchange_a: str, exchange_b: str) -> List[AssetProfile]: """Assets traded on BOTH exchanges (intersection).""" return [p for p in ASSET_PROFILES.values() if exchange_a in p.exchanges and exchange_b in p.exchanges] def get_exchange_for_asset(symbol: str) -> tuple[str, ...]: """Return which exchanges trade a given asset.""" p = ASSET_PROFILES.get(symbol) return p.exchanges if p else () def get_asset_by_coingecko_id(coingecko_id: str) -> Optional[AssetProfile]: """Look up asset by CoinGecko ID (e.g., 'bitcoin', 'ethereum').""" for p in ASSET_PROFILES.values(): if p.coingecko_id == coingecko_id: return p return None def get_asset_by_cmc_id(cmc_id: int) -> Optional[AssetProfile]: """Look up asset by CoinMarketCap numeric ID.""" for p in ASSET_PROFILES.values(): if p.cmc_id == cmc_id: return p return None def get_assets_by_base_asset(base_asset: str) -> List[AssetProfile]: """Find all profiles for a base asset (e.g., 'BTC' → BTCUSDT).""" return [p for p in ASSET_PROFILES.values() if p.base_asset == base_asset] def get_assets_by_blockchain(blockchain: str) -> List[AssetProfile]: """Find all assets on a given blockchain.""" return [p for p in ASSET_PROFILES.values() if p.blockchain == blockchain] def get_assets_by_unified_symbol(unified: str) -> Optional[AssetProfile]: """Look up by CCXT unified symbol (e.g., 'BTC/USDT').""" for p in ASSET_PROFILES.values(): if p.unified_symbol == unified: return p return None