# QuantDinger local configuration (copy to `.env` and edit) # `run.py` will load `backend_api_python/.env` automatically if present. # # This file is organized as: # 1) first-time deployment settings at the top # 2) advanced / rarely changed settings at the bottom # For Docker image source / ports, use the project-root `.env`. # ========================= # Brand / identity (drives logo, footer, version label, legal modals) # ========================= # Frontend reads these via /api/settings/brand-config — leave any field empty # to fall back to the bundled QuantDinger defaults. Change here (or via the # Settings page) to rebrand a deployment without rebuilding the frontend. BRAND_APP_NAME=QuantDinger BRAND_COPYRIGHT=© 2025-2026 QuantDinger. All rights reserved. # Logo / favicon URLs (HTTPS recommended; leave empty to use bundled assets) BRAND_LOGO_LIGHT_URL= BRAND_LOGO_DARK_URL= BRAND_LOGO_COLLAPSED_URL= BRAND_FAVICON_URL= # Contact & support links surfaced in the sidebar footer. BRAND_CONTACT_EMAIL=support@quantdinger.com BRAND_CONTACT_SUPPORT_URL=https://t.me/quantdinger BRAND_CONTACT_LIVE_CHAT_URL=https://t.me/quantdinger BRAND_CONTACT_FEATURE_REQUEST_URL=https://github.com/brokermr810/QuantDinger/issues # Social accounts shown as icons in the sidebar footer. Leave any single # entry empty to hide that icon. BRAND_SOCIAL_GITHUB=https://github.com/brokermr810/QuantDinger BRAND_SOCIAL_X=https://x.com/quantdinger_en BRAND_SOCIAL_DISCORD=https://discord.com/invite/tyx5B6TChr BRAND_SOCIAL_TELEGRAM=https://t.me/quantdinger BRAND_SOCIAL_YOUTUBE=https://youtube.com/@quantdinger # Legal: external URL takes priority; if both URL and inline text are empty, # the frontend falls back to the built-in i18n copy. BRAND_LEGAL_USER_AGREEMENT_URL= BRAND_LEGAL_USER_AGREEMENT_TEXT= BRAND_LEGAL_PRIVACY_POLICY_URL= BRAND_LEGAL_PRIVACY_POLICY_TEXT= # ========================= # Auth (required) # ========================= # >= 32 bytes for HS256 (JWT). Generate: python -c "import secrets; print(secrets.token_hex(32))" # Also derives Fernet key for encrypting qd_exchange_credentials.encrypted_config (do not rotate casually). SECRET_KEY=quantdinger-secret-key-change-me ADMIN_USER=quantdinger # Leaving ADMIN_PASSWORD as 123456 will trigger a first-login change-password reminder. # Set any non-default value before startup to skip that reminder and initialize safely. ADMIN_PASSWORD=123456 ADMIN_EMAIL= # ========================= # Core app # ========================= DATABASE_URL=postgresql://quantdinger:quantdinger123@postgres:5432/quantdinger # FRONTEND_URL: the canonical user-facing URL of the web app. # - Used for: CORS allow-list, default post-login OAuth redirect target. # - Comma-separated multi-origin IS supported (the first entry is the # default OAuth redirect; every entry is added to the CORS / OAuth # allow-list). Prefer the simpler `OAUTH_ALLOWED_REDIRECTS` below for # the second / third front-end origin so that this stays a single URL. # - You do NOT need to add Capacitor / Cordova / Ionic mobile-app origins # here (https://localhost, capacitor://localhost, ionic://localhost, # http://localhost). The backend whitelists those automatically because # they are fixed synthetic origins controlled by the OS, not user input. # - If your mobile app uses a separate API subdomain like # https://api.quantdinger.com, make sure your nginx server_block for that # host is reverse-proxying /api/* to the backend port (5000). # Examples: # FRONTEND_URL=https://ai.quantdinger.com # FRONTEND_URL=https://ai.quantdinger.com,https://m.quantdinger.com FRONTEND_URL=http://localhost:8888,http://localhost:8889 # Extra front-end origins allowed as OAuth post-login redirect targets. # Comma-separated, scheme + host (+ optional port). Used when multiple clients # (e.g. PC web + mobile web) share the same backend. FRONTEND_URL is always # allowed implicitly; list every additional front-end here. # Example: OAUTH_ALLOWED_REDIRECTS=https://m.quantdinger.com,https://app.quantdinger.com,com.quantdinger.mobile://login OAUTH_ALLOWED_REDIRECTS= # OAuth CSRF state TTL (minutes); stored in Postgres for multi-worker. Default 20, clamped [5,120]. OAUTH_STATE_TTL_MINUTES=20 ENABLE_REGISTRATION=true # ========================= # Mobile app (in-app version check) # ========================= # Semver-like string shown to clients; leave empty to avoid "new version" prompts. MOBILE_APP_LATEST_VERSION= # APK / install page URL for one-tap update on Android (HTTPS recommended). MOBILE_APP_DOWNLOAD_URL=https://www.quantdinger.com/download/app.apk # ========================= # Database bootstrap behaviour # ========================= # On every backend startup we auto-apply `migrations/init.sql` so a fresh # install (especially bare-metal / Windows local PG) gets the full schema # without anyone remembering to `psql -f` first. The file uses # `CREATE TABLE IF NOT EXISTS` everywhere so re-running is a no-op. # Set to `true` if you manage schema externally (Flyway, Liquibase, etc.) # and don't want the backend touching DDL on boot. Permission probing on # critical tables still runs either way. SKIP_AUTO_MIGRATE=false # ========================= # Database connection pool (psycopg2 ThreadedConnectionPool) # ========================= # DB_POOL_MAX defaults to auto. Most self-hosted deployments should leave it # as auto; the backend probes PostgreSQL max_connections and picks a safe # per-process pool size. Advanced operators may set an integer override. DB_POOL_MIN=5 DB_POOL_MAX=auto DB_POOL_ACQUIRE_TIMEOUT=10 DB_POOL_HEALTH_CHECK=true DB_POOL_AUTO_CAP=true DB_POOL_AUTO_DEFAULT_MAX=50 DB_POOL_RESERVE_FOR_OTHER_CLIENTS=20 DB_APPLICATION_NAME=quantdinger_api # Route-level parallel fetch executors. Each worker may hold one DB # connection, so keep MARKET_EXECUTOR_WORKERS + PORTFOLIO_EXECUTOR_WORKERS # well below DB_POOL_MAX. MARKET_EXECUTOR_WORKERS=6 PORTFOLIO_EXECUTOR_WORKERS=3 # Gunicorn worker/thread model GUNICORN_WORKERS=1 GUNICORN_THREADS=8 # ========================= # AI / LLM (choose one provider) # ========================= LLM_PROVIDER=openrouter # LLM calls are direct by default. Use this only when your model provider # must be reached through a proxy. This is intentionally separate from PROXY_URL. LLM_PROXY_URL= # Advanced: inherit HTTP_PROXY / HTTPS_PROXY / ALL_PROXY for LLM calls. # Keep false unless the backend can actually reach that proxy endpoint. LLM_USE_SYSTEM_PROXY=false OPENROUTER_API_KEY= OPENROUTER_MODEL=openai/gpt-5.4 # Optional: dedicated model for AI code generation (fallback to provider default if empty) AI_CODE_GEN_MODEL= OPENAI_API_KEY= OPENAI_MODEL=gpt-5.4 GOOGLE_API_KEY= GOOGLE_MODEL=gemini-1.5-flash DEEPSEEK_API_KEY= DEEPSEEK_MODEL=deepseek-chat GROK_API_KEY= GROK_MODEL=grok-beta # AtlasCloud (set LLM_PROVIDER=atlascloud) # Official OpenAI-compatible LLM base URL must include /v1. ATLASCLOUD_API_KEY= ATLASCLOUD_MODEL=openai/gpt-5.4 ATLASCLOUD_BASE_URL=https://api.atlascloud.ai/v1 # OpenAI-compatible third-party API (set LLM_PROVIDER=custom) # Local Ollama example (backend must reach this host; Docker Desktop: http://host.docker.internal:11434/v1): # CUSTOM_API_URL=http://127.0.0.1:11434/v1 # CUSTOM_MODEL=llama3.2 # Ollama usually needs no key — leave CUSTOM_API_KEY empty. CUSTOM_API_URL= CUSTOM_API_KEY= CUSTOM_MODEL= # MiniMax (set LLM_PROVIDER=minimax) MINIMAX_API_KEY= MINIMAX_MODEL=MiniMax-M2.7 MINIMAX_BASE_URL=https://api.minimax.io/v1 # ========================= # Common background jobs # ========================= ENABLE_PENDING_ORDER_WORKER=true ENABLE_PORTFOLIO_MONITOR=true DISABLE_RESTORE_RUNNING_STRATEGIES=false # Position sync (pending_order_worker): polls exchange positions for live strategies. # Many strategies often share one API key — cache + longer interval avoids Binance IP bans. POSITION_SYNC_ENABLED=true POSITION_SYNC_INTERVAL_SEC=30 # Reuse one get_positions() result per credential for this many seconds (0 = auto: max(30, INTERVAL)). POSITION_SYNC_CACHE_TTL_SEC=0 # After HTTP 418 / rate-limit, pause sync for this credential (seconds). EXCHANGE_SYNC_BACKOFF_SEC=900 # ========================= # Agent Gateway (/api/agent/v1) — see docs/agent/AI_INTEGRATION_DESIGN.md # ========================= # Thread pool size for async agent jobs (backtests, experiment pipelines). AGENT_JOBS_MAX_WORKERS=4 # Hard kill switch for live trading from agent tokens. Even when a token is # issued with paper_only=false, live order routing remains disabled until this # flag is true AND a live executor implementation is wired in. Until then, # `T`-class agent calls always record paper orders in qd_agent_paper_orders. AGENT_LIVE_TRADING_ENABLED=false # Deployment topology hint for the Agent Gateway. # (unset) — single-tenant / self-hosted; users manage tokens in Profile. # saas / hosted — multi-tenant hosted instance (e.g. ai.quantdinger.com): # users self-issue tokens (incl. T scope) from Profile → My Agent Token. # paper_only defaults to true; live T requires ack_live_trading_risk # at issuance plus AGENT_LIVE_TRADING_ENABLED on the server. QUANTDINGER_DEPLOYMENT_MODE= # ========================= # Email / SMTP (optional) # ========================= SMTP_HOST= SMTP_PORT=587 SMTP_USER= SMTP_PASSWORD= SMTP_FROM= SMTP_USE_TLS=true SMTP_USE_SSL=false # ========================= # Proxy (optional, often required for Binance/Coinbase OHLCV in some networks) # ========================= # PROXY_URL is for market data, exchange and broker API traffic. LLM providers do # not use it by default; configure LLM_PROXY_URL above if an LLM provider needs a proxy. # CCXT reads PROXY_URL for api.binance.com / exchange REST calls. System VPN does # NOT apply inside Docker; set this when logs show "binance GET ... exchangeInfo" failures. # Clash: enable "Allow LAN", use HTTP port (often 7890). Docker Desktop on Windows/macOS: # PROXY_URL=http://host.docker.internal:7890 # PROXY_URL=socks5h://127.0.0.1:10808 # backend run directly on host # PROXY_URL=socks5h://host.docker.internal:10808 # Docker + Clash SOCKS PROXY_URL= # Outbound HTTPS to exchanges when using PROXY_URL (especially SOCKS). If you see # SSLCertVerificationError / "unable to get local issuer certificate": # - Prefer: install OS ca-certificates in the image, or point to a PEM bundle: # LIVE_TRADING_CA_BUNDLE=/path/to/ca-bundle.pem # (REQUESTS_CA_BUNDLE / SSL_CERT_FILE are also honored.) # - Last resort only: LIVE_TRADING_SSL_VERIFY=false # disables TLS verify; insecure #LIVE_TRADING_CA_BUNDLE= #LIVE_TRADING_SSL_VERIFY= # ========================= # Local desktop brokers (IBKR) # ========================= # Interactive Brokers needs TWS/IB Gateway on a # machine reachable from this API (typically your own PC or a VPS with those apps). # On a public multi-tenant cloud deployment, set to false so users see a clear message # instead of broken flows. Crypto exchange API keys are unaffected. ALLOW_LOCAL_DESKTOP_BROKERS=true # IBKR: clientId for live orders (strategy worker). Must differ from POST /api/ibkr/connect # default (1) or TWS will disconnect one of the sessions within seconds. #IBKR_ORDER_CLIENT_ID=7 # ========================= # Captcha / OAuth (optional) # ========================= TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT_URI=http://localhost:5000/api/auth/oauth/google/callback GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GITHUB_REDIRECT_URI=http://localhost:5000/api/auth/oauth/github/callback # ========================= # Billing / payments # ========================= BILLING_ENABLED=false # 积分单价 BILLING_COST_AI_ANALYSIS=10 BILLING_COST_AI_CODE_GEN=30 BILLING_COST_AI_TUNING=50 CREDITS_REGISTER_BONUS=100 CREDITS_REFERRAL_BONUS=50 # Membership plans MEMBERSHIP_MONTHLY_PRICE_USD=19.9 MEMBERSHIP_YEARLY_PRICE_USD=199 MEMBERSHIP_LIFETIME_PRICE_USD=499 MEMBERSHIP_MONTHLY_CREDITS=500 MEMBERSHIP_YEARLY_CREDITS=8000 MEMBERSHIP_LIFETIME_MONTHLY_CREDITS=800 # ========================= # USDT payment (v3.0.6+: single fixed address per chain + amount-suffix matching) # ========================= # How it works (replaces the old xpub HD-derived per-order address model): # - One fixed receiving address per chain (your own main wallet). # - Every order is identified on-chain by a unique amount suffix in the # low decimals, e.g. base $19.9 -> 19.991234 USDT, where .001234 is the # unique order tag. Funds land directly in your wallet, no consolidation. # - Chains without an address configured are auto-hidden in the UI. # # Global switch. USDT_PAY_ENABLED=false # Comma-separated list of enabled chains. Any code not in this list is rejected # at order creation even if its address is configured. Valid codes: # TRC20 / BEP20 / ERC20 / SOL USDT_PAY_ENABLED_CHAINS=TRC20,BEP20,ERC20,SOL # --- Receiving addresses (one per chain). Leave blank to hide that chain --- # TRON (base58, starts with T...) USDT_TRC20_ADDRESS= # BSC (EVM 0x..., the same Ethereum-style address) USDT_BEP20_ADDRESS= # Ethereum (EVM 0x...) USDT_ERC20_ADDRESS= # Solana (wallet address; the SPL USDT mint ATA is computed on-chain by the wallet) USDT_SOL_ADDRESS= # --- USDT contract / mint per chain (override only if you know what you're doing) --- USDT_TRC20_CONTRACT=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t USDT_BEP20_CONTRACT=0x55d398326f99059fF775485246999027B3197955 USDT_ERC20_CONTRACT=0xdAC17F958D2ee523a2206206994597C13D831ec7 USDT_SOL_MINT=Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB # --- Block explorers / RPC endpoints used to reconcile incoming transfers --- TRONGRID_BASE_URL=https://api.trongrid.io TRONGRID_API_KEY= # BSC (BEP20) USDT reconciliation runs on public BSC JSON-RPC by default # — Etherscan's V2 free plan no longer covers BSC and the legacy v1 host # is deprecated. The watcher calls eth_getLogs against the USDT contract, # filtered by Transfer(from, to, value) events, which every public BSC # node supports natively at no cost. Comma-separated list; leave blank # to use the curated defaults baked into the code. BSC_RPC_URLS=https://bsc-dataseed1.binance.org,https://bsc.publicnode.com,https://rpc.ankr.com/bsc # ERC20 (Ethereum) USDT reconciliation uses Etherscan V2 first because # its free plan still covers Ethereum mainnet, falling back to public # Ethereum RPC if the explorer call fails. Set ETHERSCAN_API_KEY to a # key from https://etherscan.io/myapikey (free tier is enough for ETH). ETHERSCAN_API_KEY= ETHERSCAN_V2_BASE_URL=https://api.etherscan.io/v2/api ETH_RPC_URLS=https://ethereum-rpc.publicnode.com,https://rpc.ankr.com/eth # Override per-chain reconciliation strategy. Set BEP20_PREFER_EXPLORER=true # only if you upgrade to an Etherscan API paid plan that covers BSC; leave # it false (default) for RPC-only reconciliation. Same idea for ERC20. # BEP20_PREFER_EXPLORER=false # ERC20_PREFER_EXPLORER=true # Legacy v1 explorer overrides — only set these if you intentionally pin # to a deprecated v1 host (api.bscscan.com / api.etherscan.io). Leave them # commented out for new deployments; the v2 unified API + RPC fallback # above is preferred. # ETHERSCAN_BASE_URL= # BSCSCAN_BASE_URL= # BSCSCAN_API_KEY= SOLANA_RPC_URL=https://api.mainnet-beta.solana.com # --- Amount suffix policy --- # Number of decimals used for the suffix portion. 6 means ±0.000001 USDT # granularity, giving a collision space of ~10^5 per (chain, base_price) # inside the active window. The unique index on (chain, amount_usdt) where # status IN ('pending','paid') guarantees no two live orders collide. USDT_AMOUNT_SUFFIX_DECIMALS=6 # --- Confirm / expiry / worker tuning --- USDT_PAY_CONFIRM_SECONDS=30 USDT_PAY_EXPIRE_MINUTES=30 USDT_WORKER_POLL_INTERVAL=30 # ========================= # Advanced / rarely changed # ========================= # Network / App tuning PYTHON_API_HOST=0.0.0.0 PYTHON_API_PORT=5000 PYTHON_API_DEBUG=false # Interactive OpenAPI browsers (/api/docs/swagger, /api/docs/redoc). # Defaults to on when PYTHON_API_DEBUG=true; set true explicitly to enable in production. OPENAPI_ENABLED=false RATE_LIMIT=100 ENABLE_CACHE=false ENABLE_REQUEST_LOG=true # Strategy / execution tuning PENDING_ORDER_STALE_SEC=90 ORDER_MODE=market MAKER_WAIT_SEC=10 MAKER_OFFSET_BPS=2 # Spot sizing (also editable in Admin → Settings → Live Trading) # Close: cap sell size to free base × ratio (fees often make DB position > sellable qty) SPOT_CLOSE_SAFETY_RATIO=0.998 # Open: use only this fraction of allocated USDT/notional on spot buys SPOT_OPEN_QUOTE_BUFFER=0.995 STRATEGY_TICK_INTERVAL_SEC=10 # User script runtime guardrails. Keep callback timeout below tick interval # so one bad strategy cannot occupy a live execution thread indefinitely. STRATEGY_SCRIPT_CALLBACK_TIMEOUT_SEC=5 STRATEGY_SCRIPT_MAX_CONSECUTIVE_TIMEOUTS=3 STRATEGY_SCRIPT_MAX_BARS_PER_CALL=1000 STRATEGY_SCRIPT_MAX_LOGS_PER_FLUSH=50 STRATEGY_SCRIPT_MAX_LOG_CHARS=500 # Live grid bots ignore per-strategy tick_interval_sec; fixed price loop (seconds). GRID_STRATEGY_TICK_SEC=1 # Global grid fill poller (ENABLE_GRID_FILL_POLLER=true in app startup). ENABLE_GRID_FILL_POLLER=true GRID_FILL_POLL_SEC=3 # Per resting order min interval between exchange status queries (seconds). GRID_FILL_MIN_ORDER_INTERVAL_SEC=5 # Max orders polled per poller loop (spread load across cycles). GRID_FILL_MAX_ORDERS_PER_CYCLE=150 # Max exchange API calls per credential per minute (rate-limit guard). GRID_FILL_MAX_REQ_PER_CREDENTIAL_PER_MIN=120 # Grid: auto-stop after N consecutive order failures (non-fatal errors). GRID_ORDER_ERROR_STOP_THRESHOLD=5 # 单进程内「实盘/信号策略」并发线程上限(每个运行中策略约占用 1 条线程)。默认 64;调高需评估内存与系统线程上限,改后重启 API。 STRATEGY_MAX_THREADS=64 PRICE_CACHE_TTL_SEC=10 K_LINE_HISTORY_GET_NUMBER=500 SIGNAL_NOTIFY_TIMEOUT_SEC=6 # LLM advanced tuning OPENROUTER_API_URL=https://openrouter.ai/api/v1/chat/completions OPENROUTER_TEMPERATURE=0.7 OPENROUTER_MAX_TOKENS=4000 OPENROUTER_TIMEOUT=300 OPENROUTER_CONNECT_TIMEOUT=30 OPENAI_BASE_URL=https://api.openai.com/v1 DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 GROK_BASE_URL=https://api.x.ai/v1 MINIMAX_BASE_URL=https://api.minimax.io/v1 # Data sources DATA_SOURCE_TIMEOUT=30 DATA_SOURCE_RETRY=3 DATA_SOURCE_RETRY_BACKOFF=0.5 FINNHUB_API_KEY= # Optional free-tier Finnhub helper for US stock quotes, company profile and news. # Keep FINNHUB_FREE_ONLY=true unless your plan includes paid endpoints such as Economic Calendar/Social Sentiment. FINNHUB_FREE_ONLY=true FINNHUB_TIMEOUT=10 FINNHUB_RATE_LIMIT=60 # Optional official international economic calendar provider. # Leave empty to use the no-key AkShare/WallstreetCN fallback. The legacy Trading Economics guest account is discontinued. TRADING_ECONOMICS_CLIENT= TRADING_ECONOMICS_KEY= TRADING_ECONOMICS_BASE_URL=https://api.tradingeconomics.com TRADING_ECONOMICS_TIMEOUT=10 # US macro time-series and official macro detail sources. FRED_API_KEY= FRED_BASE_URL=https://api.stlouisfed.org/fred FRED_TIMEOUT=10 BLS_API_KEY= BLS_BASE_URL=https://api.bls.gov/publicAPI/v2 BLS_TIMEOUT=10 BEA_API_KEY= BEA_BASE_URL=https://apps.bea.gov/api/data BEA_TIMEOUT=10 # Public crypto OHLCV/ticker (no API keys). binance supports BTC/USDT. # In mainland CN / Docker without outbound access to api.binance.com, set PROXY_URL above. CCXT_DEFAULT_EXCHANGE=binance CCXT_TIMEOUT=10000 AKSHARE_TIMEOUT=30 YFINANCE_TIMEOUT=30 TIINGO_API_KEY= TIINGO_TIMEOUT=10 # Crypto derivatives / on-chain data (optional, used by some AI prompts) COINGLASS_API_KEY= CRYPTOQUANT_API_KEY= # Twelve Data (CN/HK stock K-lines — recommended for overseas servers) # Free tier: 800 API credits/day, 8 requests/minute. https://twelvedata.com TWELVE_DATA_API_KEY= # Adanos Market Sentiment (optional US stock sentiment enrichment) # If ADANOS_API_KEY is empty, /api/global-market/adanos-sentiment returns enabled=false. ADANOS_API_KEY= ADANOS_SENTIMENT_SOURCE=reddit ADANOS_API_BASE_URL=https://api.adanos.org ADANOS_API_TIMEOUT=10 # AI search / news # Recommended order: Tavily -> SearXNG -> GDELT -> SerpAPI -> DuckDuckGo. SEARCH_PROVIDER=tavily SEARCH_MAX_RESULTS=10 SEARCH_GOOGLE_API_KEY= SEARCH_GOOGLE_CX= SEARCH_BING_API_KEY= TAVILY_API_KEYS= SERPAPI_KEYS= SEARCH_SEARXNG_BASE_URL= SEARCH_SEARXNG_ENGINES= SEARCH_SEARXNG_CATEGORIES=general SEARCH_SEARXNG_LANGUAGE=auto SEARCH_SEARXNG_TIMEOUT=12 GDELT_BASE_URL=https://api.gdeltproject.org/api/v2/doc/doc GDELT_TIMEOUT=12 GDELT_MAX_RESULTS=10 ALPHA_VANTAGE_API_KEY= ALPHA_VANTAGE_BASE_URL=https://www.alphavantage.co/query ALPHA_VANTAGE_TIMEOUT=12 ALPHA_VANTAGE_NEWS_LIMIT=20 # SMS / phone (optional) TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_FROM_NUMBER= # Security / verification tuning SECURITY_IP_MAX_ATTEMPTS=10 SECURITY_IP_WINDOW_MINUTES=5 SECURITY_IP_BLOCK_MINUTES=15 SECURITY_ACCOUNT_MAX_ATTEMPTS=5 SECURITY_ACCOUNT_WINDOW_MINUTES=60 SECURITY_ACCOUNT_BLOCK_MINUTES=30 VERIFICATION_CODE_EXPIRE_MINUTES=10 VERIFICATION_CODE_RATE_LIMIT=60 VERIFICATION_CODE_IP_HOURLY_LIMIT=10 VERIFICATION_CODE_MAX_ATTEMPTS=5 VERIFICATION_CODE_LOCK_MINUTES=30 # AI analysis tuning ENABLE_CONFIDENCE_CALIBRATION=false ENABLE_AI_ENSEMBLE=false AI_ENSEMBLE_MODELS=openai/gpt-5.4,openai/gpt-4o-mini ENABLE_REFLECTION_WORKER=false REFLECTION_WORKER_INTERVAL_SEC=86400 REFLECTION_MIN_AGE_DAYS=7 REFLECTION_VALIDATE_LIMIT=200 AI_CALIBRATION_MARKETS=Crypto AI_CALIBRATION_LOOKBACK_DAYS=30 AI_CALIBRATION_MIN_SAMPLES=80 AI_ANALYSIS_CONSENSUS_TIMEFRAMES=1D,4H # Redis cache (auto-configured by docker-compose; set CACHE_ENABLED=true to use) REDIS_HOST=redis REDIS_PORT=6379 CACHE_ENABLED=true # Internal INTERNAL_API_KEY= # Market visibility (which markets appear in the watchlist picker / # Agent API catalog / home AI radar). # # Primary knob (recommended): # ENABLED_MARKETS — CSV whitelist. When set, ONLY these markets are # visible everywhere. Unknown values are ignored. Empty = whitelist # disabled (falls back to the legacy SHOW_* flags below). # Valid values: Crypto, USStock, CNStock, HKStock, Forex, Futures, MOEX # Examples: # ENABLED_MARKETS=Crypto,USStock # crypto + US stocks only # ENABLED_MARKETS=Crypto # crypto-only deployment # ENABLED_MARKETS= # unset → legacy flags apply # ENABLED_MARKETS= # # Legacy flags (kept for back-compat; ignored when ENABLED_MARKETS is set): # SHOW_CN_STOCK (default false) — China A-shares # SHOW_HK_STOCK (default true) — Hong Kong stocks # All other known markets default to visible. SHOW_CN_STOCK=false # SHOW_HK_STOCK=true # ============================================================ # ALPACA MARKETS (US stocks, ETFs, crypto) # ============================================================ # Paper: keys start with "PK..." (paper-api.alpaca.markets) # Live: keys start with "AK..." (api.alpaca.markets) # Get keys: https://app.alpaca.markets ALPACA_API_KEY= ALPACA_SECRET_KEY= ALPACA_PAPER=true