3a85ff05e6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
207 lines
7.0 KiB
Python
207 lines
7.0 KiB
Python
"""Pydantic Settings: legge .env e variabili d'ambiente."""
|
|
from __future__ import annotations
|
|
|
|
from pydantic import Field, SecretStr
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class _Sub(BaseSettings):
|
|
"""Base per sub-settings, condivide model_config con env_file."""
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
|
|
|
|
class DeribitSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="DERIBIT_",
|
|
extra="ignore",
|
|
)
|
|
client_id: str | None = None
|
|
client_secret: SecretStr | None = None
|
|
client_id_testnet: str | None = None
|
|
client_secret_testnet: SecretStr | None = None
|
|
client_id_live: str | None = None
|
|
client_secret_live: SecretStr | None = None
|
|
url_live: str
|
|
url_testnet: str
|
|
max_leverage: int = 3
|
|
|
|
def credentials(self, env: str) -> tuple[str, str]:
|
|
"""Return (client_id, client_secret) for the given env.
|
|
Prefers env-specific (_TESTNET / _LIVE) pair; falls back to base
|
|
(DERIBIT_CLIENT_ID / DERIBIT_CLIENT_SECRET) for legacy single-pair setups.
|
|
"""
|
|
if env == "testnet":
|
|
cid = self.client_id_testnet or self.client_id
|
|
csec = self.client_secret_testnet or self.client_secret
|
|
elif env == "mainnet":
|
|
cid = self.client_id_live or self.client_id
|
|
csec = self.client_secret_live or self.client_secret
|
|
else:
|
|
raise ValueError(f"unknown deribit env: {env}")
|
|
if not cid or csec is None:
|
|
raise ValueError(f"Deribit credentials not configured for env={env}")
|
|
return cid, csec.get_secret_value()
|
|
|
|
|
|
class BybitSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="BYBIT_",
|
|
extra="ignore",
|
|
)
|
|
api_key: str
|
|
api_secret: SecretStr
|
|
url_live: str
|
|
url_testnet: str
|
|
max_leverage: int = 3
|
|
|
|
|
|
class HyperliquidSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="HYPERLIQUID_",
|
|
extra="ignore",
|
|
)
|
|
wallet_address: str
|
|
api_wallet_address: str
|
|
private_key: SecretStr
|
|
url_live: str
|
|
url_testnet: str
|
|
max_leverage: int = 3
|
|
|
|
|
|
class AlpacaSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="ALPACA_",
|
|
extra="ignore",
|
|
)
|
|
api_key_id: str
|
|
secret_key: SecretStr
|
|
url_live: str
|
|
url_testnet: str
|
|
max_leverage: int = 1
|
|
|
|
|
|
class IBKRSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
env_prefix="IBKR_",
|
|
extra="ignore",
|
|
)
|
|
consumer_key: str | None = None
|
|
access_token: str | None = None
|
|
access_token_secret: SecretStr | None = None
|
|
signature_key_path: str | None = None
|
|
encryption_key_path: str | None = None
|
|
dh_prime: SecretStr | None = None
|
|
|
|
consumer_key_testnet: str | None = None
|
|
access_token_testnet: str | None = None
|
|
access_token_secret_testnet: SecretStr | None = None
|
|
signature_key_path_testnet: str | None = None
|
|
encryption_key_path_testnet: str | None = None
|
|
account_id_testnet: str | None = None
|
|
|
|
consumer_key_live: str | None = None
|
|
access_token_live: str | None = None
|
|
access_token_secret_live: SecretStr | None = None
|
|
signature_key_path_live: str | None = None
|
|
encryption_key_path_live: str | None = None
|
|
account_id_live: str | None = None
|
|
|
|
url_live: str = "https://api.ibkr.com/v1/api"
|
|
url_testnet: str = "https://api.ibkr.com/v1/api"
|
|
ws_url_live: str = "wss://api.ibkr.com/v1/api/ws"
|
|
ws_url_testnet: str = "wss://api.ibkr.com/v1/api/ws"
|
|
max_leverage: int = 4
|
|
ws_max_subscriptions: int = 80
|
|
ws_idle_timeout_s: int = 300
|
|
|
|
def credentials(self, env: str) -> dict:
|
|
if env == "testnet":
|
|
ck = self.consumer_key_testnet or self.consumer_key
|
|
at = self.access_token_testnet or self.access_token
|
|
ats = self.access_token_secret_testnet or self.access_token_secret
|
|
sigp = self.signature_key_path_testnet or self.signature_key_path
|
|
encp = self.encryption_key_path_testnet or self.encryption_key_path
|
|
acct = self.account_id_testnet
|
|
elif env == "mainnet":
|
|
ck = self.consumer_key_live or self.consumer_key
|
|
at = self.access_token_live or self.access_token
|
|
ats = self.access_token_secret_live or self.access_token_secret
|
|
sigp = self.signature_key_path_live or self.signature_key_path
|
|
encp = self.encryption_key_path_live or self.encryption_key_path
|
|
acct = self.account_id_live
|
|
else:
|
|
raise ValueError(f"unknown ibkr env: {env}")
|
|
|
|
missing = [
|
|
n for n, v in [
|
|
("consumer_key", ck), ("access_token", at),
|
|
("access_token_secret", ats), ("signature_key_path", sigp),
|
|
("encryption_key_path", encp), ("account_id", acct),
|
|
("dh_prime", self.dh_prime),
|
|
] if not v
|
|
]
|
|
if missing:
|
|
raise ValueError(
|
|
f"IBKR credentials not configured for env={env}: missing {missing}"
|
|
)
|
|
return {
|
|
"consumer_key": ck,
|
|
"access_token": at,
|
|
"access_token_secret": ats.get_secret_value(), # type: ignore[union-attr]
|
|
"signature_key_path": sigp,
|
|
"encryption_key_path": encp,
|
|
"account_id": acct,
|
|
"dh_prime": self.dh_prime.get_secret_value(), # type: ignore[union-attr]
|
|
}
|
|
|
|
|
|
class MacroSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
fred_api_key: SecretStr
|
|
finnhub_api_key: SecretStr
|
|
|
|
|
|
class SentimentSettings(_Sub):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
cryptopanic_key: SecretStr
|
|
lunarcrush_key: SecretStr
|
|
|
|
|
|
class Settings(_Sub):
|
|
host: str = "0.0.0.0"
|
|
port: int = 9000
|
|
log_level: str = "info"
|
|
|
|
testnet_token: SecretStr
|
|
mainnet_token: SecretStr
|
|
|
|
deribit: DeribitSettings = Field(default_factory=lambda: DeribitSettings()) # type: ignore[call-arg]
|
|
bybit: BybitSettings = Field(default_factory=lambda: BybitSettings()) # type: ignore[call-arg]
|
|
hyperliquid: HyperliquidSettings = Field(default_factory=lambda: HyperliquidSettings()) # type: ignore[call-arg]
|
|
alpaca: AlpacaSettings = Field(default_factory=lambda: AlpacaSettings()) # type: ignore[call-arg]
|
|
ibkr: IBKRSettings = Field(default_factory=lambda: IBKRSettings()) # type: ignore[call-arg]
|
|
macro: MacroSettings = Field(default_factory=lambda: MacroSettings()) # type: ignore[call-arg]
|
|
sentiment: SentimentSettings = Field(default_factory=lambda: SentimentSettings()) # type: ignore[call-arg]
|