malkhut: three-layer identifier architecture for cross-system asset identification
Layer 1 (canonical identity): symbol, base_asset, name, unified_symbol (CCXT format), quote_currency Layer 2 (cross-system): coingecko_id, cmc_id, blockchain, contract_address Layer 3 (exchange mapping): exchanges tuple All 13 pre-defined assets migrated with accurate CoinGecko IDs, CMC IDs, blockchains, and contract addresses (ERC-20 tokens). 7 new query functions: get_asset_by_coingecko_id, get_asset_by_cmc_id, get_assets_by_base_asset, get_assets_by_blockchain, get_assets_by_unified_symbol. 33 new tests covering: Layer 1 identity, Layer 2 cross-system identifiers, identifier query functions, identifier consistency (uniqueness, derivation), exchange registry. Total: 1189 tests, 47 files, all green. Based on research: CCXT BASE/QUOTE is de facto standard, CoinGecko ID most widely used in crypto-native, ISO 24165 DTI emerging, FIGI for institutional.
This commit is contained in:
@@ -398,7 +398,7 @@ simple doctrinal tick-exits (C11) ship first via T19 step 3; MALKHUT supersedes
|
||||
|
||||
## DEVELOPMENT STATUS (2026-07-11)
|
||||
|
||||
**1156 test functions. 47 test files. All green. 0 failures. 0 regressions.**
|
||||
**1189 test functions. 47 test files. All green. 0 failures. 0 regressions.**
|
||||
|
||||
### Completed subsystems
|
||||
|
||||
@@ -620,15 +620,23 @@ Regimes: `trending_up`, `trending_down`, `high_volatility`, `low_volatility`,
|
||||
|
||||
Multi-label asset classification using **only invariant characteristics** — properties
|
||||
intrinsic to the token that predict price behaviour and order-book performance.
|
||||
**Three-layer identifier architecture** for cross-system interoperability.
|
||||
|
||||
**Design principle:** Classify by WHAT an asset IS (fundamental), not how it's trading
|
||||
right now. Volatility/liquidity appear as long-run statistical profiles, not as
|
||||
classification axes that change minute-to-minute.
|
||||
|
||||
**Industry alignment:** CoinGecko/CMC use multi-tag taxonomy (ETH = "Smart Contracts" +
|
||||
"SEC/CFTC Token Taxonomy"). BNB = "CEX Token" + "Smart Contracts" + "Layer 1". Our
|
||||
system matches this: Sector and TokenRole are tuples (multi-label), first element =
|
||||
primary label for backward compatibility.
|
||||
#### Three-Layer Identifier Architecture
|
||||
|
||||
| Layer | Fields | Purpose | Example |
|
||||
|-------|--------|---------|---------|
|
||||
| **1. Canonical** | `symbol`, `base_asset`, `name`, `unified_symbol`, `quote_currency` | Venue-independent identity | BTCUSDT / BTC / "Bitcoin" / BTC/USDT / USDT |
|
||||
| **2. Cross-system** | `coingecko_id`, `cmc_id`, `blockchain`, `contract_address` | Data provider + on-chain mapping | "bitcoin" / 1 / "ethereum" / "" |
|
||||
| **3. Exchange mapping** | `exchanges: tuple[str, ...]` | Which venues trade this asset | ("binance", "bingx", "bybit") |
|
||||
|
||||
**Industry standards:** CoinGecko `id` (most widely used in crypto-native),
|
||||
CoinMarketCap numeric `id` (second most used), ISO 24165 DTI (emerging), FIGI
|
||||
(institutional), CCXT `BASE/QUOTE` format (de facto trading standard).
|
||||
|
||||
#### Fundamental Dimensions (intrinsic, never change)
|
||||
|
||||
|
||||
@@ -2,19 +2,24 @@
|
||||
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.
|
||||
predictive properties, ScenarioFactory integration, and layered identifiers.
|
||||
"""
|
||||
import pytest
|
||||
from malkhut.training.asset_classification import (
|
||||
Sector, TokenRole, SupplyModel, ConsensusFamily, SmartContractCapability,
|
||||
MarketCapTier, DerivativeAccess, VolatilityProfile, LiquidityProfile,
|
||||
AssetProfile, ASSET_PROFILES,
|
||||
ExchangeProfile, EXCHANGE_PROFILES, get_exchange, list_exchanges,
|
||||
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,
|
||||
get_assets_on_exchange, get_common_assets, get_exchange_for_asset,
|
||||
get_asset_by_coingecko_id, get_asset_by_cmc_id,
|
||||
get_assets_by_base_asset, get_assets_by_blockchain,
|
||||
get_assets_by_unified_symbol,
|
||||
)
|
||||
from malkhut.training.cma_trainer import ScenarioFactory
|
||||
|
||||
@@ -996,3 +1001,200 @@ class TestMultiAssetScenarios:
|
||||
suite = factory.build_suite(symbols=("ETHUSDT",), steps_per_scenario=10)
|
||||
for s in suite:
|
||||
assert s.max_steps >= 10
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Layered Identifier Architecture Tests
|
||||
# ==============================================================================
|
||||
|
||||
class TestLayeredIdentifiers:
|
||||
"""Layer 1: Canonical identity."""
|
||||
|
||||
def test_all_have_base_asset(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert p.base_asset, f'{p.symbol}: empty base_asset'
|
||||
|
||||
def test_all_have_name(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert p.name, f'{p.symbol}: empty name'
|
||||
|
||||
def test_all_have_unified_symbol(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert '/' in p.unified_symbol, f'{p.symbol}: unified missing /'
|
||||
base, quote = p.unified_symbol.split('/')
|
||||
assert base == p.base_asset
|
||||
assert quote == p.quote_currency
|
||||
|
||||
def test_btc_identity(self):
|
||||
p = get_asset_profile("BTCUSDT")
|
||||
assert p.base_asset == "BTC"
|
||||
assert p.name == "BTC"
|
||||
assert p.unified_symbol == "BTC/USDT"
|
||||
assert p.quote_currency == "USDT"
|
||||
|
||||
def test_eth_identity(self):
|
||||
p = get_asset_profile("ETHUSDT")
|
||||
assert p.base_asset == "ETH"
|
||||
assert p.unified_symbol == "ETH/USDT"
|
||||
|
||||
def test_doge_identity(self):
|
||||
p = get_asset_profile("DOGEUSDT")
|
||||
assert p.base_asset == "DOGE"
|
||||
assert p.unified_symbol == "DOGE/USDT"
|
||||
|
||||
|
||||
class TestCrossSystemIdentifiers:
|
||||
"""Layer 2: Cross-system identifiers."""
|
||||
|
||||
def test_all_have_coingecko_id(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert p.coingecko_id, f'{p.symbol}: empty coingecko_id'
|
||||
|
||||
def test_all_have_cmc_id(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert p.cmc_id > 0, f'{p.symbol}: invalid cmc_id'
|
||||
|
||||
def test_all_have_blockchain(self):
|
||||
for p in ASSET_PROFILES.values():
|
||||
assert p.blockchain, f'{p.symbol}: empty blockchain'
|
||||
|
||||
def test_btc_identifiers(self):
|
||||
p = get_asset_profile("BTCUSDT")
|
||||
assert p.coingecko_id == "bitcoin"
|
||||
assert p.cmc_id == 1
|
||||
assert p.blockchain == "bitcoin"
|
||||
assert p.contract_address == ""
|
||||
|
||||
def test_eth_identifiers(self):
|
||||
p = get_asset_profile("ETHUSDT")
|
||||
assert p.coingecko_id == "ethereum"
|
||||
assert p.cmc_id == 1027
|
||||
assert p.blockchain == "ethereum"
|
||||
|
||||
def test_uni_has_contract_address(self):
|
||||
p = get_asset_profile("UNIUSDT")
|
||||
assert p.contract_address.startswith("0x")
|
||||
assert p.blockchain == "ethereum"
|
||||
|
||||
def test_link_has_contract_address(self):
|
||||
p = get_asset_profile("LINKUSDT")
|
||||
assert p.contract_address.startswith("0x")
|
||||
|
||||
def test_native_coins_have_empty_address(self):
|
||||
"""Native chain coins (BTC, ETH, SOL, etc.) have no contract address."""
|
||||
for sym in ("BTCUSDT", "ETHUSDT", "SOLUSDT", "DOGEUSDT", "ADAUSDT",
|
||||
"AVAXUSDT", "DOTUSDT", "ATOMUSDT"):
|
||||
p = get_asset_profile(sym)
|
||||
assert p.contract_address == "", f'{sym}: native coin should have empty address'
|
||||
|
||||
|
||||
class TestIdentifierQueryFunctions:
|
||||
"""Query by layered identifiers."""
|
||||
|
||||
def test_coingecko_query(self):
|
||||
btc = get_asset_by_coingecko_id("bitcoin")
|
||||
assert btc is not None
|
||||
assert btc.symbol == "BTCUSDT"
|
||||
|
||||
def test_coingecko_miss(self):
|
||||
assert get_asset_by_coingecko_id("nonexistent") is None
|
||||
|
||||
def test_cmc_query(self):
|
||||
eth = get_asset_by_cmc_id(1027)
|
||||
assert eth is not None
|
||||
assert eth.symbol == "ETHUSDT"
|
||||
|
||||
def test_cmc_miss(self):
|
||||
assert get_asset_by_cmc_id(99999) is None
|
||||
|
||||
def test_base_asset_query(self):
|
||||
uni = get_assets_by_base_asset("UNI")
|
||||
assert len(uni) == 1
|
||||
assert uni[0].symbol == "UNIUSDT"
|
||||
|
||||
def test_blockchain_query(self):
|
||||
eth_chain = get_assets_by_blockchain("ethereum")
|
||||
symbols = {p.symbol for p in eth_chain}
|
||||
assert "ETHUSDT" in symbols
|
||||
assert "UNIUSDT" in symbols
|
||||
assert "LINKUSDT" in symbols
|
||||
assert "AAVEUSDT" in symbols
|
||||
assert "MATICUSDT" in symbols
|
||||
assert len(eth_chain) >= 5
|
||||
|
||||
def test_unified_symbol_query(self):
|
||||
p = get_assets_by_unified_symbol("BTC/USDT")
|
||||
assert p is not None
|
||||
assert p.symbol == "BTCUSDT"
|
||||
|
||||
def test_unified_symbol_miss(self):
|
||||
assert get_assets_by_unified_symbol("BTC/EUR") is None
|
||||
|
||||
|
||||
class TestIdentifierConsistency:
|
||||
"""Cross-checks: identifiers must be internally consistent."""
|
||||
|
||||
def test_symbol_matches_unified_base(self):
|
||||
"""symbol[:-len(quote)] should equal unified base."""
|
||||
for p in ASSET_PROFILES.values():
|
||||
expected_base = p.symbol.replace(p.quote_currency, "")
|
||||
assert p.base_asset == expected_base, \
|
||||
f'{p.symbol}: base_asset={p.base_asset} != expected {expected_base}'
|
||||
|
||||
def test_coingecko_ids_unique(self):
|
||||
ids = [p.coingecko_id for p in ASSET_PROFILES.values()]
|
||||
assert len(ids) == len(set(ids)), "Duplicate coingecko_ids"
|
||||
|
||||
def test_cmc_ids_unique(self):
|
||||
ids = [p.cmc_id for p in ASSET_PROFILES.values()]
|
||||
assert len(ids) == len(set(ids)), "Duplicate cmc_ids"
|
||||
|
||||
def test_blockchain_grouping(self):
|
||||
"""Same-chain assets should share blockchain field."""
|
||||
btc_chain = get_assets_by_blockchain("bitcoin")
|
||||
assert len(btc_chain) == 1 # only BTC
|
||||
|
||||
eth_chain = get_assets_by_blockchain("ethereum")
|
||||
assert len(eth_chain) >= 4 # ETH, UNI, LINK, MATIC, AAVE
|
||||
|
||||
|
||||
class TestExchangeRegistry:
|
||||
"""ExchangeProfile tests."""
|
||||
|
||||
def test_exchanges_registered(self):
|
||||
assert len(EXCHANGE_PROFILES) >= 3
|
||||
|
||||
def test_binance_profile(self):
|
||||
ex = get_exchange("binance")
|
||||
assert ex is not None
|
||||
assert ex.display_name == "Binance"
|
||||
assert ex.has_spot
|
||||
assert ex.has_perps
|
||||
assert ex.has_options
|
||||
assert ex.default_taker_fee_bps > 0
|
||||
assert ex.typical_latency_ms > 0
|
||||
|
||||
def test_bingx_profile(self):
|
||||
ex = get_exchange("bingx")
|
||||
assert ex is not None
|
||||
assert ex.display_name == "BingX"
|
||||
assert not ex.has_options
|
||||
|
||||
def test_bybit_profile(self):
|
||||
ex = get_exchange("bybit")
|
||||
assert ex is not None
|
||||
assert ex.has_options
|
||||
|
||||
def test_list_exchanges(self):
|
||||
exs = list_exchanges()
|
||||
assert "binance" in exs
|
||||
assert "bingx" in exs
|
||||
assert "bybit" in exs
|
||||
|
||||
def test_get_exchange_miss(self):
|
||||
assert get_exchange("nonexistent") is None
|
||||
|
||||
def test_exchange_fields_frozen(self):
|
||||
for ex in EXCHANGE_PROFILES.values():
|
||||
with pytest.raises(AttributeError):
|
||||
ex.display_name = "X"
|
||||
|
||||
@@ -69,8 +69,18 @@ EXCHANGE_PROFILES["okx"] = ExchangeProfile(
|
||||
```python
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AssetProfile:
|
||||
# Identity
|
||||
symbol: str # "BTCUSDT"
|
||||
# --- Layer 1: Canonical identity (venue-independent) ---
|
||||
symbol: str # "BTCUSDT" — internal canonical ID
|
||||
base_asset: str # "BTC" — asset without quote
|
||||
name: str # "Bitcoin" — human-readable
|
||||
unified_symbol: str # "BTC/USDT" — CCXT format
|
||||
quote_currency: str # "USDT" — settlement currency
|
||||
|
||||
# --- Layer 2: Cross-system identifiers ---
|
||||
coingecko_id: str # "bitcoin" — CoinGecko slug
|
||||
cmc_id: int # 1 — CoinMarketCap numeric ID
|
||||
blockchain: str # "bitcoin" — native chain
|
||||
contract_address: str # "0x..." for ERC-20, "" for native coins
|
||||
|
||||
# Fundamental (intrinsic, never change)
|
||||
sectors: tuple[Sector, ...] # ("CURRENCY",)
|
||||
@@ -101,7 +111,7 @@ class AssetProfile:
|
||||
has_funding: bool = False
|
||||
has_options: bool = False
|
||||
|
||||
# Exchange membership
|
||||
# --- Layer 3: Exchange membership ---
|
||||
exchanges: tuple[str, ...] = ("binance",) # which venues trade this
|
||||
```
|
||||
|
||||
@@ -118,6 +128,11 @@ class AssetProfile:
|
||||
|----------|---------|
|
||||
| `get_asset_profile(symbol)` | Single profile or None |
|
||||
| `list_assets()` | All symbols |
|
||||
| `get_asset_by_coingecko_id(id)` | Profile by CoinGecko slug |
|
||||
| `get_asset_by_cmc_id(id)` | Profile by CMC numeric ID |
|
||||
| `get_assets_by_base_asset(base)` | All profiles for a base asset |
|
||||
| `get_assets_by_blockchain(chain)` | Assets on a given chain |
|
||||
| `get_assets_by_unified_symbol(sym)` | Profile by CCXT unified symbol |
|
||||
| `get_assets_by_sector(sector)` | Assets in ANY of the queried sector |
|
||||
| `get_assets_by_token_role(role)` | Assets with ANY of the queried role |
|
||||
| `get_assets_by_supply(model)` | Assets with given supply model |
|
||||
|
||||
@@ -191,10 +191,30 @@ def list_exchanges() -> List[str]:
|
||||
|
||||
@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
|
||||
"""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.
|
||||
@@ -290,12 +310,43 @@ def _profile(
|
||||
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,
|
||||
@@ -333,6 +384,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="bitcoin", cmc_id=1, blockchain="bitcoin", contract_address="",
|
||||
has_funding=True, has_options=True,
|
||||
),
|
||||
"ETHUSDT": _profile(
|
||||
@@ -349,6 +401,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="ethereum", cmc_id=1027, blockchain="ethereum", contract_address="",
|
||||
has_funding=True, has_options=True,
|
||||
),
|
||||
"SOLUSDT": _profile(
|
||||
@@ -365,6 +418,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="solana", cmc_id=5426, blockchain="solana", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
"DOGEUSDT": _profile(
|
||||
@@ -381,6 +435,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="dogecoin", cmc_id=74, blockchain="dogecoin", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
"ADAUSDT": _profile(
|
||||
@@ -397,6 +452,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="cardano", cmc_id=2010, blockchain="cardano", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
"AVAXUSDT": _profile(
|
||||
@@ -413,6 +469,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="avalanche-2", cmc_id=5805, blockchain="avalanche", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
"UNIUSDT": _profile(
|
||||
@@ -429,6 +486,8 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="uniswap", cmc_id=7083, blockchain="ethereum",
|
||||
contract_address="0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
|
||||
has_funding=True,
|
||||
),
|
||||
"LINKUSDT": _profile(
|
||||
@@ -445,6 +504,8 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="chainlink", cmc_id=1975, blockchain="ethereum",
|
||||
contract_address="0x514910771af9ca656af840dff83e8264ecf986ca",
|
||||
has_funding=True,
|
||||
),
|
||||
"BNBUSDT": _profile(
|
||||
@@ -461,6 +522,8 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="binancecoin", cmc_id=1839, blockchain="binance",
|
||||
contract_address="0xfb6312216814d55f56bee942ae2c4d04f1241e6b",
|
||||
has_funding=True,
|
||||
),
|
||||
"MATICUSDT": _profile(
|
||||
@@ -477,6 +540,8 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="matic-network", cmc_id=3890, blockchain="ethereum",
|
||||
contract_address="0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
|
||||
has_funding=True,
|
||||
),
|
||||
"AAVEUSDT": _profile(
|
||||
@@ -493,6 +558,8 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="aave", cmc_id=7278, blockchain="ethereum",
|
||||
contract_address="0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9",
|
||||
has_funding=True,
|
||||
),
|
||||
"DOTUSDT": _profile(
|
||||
@@ -509,6 +576,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="polkadot", cmc_id=6636, blockchain="polkadot", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
"ATOMUSDT": _profile(
|
||||
@@ -525,6 +593,7 @@ ASSET_PROFILES: Dict[str, AssetProfile] = {
|
||||
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,
|
||||
coingecko_id="cosmos", cmc_id=3794, blockchain="cosmos", contract_address="",
|
||||
has_funding=True,
|
||||
),
|
||||
}
|
||||
@@ -614,3 +683,37 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user