# developer: Taoshi from datetime import datetime, timezone import os import math from enum import Enum from meta import load_version from shared_objects.log import logger BASE_DIR = base_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) meta_dict = load_version(os.path.join(base_directory, "meta", "meta.json")) if meta_dict is None: # Databricks print('Unable to load meta_dict. This is expected if running on Databricks.') meta_version = "x.x.x" else: meta_version = meta_dict.get("subnet_version", "x.x.x") class RPCConnectionMode(int, Enum): """ Connection mode for RPC clients/servers. LOCAL: Direct mode - bypass RPC, use set_direct_server() for in-process communication. Use this for tests that need to verify logic without RPC overhead. RPC: Normal RPC mode - connect via network. Use this for production and integration tests that need full RPC behavior. Usage: # Test without RPC (fastest, no network) client = MyClient(connection_mode=RPCConnectionMode.LOCAL) client.set_direct_server(server_instance) # Test with real RPC (like production) server = MyServer(connection_mode=RPCConnectionMode.RPC) # Starts RPC server client = MyClient(connection_mode=RPCConnectionMode.RPC) # Connects via RPC """ LOCAL = 0 # Direct mode - bypass RPC, use set_direct_server() RPC = 1 # Normal RPC mode - connect via network from vali_objects.enums.miner_asset_class_enum import MinerAssetClass # noqa: E402,F401 # Re-export TradePair classes for backwards compatibility. # Per-trade-pair constants (leverage, BLOCKED/FLAT_ONLY id sets, lookup dicts) live in # vali_objects.trade_pair and should be imported from there directly. from vali_objects.trade_pair import ( # noqa: E402,F401 TradePair, TradePairCategory, TradePairSource, InstrumentType, StandardLeverageGroup, ) class InterpolatedValueFromDate(): """ Dynamic value based on dates. Used for setting configs in the future. """ def __init__(self, start_date: str, *, low: int=None, high:int=None, interval: int, increment: int, target: int): self.start_date = datetime.strptime(start_date, "%Y-%m-%d").replace(tzinfo=timezone.utc) self.low = low self.high = high self.interval = interval self.increment = increment self.target = target def value(self): days_since_start = (datetime.now(tz=timezone.utc) - self.start_date).days intervals = max(0, days_since_start // self.interval) if self.low is not None: new_n = self.low + abs(self.increment) * intervals return min(self.target, new_n) else: new_n = self.high - abs(self.increment) * intervals return max(self.target, new_n) # These knobs move consensus (promotion decisions and weekly penalties feed the weight # calculator), so a stray environment variable must never quietly change a validator's # view. Reading one requires an explicit opt-in, and neurons/validator.py refuses to # start on mainnet with any of them present. _ENV_OVERRIDES_ALLOWED = os.environ.get("PTN_ALLOW_CONFIG_OVERRIDES", "").strip() == "1" def _env_override(name: str, default, cast=float, maximum=None): """Testnet knob: read a positive number from the environment variable `name` once at import. Ignored unless `PTN_ALLOW_CONFIG_OVERRIDES=1` is set (testnet only). Unset (or blank) keeps `default`; anything else must parse as a positive number no larger than `maximum`. """ raw = os.environ.get(name) if raw is None or raw.strip() == "": return default if not _ENV_OVERRIDES_ALLOWED: raise ValueError( f"{name} is set but PTN_ALLOW_CONFIG_OVERRIDES=1 is not. {name} changes " f"consensus-affecting config; unset it or opt in explicitly (testnet only)." ) try: value = cast(raw) except ValueError as e: raise ValueError(f"{name}={raw!r} must be a positive number") from e if not math.isfinite(value) or value <= 0: raise ValueError(f"{name}={raw!r} must be a positive number") if maximum is not None and value > maximum: raise ValueError(f"{name}={raw!r} must be at most {maximum}") logger.warning(f"[VALI_CONFIG] {name} overridden by environment: {value} (default {default})") return value class ValiConfig: # versioning VERSION = meta_version # minimum required vanta-cli version VANTA_CLI_MINIMUM_VERSION = "2.2.1" DAYS_IN_YEAR_CRYPTO = 365 # annualization factor DAYS_IN_YEAR_FOREX = 252 DAYS_IN_YEAR_EQUITIES = 252 # Development hotkey for testing DEVELOPMENT_HOTKEY = "DEVELOPMENT" # RPC Service Configuration # Centralized port and service name definitions to avoid conflicts and inconsistencies # All RPC services are defined here to prevent port conflicts and ensure consistent authkey generation # Core Manager Services RPC_LIVEPRICEFETCHER_PORT = 50000 RPC_LIVEPRICEFETCHER_SERVICE_NAME = "LivePriceFetcherServer" RPC_LIMITORDERMANAGER_PORT = 50001 RPC_LIMITORDERMANAGER_SERVICE_NAME = "LimitOrderServer" RPC_POSITIONMANAGER_PORT = 50002 RPC_POSITIONMANAGER_SERVICE_NAME = "PositionManagerServer" RPC_CHALLENGEPERIOD_PORT = 50003 RPC_CHALLENGEPERIOD_SERVICE_NAME = "ChallengePeriodServer" RPC_ELIMINATION_PORT = 50004 RPC_ELIMINATION_SERVICE_NAME = "EliminationServer" RPC_METAGRAPH_PORT = 50005 RPC_METAGRAPH_SERVICE_NAME = "MetagraphServer" RPC_MINERSTATS_PORT = 50006 RPC_MINERSTATS_SERVICE_NAME = "MinerStatsServer" RPC_COREOUTPUTS_PORT = 50007 RPC_COREOUTPUTS_SERVICE_NAME = "CoreOutputsServer" # Utility Services RPC_POSITIONLOCK_PORT = 50008 RPC_POSITIONLOCK_SERVICE_NAME = "PositionLockServer" RPC_DEBTLEDGER_PORT = 50009 RPC_DEBTLEDGER_SERVICE_NAME = "DebtLedgerServer" RPC_ASSETSELECTION_PORT = 50010 RPC_ASSETSELECTION_SERVICE_NAME = "AssetSelectionServer" RPC_CONTRACTMANAGER_PORT = 50011 RPC_CONTRACTMANAGER_SERVICE_NAME = "ValidatorContractServer" RPC_MINERSTATISTICS_PORT = 50012 RPC_MINERSTATISTICS_SERVICE_NAME = "MinerStatisticsServer" RPC_REQUESTCORE_PORT = 50013 RPC_REQUESTCORE_SERVICE_NAME = "RequestCoreServer" RPC_WEBSOCKET_NOTIFIER_PORT = 50014 RPC_WEBSOCKET_NOTIFIER_SERVICE_NAME = "WebSocketNotifierServer" RPC_WEIGHT_SETTER_PORT = 50015 RPC_WEIGHT_SETTER_SERVICE_NAME = "WeightSetterServer" RPC_PERFLEDGER_PORT = 50016 RPC_PERFLEDGER_SERVICE_NAME = "PerfLedgerServer" RPC_PLAGIARISM_PORT = 50017 RPC_PLAGIARISM_SERVICE_NAME = "PlagiarismServer" RPC_PLAGIARISM_DETECTOR_PORT = 50018 RPC_PLAGIARISM_DETECTOR_SERVICE_NAME = "PlagiarismDetectorServer" RPC_COMMONDATA_PORT = 50019 RPC_COMMONDATA_SERVICE_NAME = "CommonDataServer" RPC_MDDCHECKER_PORT = 50020 RPC_MDDCHECKER_SERVICE_NAME = "MDDCheckerServer" RPC_WEIGHT_CALCULATOR_PORT = 50021 RPC_WEIGHT_CALCULATOR_SERVICE_NAME = "WeightCalculatorServer" RPC_REST_SERVER_PORT = 50022 RPC_REST_SERVER_SERVICE_NAME = "VantaRestServer" RPC_MINERACCOUNT_PORT = 50023 RPC_MINERACCOUNT_SERVICE_NAME = "MinerAccountServer" RPC_ENTITY_PORT = 50024 RPC_ENTITY_SERVICE_NAME = "EntityServer" RPC_HL_FUNDING_PORT = 50025 RPC_HL_FUNDING_SERVICE_NAME = "HLFundingRateServer" RPC_ENTITY_COLLATERAL_PORT = 50026 RPC_ENTITY_COLLATERAL_SERVICE_NAME = "EntityCollateralServer" RPC_MARKET_ORDER_PORT = 50027 RPC_MARKET_ORDER_SERVICE_NAME = "MarketOrderServer" # Entity collateral cache refresh interval (seconds) ENTITY_COLLATERAL_CACHE_REFRESH_S = 30 * 60 # Public API Configuration (well-known network endpoints) REST_API_HOST = "127.0.0.1" REST_API_PORT = 48888 VANTA_WEBSOCKET_HOST = "localhost" VANTA_WEBSOCKET_PORT = 8765 @staticmethod def get_rpc_authkey(service_name: str, port: int) -> bytes: """ Generate RPC authkey for a service. Args: service_name: Service name (e.g., "ChallengePeriodManagerServer") port: Port number (e.g., 50003) Returns: bytes: 32-byte authkey for RPC authentication """ import hashlib return hashlib.sha256(f"{service_name}_{port}".encode()).digest()[:32] # Min number of trading days required for scoring STATISTICAL_CONFIDENCE_MINIMUM_N_CEIL = 60 STATISTICAL_CONFIDENCE_MINIMUM_N_FLOOR = 7 # Dynamic minimum days calculation - use Nth longest participating miner as threshold DYNAMIC_MIN_DAYS_NUM_MINERS = 20 # Market-specific configurations ANNUAL_RISK_FREE_PERCENTAGE = 3.89 # From tbill rates ANNUAL_RISK_FREE_DECIMAL = ANNUAL_RISK_FREE_PERCENTAGE / 100 DAILY_LOG_RISK_FREE_RATE_CRYPTO = math.log(1 + ANNUAL_RISK_FREE_DECIMAL) / DAYS_IN_YEAR_CRYPTO DAILY_LOG_RISK_FREE_RATE_FOREX = math.log(1 + ANNUAL_RISK_FREE_DECIMAL) / DAYS_IN_YEAR_FOREX MS_RISK_FREE_RATE = math.log(1 + ANNUAL_RISK_FREE_PERCENTAGE / 100) / (365 * 24 * 60 * 60 * 1000) # Asset Class Breakdown - defines the total emission for each asset class ASSET_CLASS_BREAKDOWN = { TradePairCategory.CRYPTO: { "emission": 0.334, # Total emission for crypto "days_in_year": DAYS_IN_YEAR_CRYPTO, }, # These are based on margin requirements on brokerage accounts TradePairCategory.FOREX: { "emission": 0.333, # Total emission for forex "days_in_year": DAYS_IN_YEAR_FOREX, }, TradePairCategory.EQUITIES: { "emission": 0.333, # Total emission for equities "days_in_year": DAYS_IN_YEAR_CRYPTO, }, } # Annualization factor per miner asset class (ASSET_CLASS_BREAKDOWN is keyed by trade pair category) #TODO might be unnecessary MINER_ASSET_CLASS_DAYS_IN_YEAR = { MinerAssetClass.CRYPTO: DAYS_IN_YEAR_CRYPTO, MinerAssetClass.FOREX: DAYS_IN_YEAR_FOREX, MinerAssetClass.EQUITIES: DAYS_IN_YEAR_CRYPTO, MinerAssetClass.COMMODITIES: DAYS_IN_YEAR_CRYPTO, MinerAssetClass.HL_ALL: DAYS_IN_YEAR_CRYPTO, MinerAssetClass.ALL_MARKETS: DAYS_IN_YEAR_CRYPTO, } # Time Configurations TARGET_CHECKPOINT_DURATION_MS = 1000 * 60 * 60 * 12 # 12 hours DAILY_MS = 1000 * 60 * 60 * 24 # 1 day DAILY_CHECKPOINTS = DAILY_MS // TARGET_CHECKPOINT_DURATION_MS # 2 checkpoints per day # Set the target ledger window in days directly TARGET_LEDGER_WINDOW_DAYS = 1000 TARGET_LEDGER_WINDOW_MS = TARGET_LEDGER_WINDOW_DAYS * DAILY_MS WEIGHTED_AVERAGE_DECAY_RATE = 0.075 WEIGHTED_AVERAGE_DECAY_MIN = 0.15 WEIGHTED_AVERAGE_DECAY_MAX = 1.0 # Decay min specific for daily average PnL calculations WEIGHTED_AVERAGE_DECAY_MIN_PNL = 0.045 # Results in most recent 30 days having 70% weight POSITIONAL_EQUIVALENCE_WINDOW_MS = 1000 * 60 * 60 * 24 # 1 day SET_WEIGHT_REFRESH_TIME_MS = 60 * 5 * 1000 # 5 minutes SET_WEIGHT_LOOKBACK_RANGE_DAYS = TARGET_LEDGER_WINDOW_DAYS # Fees take into account exiting and entering a position, liquidity, and futures fees PERF_LEDGER_REFRESH_TIME_MS = 1000 * 60 * 5 # minutes MDD_CHECK_REFRESH_TIME_MS = 30 * 1000 # 30 seconds CHALLENGE_PERIOD_REFRESH_TIME_MS = MDD_CHECK_REFRESH_TIME_MS PRICE_SOURCE_COMPACTING_SLEEP_INTERVAL_SECONDS = 60 * 60 * 12 # 12 hours # HL dynamic universe — HS position leverage mapping HL_HIGH_TIER_THRESHOLD = 50 # HL max lev at which HS high tier applies HS_HIGH_TIER_MAX_LEVERAGE = 5.0 # intended for forex/spx-tier (HL max lev 50x) pairs; dead code since forex pairs are excluded for now HS_PORTFOLIO_MAX_LEVERAGE = 4.0 # HS portfolio-level leverage cap (funded accounts) # Minimum position size FOREX_MIN_POSITION_SIZE_LOTS = 0.01 # micro lot — subaccounts above FOREX_SMALL_ACCOUNT_THRESHOLD FOREX_MIN_POSITION_SIZE_LOTS_NANO = 0.001 # nano lot — deprecated; no account tier currently uses this FOREX_MIN_POSITION_SIZE_LOTS_SUB_NANO = 0.0001 # sub-nano lot — subaccounts at or below FOREX_SMALL_ACCOUNT_THRESHOLD FOREX_SMALL_ACCOUNT_THRESHOLD = 10_000.0 # USD; subaccounts at or below this use sub-nano lot minimum CRYPTO_MIN_POSITION_SIZE_USD = 10.0 # $10 USD EQUITIES_MIN_POSITION_SIZE_SHARES = 0.01 # 0.01 shares DEFAULT_MIN_POSITION_SIZE_USD = 10.0 # Minimum order size in quantity (different from minimum position size ex. crypto) CRYPTO_MIN_ORDER_SIZE = 0.00001 COMMODITIES_MIN_ORDER_SIZE = 0.00001 EQUITIES_MIN_ORDER_SIZE = 0.00001 FOREX_MIN_ORDER_SIZE = 0.01 FOREX_MIN_ORDER_SIZE_SUB_NANO = 0.0001 MAX_DAILY_DRAWDOWN = 0.95 # Portfolio should never fall below .95 x of initial value when measured day to day MAX_TOTAL_DRAWDOWN = 0.9 # Portfolio should never fall below .90 x of initial value when measured at any instant MAX_TOTAL_DRAWDOWN_V2 = 0.95 MAX_ORDERS_PER_POSITION = 1000 # High value that realistically should never be reached ORDER_COOLDOWN_MS = 5000 # 5 seconds # Cross-process order/sync coordination (spec R2.4): an in-flight order registration on # CommonDataServer older than this is treated as abandoned (the producer — e.g. vanta-orders — # crashed mid-order) and reaped, so it can't block position sync forever. Must comfortably # exceed the worst-case real order-processing time — which is NOT "~seconds": a hung (not dead) # state server blocks the apply silently (BaseManager calls have no transport timeout), and # lock waits (10s/attempt) plus RPC self-heal cycles stack on top. Reaping a live order lets # the checkpoint-wide sync rewrite race that order's eventual position write. Sync is # infrequent (daily), so waiting minutes for a straggler is far cheaper than that race. ORDER_INFLIGHT_TTL_MS = 300_000 # 5 minutes # Server-side order-UUID dedup (spec R2.6): FIFO capacity of the authoritative dedup set on # CommonDataServer that replaces the process-local UUIDTracker. Matches UUIDTracker's historic # 100k. Dedup only needs to cover the retry/replay window (seconds–minutes), not forever; # oldest uuids are evicted past capacity. ORDER_UUID_DEDUP_CAPACITY = 100_000 # A claimed-but-unconfirmed order uuid (check_and_add taken, confirm/release never called — # the claimant was hard-killed mid-apply) expires after this long, so a miner's retry of an # order that never committed is not permanently rejected as a duplicate. Confirmed uuids # (post-commit) never expire this way — they live in the FIFO dedup set. Must exceed the # longest real apply (lock wait + RPC self-heal cycles + price fetches). ORDER_UUID_CLAIM_TTL_MS = 300_000 # 5 minutes # Lease on the cross-process sync gate (sync_waiting on CommonDataServer): the sync side # (core) renews it via a heartbeat while its sync runs; if core is hard-killed mid-sync the # renewals stop and the gate auto-clears after this long, instead of rejecting every order # network-wide until the next successful sync (potentially ~24h). Must exceed the heartbeat # interval (60s) by enough to ride out core GC pauses / brief stalls. SYNC_WAITING_LEASE_MS = 300_000 # 5 minutes SYNC_LEASE_RENEW_INTERVAL_S = 60 # core heartbeat cadence while a sync holds the gate # Position-lock lease (never-release protection): a server-side (hotkey, trade_pair) lock held # longer than this is presumed abandoned (holder crashed between acquire and release) and # force-reclaimed on the next acquire. Must sit FAR above the worst LEGITIMATE hold — which is # minutes, not seconds: mdd_checker._refresh_position_prices holds this exact lock while making # one historical Polygon REST call per recent order (5-min window, ~5s order cadence => dozens # of calls at 1-3s+ each under normal latency, worse when rate-limited). Reclaiming a live # holder puts two writers on one position. Releases are owner-token-checked (see # PositionLockServer), so a reclaimed holder's late release can no longer free the reclaimer's # lock. Analog of ORDER_INFLIGHT_TTL_MS for the position-lock server. POSITION_LOCK_LEASE_MS = 600_000 # 10 minutes ORDER_MIN_LEVERAGE = 0.00001 ORDER_MAX_LEVERAGE = 500 # Controls how much history to store for price data which is used in retroactive updates RECENT_EVENT_TRACKER_OLDEST_ALLOWED_RECORD_MS = 300000 # 5 minutes # Risk Profiling RISK_PROFILING_STEPS_MIN_LEVERAGE = 0.01 # min of category MIN_LEVERAGE values in vali_objects/trade_pair.py RISK_PROFILING_STEPS_CRITERIA = 3 RISK_PROFILING_MONOTONIC_CRITERIA = 2 RISK_PROFILING_MARGIN_CRITERIA = 0.5 RISK_PROFILING_LEVERAGE_ADVANCE = 1.5 RISK_PROFILING_SCOPING_MECHANIC = 100 RISK_PROFILING_SIGMOID_SHIFT = 1.2 RISK_PROFILING_SIGMOID_SPREAD = 4 # RISK_PROFILING_TIME_DECAY = 5 # RISK_PROFILING_TIME_CYCLE = POSITIONAL_EQUIVALENCE_WINDOW_MS RISK_PROFILING_TIME_CRITERIA = 0.185 # threshold for the normalized error of a position’s order time intervals PLAGIARISM_MATCHING_TIME_RESOLUTION_MS = 60 * 1000 * 2 # 2 minutes PLAGIARISM_MAX_LAGS = 60 PLAGIARISM_LOOKBACK_RANGE_MS = 10 * 24 * 60 * 60 * 1000 # 10 days PLAGIARISM_FOLLOWER_TIMELAG_THRESHOLD = 1.0005 PLAGIARISM_FOLLOWER_SIMILARITY_THRESHOLD = 0.75 PLAGIARISM_REPORTING_THRESHOLD = 0.8 PLAGIARISM_REFRESH_TIME_MS = 1000 * 60 * 60 * 24 # 1 day PLAGIARISM_ORDER_TIME_WINDOW_MS = 1000 * 60 * 60 * 12 PLAGIARISM_MINIMUM_FOLLOW_MS = 1000 * 10 # Minimum follow time of 10 seconds for each order EPSILON = 1e-6 RETURN_SHORT_LOOKBACK_TIME_MS = 5 * 24 * 60 * 60 * 1000 # 5 days RETURN_SHORT_LOOKBACK_LEDGER_WINDOWS = RETURN_SHORT_LOOKBACK_TIME_MS // TARGET_CHECKPOINT_DURATION_MS MINIMUM_POSITION_DURATION_MS = 1 * 60 * 1000 # 1 minutes SHORT_LOOKBACK_WINDOW = 7 * DAILY_CHECKPOINTS # Scoring weights SCORING_OMEGA_WEIGHT = 0.0 SCORING_SHARPE_WEIGHT = 0.0 SCORING_SORTINO_WEIGHT = 0.0 SCORING_STATISTICAL_CONFIDENCE_WEIGHT = 0.0 SCORING_CALMAR_WEIGHT = 0.0 SCORING_RETURN_WEIGHT = 0.0 SCORING_PNL_WEIGHT = 1.0 # Scoring hyperparameters OMEGA_LOSS_MINIMUM = 0.01 # Equivalent to 1% loss OMEGA_NOCONFIDENCE_VALUE = 0.0 SHARPE_STDDEV_MINIMUM = 0.01 # Equivalent to 1% standard deviation SHARPE_NOCONFIDENCE_VALUE = -100 SORTINO_DOWNSIDE_MINIMUM = 0.01 # Equivalent to 1% standard deviation SORTINO_NOCONFIDENCE_VALUE = -100 STATISTICAL_CONFIDENCE_NOCONFIDENCE_VALUE = -100 CALMAR_NOCONFIDENCE_VALUE = -100 PNL_NOCONFIDENCE_VALUE = 0 # MDD penalty calculation APPROXIMATE_DRAWDOWN_PERCENTILE = 0.75 DRAWDOWN_UPPER_SCALING = 5 DRAWDOWN_MAXVALUE_PERCENTAGE = 10 DRAWDOWN_MINVALUE_PERCENTAGE = 0.5 # Risk Adjusted Performance Penalty CRYPTO_RAT = {'sharpe': 1.0, 'sortino': 1.0, 'calmar': 2.0, 'omega': 1.4} FOREX_RAT = {'sharpe': 0.5, 'sortino': 0.5, 'calmar': 2.0, 'omega': 1.2} # Maximum metric value for capping individual metrics in RAS calculation RISK_ADJUSTED_MAX_METRIC_VALUE = 10 # Sigmoid parameters for risk-adjusted performance penalty (range: 0.2 to 1.0) RISK_ADJUSTED_SIGMOID_SHIFT = 0.6 RISK_ADJUSTED_SIGMOID_SPREAD = -14 RISK_ADJUSTED_PERFORMANCE_PENALTY_MIN = 0.2 # Challenge period CHALLENGE_PERIOD_MIN_WEIGHT = 1.5e-05 # essentially nothing CHALLENGE_PERIOD_MAX_WEIGHT = 2.4e-05 CHALLENGE_PERIOD_MINIMUM_DAYS = 61 CHALLENGE_PERIOD_MINIMUM_MS = CHALLENGE_PERIOD_MINIMUM_DAYS * DAILY_MS CHALLENGE_PERIOD_MAXIMUM_DAYS = 90 CHALLENGE_PERIOD_MAXIMUM_MS = CHALLENGE_PERIOD_MAXIMUM_DAYS * DAILY_MS CHALLENGE_PERIOD_PERCENTILE_THRESHOLD = 0.75 # miners must pass 75th percentile to enter the main competition PROBATION_MAXIMUM_DAYS = 90 PROBATION_MAXIMUM_MS = PROBATION_MAXIMUM_DAYS * DAILY_MS IDLE_MINER_MAXIMUM_DAYS = 60 IDLE_MINER_MAXIMUM_MS = IDLE_MINER_MAXIMUM_DAYS * DAILY_MS PROMOTION_THRESHOLD_RANK = 25 # Number of MAINCOMP miners per asset class # Plagiarism ORDER_SIMILARITY_WINDOW_MS = 60000 * 60 * 24 MINER_COPYING_WEIGHT = 0.01 MAX_MINER_PLAGIARISM_SCORE = 0.9 # want to make sure we're filtering out the bad actors PLAGIARISM_UPDATE_FREQUENCY_MS = 1000 * 60 * 60 # 1 hour PLAGIARISM_REVIEW_PERIOD_MS = 1000 * 60 * 60 * 24 * 14 # Time from plagiarism detection to elimination, 2 weeks PLAGIARISM_URL = "https://plagiarism.ultron.ts.taoshi.io/plagiarism" # Public domain for getting plagiarism scores BASE_DIR = base_directory = BASE_DIR METAGRAPH_UPDATE_REFRESH_TIME_VALIDATOR_MS = 60 * 1000 # 1 minute METAGRAPH_UPDATE_REFRESH_TIME_MINER_MS = 60 * 1000 * 15 # 15 minutes ELIMINATION_CHECK_INTERVAL_MS = 60 * 2 * 1000 # 2 minutes ELIMINATION_CACHE_REFRESH_INTERVAL_S = 5 # Elimination cache refresh interval in seconds ELIMINATION_FILE_DELETION_DELAY_MS = 30 * 24 * 60 * 60 * 1000 # 30 days # Entity Miners Configuration ENTITY_ELIMINATION_CHECK_INTERVAL = 300 # 5 minutes (in seconds) - for challenge period + elimination checks MAX_REGISTERED_ENTITIES = 10 # Maximum number of entities that can register ENTITY_MAX_SUBACCOUNTS = 10_000 # Default maximum subaccounts per entity (Phase 1) ENTITY_DATA_DIR = "validation/entities/" # Entity data persistence directory FIXED_SUBACCOUNT_SIZE = 10000.0 # Fixed account size for subaccounts (USD) - placeholder SUBACCOUNT_COLLATERAL_AMOUNT = 1000.0 # Placeholder collateral amount per subaccount # Challenge Period Configuration SUBACCOUNT_CHALLENGE_RETURNS_THRESHOLD_DEFAULT = 0.1 # Default fallback returns threshold SUBACCOUNT_CHALLENGE_RETURNS_THRESHOLD = { MinerAssetClass.CRYPTO: 0.1, # 10% returns required to pass crypto evaluation MinerAssetClass.FOREX: 0.08, # 8% returns required to pass forex evaluation MinerAssetClass.EQUITIES: 0.1, # 10% returns required to pass equities evaluation MinerAssetClass.HL_ALL: 0.1, # 10% returns required to pass hl all markets evaluation MinerAssetClass.ALL_MARKETS: 0.1, # 10% returns required to pass all markets evaluation MinerAssetClass.COMMODITIES: 0.1, # 10% returns required to pass commodities evaluation } CHALLENGE_INTRADAY_DRAWDOWN_THRESHOLD = 0.05 # Rule 1: 5% intraday drop from day-open equity eliminates CHALLENGE_EOD_DRAWDOWN_THRESHOLD = 0.05 # Rule 2: 5% drop from highest-ever EOD equity eliminates FUNDED_INTRADAY_DRAWDOWN_THRESHOLD_V0 = 0.10 # V0 applies to subaccounts registered before Sun Mar 15, 2026 FUNDED_INTRADAY_DRAWDOWN_THRESHOLD_V1 = 0.08 # V1 applies to subaccounts registered before Wed May 27, 2026 FUNDED_INTRADAY_DRAWDOWN_THRESHOLD = 0.05 FUNDED_EOD_DRAWDOWN_THRESHOLD_V0 = 0.10 # V0 applies to subaccounts registered before Sun Mar 15, 2026 FUNDED_EOD_DRAWDOWN_THRESHOLD = 0.08 # Rule 2: 8% drop from highest-ever EOD equity (trailing) eliminates # Registration cutoffs for versioned SUBACCOUNT_FUNDED thresholds (ms) FUNDED_V0_CUTOFF_MS = 1773532799000 # Mar 14, 2026 23:59:59 UTC FUNDED_V1_CUTOFF_MS = 1779840000000 # May 27, 2026 00:00:00 UTC # Entity subaccount (standard account) static drawdown rules # Rule 1: equity vs starting balance. Rule 2: intraday drawdown (shared check with trailing accounts, flat threshold) SUBACCOUNT_STATIC_DRAWDOWN_THRESHOLD = 0.05 # Rule 1: equity (incl. unrealized PnL) more than 5% below starting balance eliminates SUBACCOUNT_STATIC_EOD_DRAWDOWN_THRESHOLD = 0.05 # retired rule, no longer enforced — kept for dashboard/API payload compatibility SUBACCOUNT_STATIC_INTRADAY_DRAWDOWN_THRESHOLD = 0.05 # Rule 2: flat intraday-drawdown threshold for static accounts, regardless of bucket entry time # Pro account (entity subaccount) rules. The promotion criteria and transition grace period can # be overridden for testnet through environment variables of the same name (docs/entity_miner.md). PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT = _env_override("PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT", 0.06) PRO_CHALLENGE_RETURNS_THRESHOLD = { MinerAssetClass.CRYPTO: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, MinerAssetClass.FOREX: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, MinerAssetClass.EQUITIES: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, MinerAssetClass.HL_ALL: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, MinerAssetClass.ALL_MARKETS: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, MinerAssetClass.COMMODITIES: PRO_CHALLENGE_RETURNS_THRESHOLD_DEFAULT, } # Rule 1: intraday drop from day-open equity. Rule 2: drop from the highest EOD equity, # measured against live equity rather than once a day. PRO_CHALLENGE_INTRADAY_DRAWDOWN_THRESHOLD = 0.05 PRO_CHALLENGE_EOD_DRAWDOWN_THRESHOLD = 0.08 PRO_FUNDED_INTRADAY_DRAWDOWN_THRESHOLD = 0.05 PRO_FUNDED_EOD_DRAWDOWN_THRESHOLD = 0.08 # Also affects promotion cost # Pro promotion criteria. PRO_CHALLENGE_MINIMUM_DAYS = _env_override("PRO_CHALLENGE_MINIMUM_DAYS", 90, int, maximum=3650) PRO_CHALLENGE_CALMAR_THRESHOLD = _env_override("PRO_CHALLENGE_CALMAR_THRESHOLD", 1.75) # All-time realized return over all-time max drawdown PRO_CHALLENGE_DAILY_CONSISTENCY_THRESHOLD = _env_override("PRO_CHALLENGE_DAILY_CONSISTENCY_THRESHOLD", 0.2) # Best day must be at most this share of total return PRO_DAILY_RETURN_CAP = 0.015 # Each day's profit counts for at most this much toward the total CALMAR_DRAWDOWN_MINIMUM = 0.001 # Floor on the calmar denominator, mirrors SHARPE_STDDEV_MINIMUM # Days of latched soft-breach history kept per miner. The payout only ever reads the current and # previous payout week; the rest is retained for operator forensics and bounded here so the # challenge period checkpoint cannot grow without limit. PRO_SOFT_BREACH_LATCH_RETENTION_DAYS = 60 # Payout basis for a trader running the pro challenge after passing the standard challenge # (PRO_CHALLENGE_FROM_STANDARD). PnL earned on the pro account is paid as though it had been # earned on this multiple of their standard account size: at 2.0, $5K of eligible PnL on a # $500K pro account pays a $100K standard account (5K / 500K) * 2 * 100K = $2K. PRO_TRANSITION_PAYOUT_MULTIPLIER = 2.0 # Subaccount promotion requirements SUBACCOUNT_FUNDED_MINIMUM_DAYS = 90 # Minimum days in FUNDED before promoting to ALPHA # Minimum tier required to get checkpoint file CHECKPOINT_TIER = 100 # Minimum tier required for subaccount dashboard subscriptions SUBACCOUNT_SUBSCRIPTION_TIER = 200 # Distributional statistics SOFTMAX_TEMPERATURE = 0.15 # Qualifications to be a trusted validator sending checkpoints TOP_N_CHECKPOINTS = 10 TOP_N_STAKE = 20 STAKE_MIN = 1000.0 AXON_NO_IP = "0.0.0.0" # Authorized mothership hotkey for state broadcasts # This is the ONLY validator authorized to broadcast CollateralRecord, AssetSelection, and SubaccountRegistration updates # TODO: Replace with actual mothership hotkey SS58 address MOTHERSHIP_HOTKEY = "5FeNwZ5oAqcJMitNqGx71vxGRWJhsdTqxFGVwPRfg8h2UZmo" MOTHERSHIP_HOTKEY_TESTNET = "5GTNzNkJiQWK4NpEErQohqZC8EzzeqrckgLgrQPwuvu8bHLN" # Require at least this many successful checkpoints before building golden MIN_CHECKPOINTS_RECEIVED = 5 # Legacy leverage tiers 1 to 4 (HL-linked subaccounts, pro subaccounts, regular miners): # tier 1 = subaccount challenge, then by account size. Standard subaccounts use the # STANDARD_* tables below instead (see leverage_utils.is_standard_tiered). LEVERAGE_TIER3_MIN_ACCOUNT_SIZE = 200_000 # $200K: Tier 2 → Tier 3 LEVERAGE_TIER4_MIN_ACCOUNT_SIZE = 1_000_000 # $1M: Tier 3 → Tier 4 LEGACY_LEVERAGE_TIERS = (1, 2, 3, 4) # Legacy per-tier portfolio caps. Single-class per-category sub-caps; multi-class subaccounts # (HL_ALL, ALL_MARKETS) reuse these per-class entries for sub-cap enforcement and pull their # overall cross-class cap from LEGACY_TIER_PORTFOLIO_LEVERAGE_BY_ASSET_CLASS below. LEGACY_TIER_PORTFOLIO_LEVERAGE_BY_CATEGORY = { 1: {TradePairCategory.CRYPTO: 2.0, TradePairCategory.FOREX: 5.0, TradePairCategory.EQUITIES: 1.0, TradePairCategory.INDICES: 3.0, TradePairCategory.COMMODITIES: 2.0}, 2: {TradePairCategory.CRYPTO: 2.0, TradePairCategory.FOREX: 10.0, TradePairCategory.EQUITIES: 1.5, TradePairCategory.INDICES: 6.0, TradePairCategory.COMMODITIES: 2.0}, 3: {TradePairCategory.CRYPTO: 3.0, TradePairCategory.FOREX: 15.0, TradePairCategory.EQUITIES: 2.0, TradePairCategory.INDICES: 8.0, TradePairCategory.COMMODITIES: 3.0}, 4: {TradePairCategory.CRYPTO: 4.0, TradePairCategory.FOREX: 20.0, TradePairCategory.EQUITIES: 2.0, TradePairCategory.INDICES: 10.0, TradePairCategory.COMMODITIES: 4.0}, } LEGACY_TIER_PORTFOLIO_LEVERAGE_BY_ASSET_CLASS = { 1: {MinerAssetClass.CRYPTO: 2.0, MinerAssetClass.FOREX: 5.0, MinerAssetClass.EQUITIES: 1.0, MinerAssetClass.COMMODITIES: 2.0, MinerAssetClass.HL_ALL: 4.0, MinerAssetClass.ALL_MARKETS: 6.0}, 2: {MinerAssetClass.CRYPTO: 2.0, MinerAssetClass.FOREX: 10.0, MinerAssetClass.EQUITIES: 1.5, MinerAssetClass.COMMODITIES: 2.0, MinerAssetClass.HL_ALL: 7.0, MinerAssetClass.ALL_MARKETS: 12.0}, 3: {MinerAssetClass.CRYPTO: 3.0, MinerAssetClass.FOREX: 15.0, MinerAssetClass.EQUITIES: 2.0, MinerAssetClass.COMMODITIES: 3.0, MinerAssetClass.HL_ALL: 10.0, MinerAssetClass.ALL_MARKETS: 18.0}, 4: {MinerAssetClass.CRYPTO: 4.0, MinerAssetClass.FOREX: 20.0, MinerAssetClass.EQUITIES: 2.0, MinerAssetClass.COMMODITIES: 4.0, MinerAssetClass.HL_ALL: 12.0, MinerAssetClass.ALL_MARKETS: 24.0}, } # Standard subaccount leverage tiers, per the Pro Launch spec §2a: 1 = Base, 2 = Boost I, # 3 = Boost II (max). Challenge and funded share the same limits and account size does not # change them. HL-linked and pro subaccounts never use these tables. STANDARD_LEVERAGE_TIERS = (1, 2, 3) STANDARD_LEVERAGE_TIER_BASE = 1 STANDARD_LEVERAGE_TIER_DEFAULT = STANDARD_LEVERAGE_TIER_BASE # new registrations # A standard subaccount with no stored leverage_tier (created before tiers existed) trades # tier 0: each per-pair, class and portfolio limit is max(its legacy curve value, Base), so the # tier rollout lowers nothing. Not selectable through any endpoint. Reported to clients as # -legacy_tier (-1 to -4), the key of its rows in /trade-pairs (see leverage_utils.get_grandfathered_*). STANDARD_LEVERAGE_TIER_GRANDFATHERED = 0 @staticmethod def is_valid_standard_leverage_tier(tier) -> bool: """True for an int in STANDARD_LEVERAGE_TIERS; bools and floats are rejected.""" return isinstance(tier, int) and not isinstance(tier, bool) and tier in ValiConfig.STANDARD_LEVERAGE_TIERS # Per-pair groups narrower than an asset class (see leverage_utils.get_standard_leverage_group). STANDARD_CRYPTO_MAJOR_COINS = {"BTC", "ETH", "SOL", "XRP", "DOGE"} STANDARD_FX_NZD_CROSS_IDS = {"EURNZD", "GBPNZD", "NZDJPY", "AUDNZD", "NZDCAD", "NZDCHF"} STANDARD_INDEX_OTHER_IDS = {"EWYUSDC"} # Per-pair positional leverage, as a multiple of balance. STANDARD_POSITIONAL_LEVERAGE_BY_TIER = { 1: { StandardLeverageGroup.CRYPTO_MAJORS: 1.5, StandardLeverageGroup.CRYPTO_OTHER: 0.5, StandardLeverageGroup.FX: 10.0, StandardLeverageGroup.FX_NZD_CROSSES: 5.0, StandardLeverageGroup.INDICES_US: 2.5, StandardLeverageGroup.INDICES_OTHER: 1.0, StandardLeverageGroup.COMMODITIES: 1.5, StandardLeverageGroup.EQUITIES: 0.5, }, 2: { StandardLeverageGroup.CRYPTO_MAJORS: 2.0, StandardLeverageGroup.CRYPTO_OTHER: 0.75, StandardLeverageGroup.FX: 15.0, StandardLeverageGroup.FX_NZD_CROSSES: 7.5, StandardLeverageGroup.INDICES_US: 4.0, StandardLeverageGroup.INDICES_OTHER: 1.5, StandardLeverageGroup.COMMODITIES: 2.0, StandardLeverageGroup.EQUITIES: 1.0, }, 3: { StandardLeverageGroup.CRYPTO_MAJORS: 2.5, StandardLeverageGroup.CRYPTO_OTHER: 1.0, StandardLeverageGroup.FX: 20.0, StandardLeverageGroup.FX_NZD_CROSSES: 10.0, StandardLeverageGroup.INDICES_US: 5.0, StandardLeverageGroup.INDICES_OTHER: 2.0, StandardLeverageGroup.COMMODITIES: 3.0, StandardLeverageGroup.EQUITIES: 1.5, }, } # Per-asset-class exposure cap, as a multiple of balance. STANDARD_CLASS_LEVERAGE_BY_TIER = { 1: {TradePairCategory.CRYPTO: 1.5, TradePairCategory.FOREX: 10.0, TradePairCategory.EQUITIES: 1.0, TradePairCategory.INDICES: 3.0, TradePairCategory.COMMODITIES: 1.5}, 2: {TradePairCategory.CRYPTO: 2.0, TradePairCategory.FOREX: 15.0, TradePairCategory.EQUITIES: 2.0, TradePairCategory.INDICES: 6.0, TradePairCategory.COMMODITIES: 2.0}, 3: {TradePairCategory.CRYPTO: 2.5, TradePairCategory.FOREX: 20.0, TradePairCategory.EQUITIES: 3.0, TradePairCategory.INDICES: 8.0, TradePairCategory.COMMODITIES: 3.0}, } # Overall portfolio cap keyed by the subaccount's own asset_class. Single-class subaccounts # cap at their class row; all_markets caps across classes. STANDARD_PORTFOLIO_LEVERAGE_BY_TIER = { 1: {MinerAssetClass.CRYPTO: 1.5, MinerAssetClass.FOREX: 10.0, MinerAssetClass.EQUITIES: 1.0, MinerAssetClass.COMMODITIES: 1.5, MinerAssetClass.ALL_MARKETS: 15.0}, 2: {MinerAssetClass.CRYPTO: 2.0, MinerAssetClass.FOREX: 15.0, MinerAssetClass.EQUITIES: 2.0, MinerAssetClass.COMMODITIES: 2.0, MinerAssetClass.ALL_MARKETS: 20.0}, 3: {MinerAssetClass.CRYPTO: 2.5, MinerAssetClass.FOREX: 20.0, MinerAssetClass.EQUITIES: 3.0, MinerAssetClass.COMMODITIES: 3.0, MinerAssetClass.ALL_MARKETS: 25.0}, } # Per-pair positional leverage. PRO_CRYPTO_POSITIONAL_LEVERAGE = { "BTC": 5.0, "ETH": 5.0, "SOL": 5.0, "XRP": 5.0, "DOGE": 5.0, "HYPE": 2.0, "SUI": 2.0, "BNB": 2.0, "kPEPE": 1.5, "ADA": 1.5, "ZEC": 1.5, "LINK": 1.5, "LTC": 1.0, "AVAX": 1.0, "TRX": 1.0, } PRO_COMMODITY_POSITIONAL_LEVERAGE = { "WTIOILUSDC": 8.0, "COPPERUSDC": 8.0, "GOLDUSDC": 8.0, "NATGASUSDC": 8.0, "SILVERUSDC": 5.0, "PLATINUMUSDC": 5.0, } PRO_INDEX_POSITIONAL_LEVERAGE = { "SP500USDC": 10.0, "XYZ100USDC": 10.0, "EWYUSDC": 5.0, } PRO_EQUITIES_POSITIONAL_LEVERAGE = 2.0 PRO_FX_POSITIONAL_LEVERAGE = 20.0 # The six NZD crosses in STANDARD_FX_NZD_CROSS_IDS are held to half the major FX limit. PRO_FX_NZD_CROSS_POSITIONAL_LEVERAGE = 10.0 # Fallback for a pro-tradable pair none of the tables above names -- see # docs/pro_leverage_discrepancies.md. PRO_DEFAULT_POSITIONAL_LEVERAGE = 1.0 # Per-asset-class exposure cap. PRO_CLASS_LEVERAGE = { TradePairCategory.CRYPTO: 6.0, TradePairCategory.EQUITIES: 6.0, TradePairCategory.COMMODITIES: 8.0, TradePairCategory.INDICES: 10.0, TradePairCategory.FOREX: 35.0, } # Overall portfolio cap. A single number, not a per-asset-class row: pro accounts are # all_markets in practice and the cap does not vary by asset class. PRO_PORTFOLIO_LEVERAGE = 40.0 # Correlated-exposure limits, pro accounts only. Multiples of account balance, applied # separately to the *gross long* and the *gross short* exposure summed across a correlation # group (see leverage_utils) PRO_CURRENCY_EXPOSURE_LIMITS = { "USD": 30.0, "EUR": 30.0, "GBP": 30.0, "JPY": 30.0, "CHF": 30.0, "CAD": 30.0, "AUD": 30.0, "NZD": 30.0, } PRO_SECTOR_EXPOSURE_LIMIT = 3.0 PRO_US_INDEX_EXPOSURE_LIMIT = 10.0 # shared across the six instruments below, same as the indices class limit # US index pairs and broad US market ETFs carry the same beta, so they share one limit. # EWY, single stocks, and all other ETFs are excluded. PRO_US_INDEX_TRADE_PAIR_IDS = frozenset({ "SP500USDC", "XYZ100USDC", "SPY", "QQQ", "IWM", "DIA", }) # Collateral limits MIN_COLLATERAL_BALANCE_THETA = 300 # Required minimum total collateral balance per miner in Theta. Approx $150k capital account size MAX_COLLATERAL_BALANCE_THETA = 1000 # Approx $500k capital account size MIN_COLLATERAL_BALANCE_TESTNET = 100 MAX_COLLATERAL_BALANCE_TESTNET = 10000.0 # Entity Miner Collateral ENTITY_REGISTRATION_FEE = 1000 # Theta required to register an entity ENTITY_COST_PER_THETA = 5000 # USD account size per theta of collateral for entity subaccounts ENTITY_COST_PER_THETA_LOW = 2500 # CPT value used for smaller account sizes <=10k ENTITY_COST_PER_THETA_LOW_THRESHOLD = 10_000 # Account sizes at or below this use ENTITY_COST_PER_THETA_LOW MAX_SUBACCOUNT_ACCOUNT_SIZE = 100_000 # Maximum account size in USD for entity subaccounts # Largest pro account size in USD. There is no network default: the entity picks the size when it # promotes onto the pro track (POST /entity/subaccount/promote), and the network enforces only that # the size is finite, positive, at most this cap, and never below the subaccount's own standard # account size - a pro account cannot shrink the account the subaccount already trades. MAX_PRO_ACCOUNT_SIZE = 1_000_000 # Pro promotion price. The premium term buys the drawdown the entity is already exposed to on # the standard account, priced in theta at the token's USD price; the second term charges # registration's own per-dollar rate on the size granted above the standard account. PRO_PROMOTION_PREMIUM_RATE = 0.10 # Share of the standard account's drawdown allowance charged up front THETA_USD_PRICE = 7.0 # Assumed USD price of one theta @staticmethod def entity_cost_per_theta(account_size: float) -> float: """USD of account size per theta that registration charges for an account of this size.""" return (ValiConfig.ENTITY_COST_PER_THETA_LOW if (account_size or 0.0) <= ValiConfig.ENTITY_COST_PER_THETA_LOW_THRESHOLD else ValiConfig.ENTITY_COST_PER_THETA) @staticmethod def pro_promotion_fee_theta(pro_account_size: float, standard_account_size: float) -> float: """ Theta owed to put a subaccount on a pro account of `pro_account_size`: PREMIUM_RATE * (FUNDED_EOD_DRAWDOWN_THRESHOLD * standard_size) / THETA_USD_PRICE + (pro_size - standard_size) / registration CPT Registration already paid for `standard_account_size`, so only the size granted above it is charged per dollar: a pro account the same size as the standard one grants no extra dollars and so owes nothing at the registration rate. Callers subtract the fee already assessed, so re-entering the pro track at the same size is free and a larger grant costs only the increase. Never negative: both terms are clamped at zero, so a pro size at or below the standard size can only ever cost the premium. """ standard_size = max(0.0, standard_account_size or 0.0) size_granted = max(0.0, (pro_account_size or 0.0) - standard_size) drawdown_allowance_usd = ValiConfig.PRO_FUNDED_EOD_DRAWDOWN_THRESHOLD * standard_size premium_theta = max(0.0, ValiConfig.PRO_PROMOTION_PREMIUM_RATE * drawdown_allowance_usd / ValiConfig.THETA_USD_PRICE) registration_theta = size_granted / ValiConfig.entity_cost_per_theta(pro_account_size) return max(0.0, premium_theta + registration_theta) # Entity margin collateral requirement (funded subaccounts only): # required_theta = sum(max_slash_usd - cumulative_slashed_usd) / CPT_RISK # for each funded subaccount with open positions (or placing this order) # max_slash_usd = account_size * the bucket's intraday drawdown threshold. A subaccount in # PRO_CHALLENGE_FROM_STANDARD trades the pro account but is charged against its standard # account size, so the pro account's margin only lands when it reaches PRO_FUNDED. ENTITY_COLLATERAL_CPT_RISK = 35 # USD of remaining loss capacity per theta ($35 of capacity = 1 theta) # Hyperliquid tracking configuration HL_USE_TESTNET = False # Set to True to use Hyperliquid testnet endpoints HL_MAINNET_WS = "wss://api.hyperliquid.xyz/ws" HL_MAINNET_INFO = "https://api.hyperliquid.xyz/info" HL_MAINNET_HOST = "api.hyperliquid.xyz" HL_TESTNET_WS = "wss://api.hyperliquid-testnet.xyz/ws" HL_TESTNET_INFO = "https://api.hyperliquid-testnet.xyz/info" HL_TESTNET_HOST = "api.hyperliquid-testnet.xyz" @classmethod def hl_ws_url(cls) -> str: return cls.HL_TESTNET_WS if cls.HL_USE_TESTNET else cls.HL_MAINNET_WS @classmethod def hl_info_url(cls) -> str: return cls.HL_TESTNET_INFO if cls.HL_USE_TESTNET else cls.HL_MAINNET_INFO @classmethod def hl_host(cls) -> str: return cls.HL_TESTNET_HOST if cls.HL_USE_TESTNET else cls.HL_MAINNET_HOST HL_MAX_TRACKED_ADDRESSES_PER_IP = 10 # HL WebSocket limit: 10 unique users per IP HL_MAX_TRACKED_ADDRESSES = HL_MAX_TRACKED_ADDRESSES_PER_IP # backward compat alias HL_WS_HEARTBEAT_INTERVAL_S = 30.0 HL_WS_RECONNECT_BACKOFF_MAX_S = 30.0 HL_PROXY_SECRET_KEY = "hl_proxy_url" # key in secrets.json for base proxy URL (without port) HL_PROXY_PORTS_SECRET_KEY = "hl_proxy_ports" # key in secrets.json for port list/range HL_MAX_PROXY_SHARDS = 100 # safety cap on proxy connections (500 addresses max) HL_SHARD_MAX_CONSECUTIVE_FAILURES = 5 # failures before marking a proxy IP as unhealthy HL_PORT_REST_FAILURE_THRESHOLD = 3 HL_PORT_HEALTH_PROBE_INTERVAL_S = 30.0 HL_PORT_HEALTH_MAX_COOLDOWN_S = 600.0 HL_ADDRESS_REGEX = r"^0x[a-fA-F0-9]{40}$" HL_BACKUP_POLL_INTERVAL_S = 10.0 HL_BACKUP_POLL_RATE_BUDGET = 60 HL_BACKUP_POLL_LOOKBACK_MS = 60 * 60 * 1000 # TODO: change to 2 min HL_BACKUP_RESTART_LOOKBACK_MS = 60 * 60 * 1000 # L2 orderbook precision: nSigFigs controls price aggregation granularity. # HL returns max 20 levels per side regardless of nSigFigs. # None (full precision, native ticks) is finest; 2 sig figs is coarsest/deepest # coverage but loses granular price distribution. We subscribe to every resolution # in the cascade on its own WS connection and walk them finest-to-coarsest, only # extending into the next coarser book once the finer one is exhausted. HL_L2_SIG_FIGS_CASCADE = [None, 5, 4, 3, 2] # Slippage audit logging: log the L2 levels walked whenever simulated Hyperliquid # slippage exceeds this fraction, so anomalously high slippage can be # reconstructed/verified after the fact. HL_SLIPPAGE_AUDIT_LOG_THRESHOLD = 0.008 # HL Funding Rate Service HL_FUNDING_DAEMON_INTERVAL_S = 300 HL_FUNDING_BACKFILL_HOURS = 4 # Account Size COST_PER_THETA = 500 # Account size USD value per theta of collateral MIN_COLLATERAL_VALUE = MIN_COLLATERAL_BALANCE_THETA * COST_PER_THETA # Approx $150k MIN_CAPITAL = 5_000 # USD minimum capital account size DEFAULT_CAPITAL = 100_000 # conversion of 1x leverage to $100K in capital # 100% percent of collateral deposit is at risk of slashing based on drawdown DRAWDOWN_SLASH_PROPORTION = 1.0 MAX_UNFILLED_LIMIT_ORDERS = 100 LIMIT_ORDER_FILL_INTERVAL_MS = 10 * 1000 # 10 seconds LIMIT_ORDER_PRICE_BUFFER_MS = 30 * 1000