Files
sentiment-engine/MALKHUT/malkhut/training/asset_classification.py

720 lines
30 KiB
Python
Raw Normal View History

"""
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
typical_latency_ms: float
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 maker rebate
typical_latency_ms=40,
)
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,
)
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
typical_latency_ms=50,
)
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