from typing import Optional from pydantic import BaseModel class PriceImpact(BaseModel): asset: str # None = window still open (live rolling peak) or no price data yet. # Float = sealed peak move (%) in the signal direction once window closed. m5: Optional[float] = None m15: Optional[float] = None m1h: Optional[float] = None price_at_post: float # None = outcome window not yet reached; True/False = signal direction matched correct_m5: Optional[bool] = None correct_m15: Optional[bool] = None correct_m1h: Optional[bool] = None class TrumpPost(BaseModel): id: int text: str source: str published_at: str # ISO sentiment: str signal: Optional[str] = None # buy | sell | short | hold ai_confidence: int ai_reasoning: Optional[str] = None prefilter_reason: Optional[str] = None # rt_only | url_only | empty | parse_error | api_error | null analysis_version: Optional[str] = None relevant: bool price_impact: Optional[PriceImpact] = None model_config = {"from_attributes": True} class Candle(BaseModel): time: int open: float high: float low: float close: float volume: float class BotTrade(BaseModel): id: int asset: str side: str entry_price: float exit_price: float pnl_usd: float hold_seconds: int trigger_post_id: int opened_at: str closed_at: str model_config = {"from_attributes": True} class BotPerformance(BaseModel): period_days: int total_trades: int win_rate: float net_pnl_usd: float avg_hold_seconds: float max_drawdown_pct: float class SignedEnvelope(BaseModel): """Every write request carries this envelope. Client builds a canonical message (see app.services.signed_request.build_message) and signs it via EIP-191 personal_sign. Server re-builds the message from {action, wallet, timestamp, sha256(body)} and recovers the signer. """ wallet: str timestamp: int # milliseconds since epoch signature: str class SubscribeRequest(SignedEnvelope): pass class SubscribeResponse(BaseModel): status: str wallet: str class SetApiKeyRequest(SignedEnvelope): api_key: str # Hyperliquid API wallet private key (0x...) class SetApiKeyResponse(BaseModel): status: str masked_key: str # last 6 chars only, e.g. "...a1b2c3" class UserSettings(BaseModel): leverage: int position_size_usd: float take_profit_pct: Optional[float] = None stop_loss_pct: Optional[float] = None min_confidence: int daily_budget_usd: Optional[float] = None # ISO-8601 UTC strings; both None = always on (Subscription.active still gates it). active_from: Optional[str] = None active_until: Optional[str] = None class SetSettingsRequest(SignedEnvelope): settings: UserSettings class UserResponse(BaseModel): wallet_address: str active: bool subscribed_at: Optional[str] = None hl_api_key_set: bool hl_api_key_masked: Optional[str] = None trades: list[BotTrade] settings: UserSettings