QuantDinger QuantDinger Docs
Home GitHub Launch App
QuantDinger v3.0.2

QuantDinger Documentation

Self-hosted AI trading platform for quant research, Python strategy development, backtesting, and live execution. One stack for AI analysis, charting, strategy code, backtests, quick trade, and live operations — on infrastructure you fully control.

Docker-first install

One command spins up the full backend, PostgreSQL, Redis, and Nginx-served frontend.

Python-native

Write strategies in pandas dataframes or event-driven on_bar scripts.

AI built into the loop

OpenAI, Claude, Gemini, DeepSeek, Grok — analysis, generation, ensemble, calibration.

Mobile + Web + Native

Full mobile H5, Android Capacitor build, Vue desktop — all talk to the same API.

Introduction #

QuantDinger is a self-hosted quantitative trading and algorithmic trading platform for AI-assisted research, Python strategy development, backtesting, and live execution. Your credentials, strategy code, market workflows, and operational data stay under your control.

Capabilities include:

  • AI market analysis — structured, low-latency analysis with memory, ensemble voting, and confidence calibration across OpenAI, Claude, Gemini, DeepSeek, Grok and more.
  • Python strategy development — dataframe-based IndicatorStrategy or event-driven ScriptStrategy. AI can draft a starting point; you stay in control of the code.
  • Deterministic backtesting — commission and slippage modeling, trade-by-trade analytics, equity curves. Every run is pinned to a code hash and config snapshot.
  • Autonomous trading bots — Grid, Martingale, Trend Following, and DCA. Execution-aware, restart-resilient, signal or live execution modes.
  • Operator-ready — multi-user PostgreSQL, role-based access, Google / GitHub OAuth, memberships, credits, USDT payments, Telegram / Email / SMS / Discord / Webhook alerts.

Architecture #

QuantDinger runs as a self-hosted application stack:

LayerTechnology
FrontendPrebuilt Vue application served by Nginx
BackendFlask API, Python services, strategy runtime
StoragePostgreSQL 16
Cache / worker supportRedis 7
Trading layerExchange adapters, IBKR, MT5
AI layerLLM provider integration, memory, calibration, optional workers
BillingMembership, credits, USDT TRC20 payment flow
DeploymentDocker Compose with health checks

Execution model

  • Market data is pulled through a pluggable data layer.
  • Backtests run on the server-side strategy engine, including strategy snapshot handling.
  • Live strategies run through runtime services that generate order intent.
  • Pending orders are dispatched through exchange-specific execution adapters.
  • Crypto live execution is intentionally separated from market-data collection concerns.

Quick Start #

The fastest way to try QuantDinger locally — everything runs in Docker, no Node.js required:

bash
git clone https://github.com/brokermr810/QuantDinger.git
cd QuantDinger
cp backend_api_python/env.example backend_api_python/.env
./scripts/generate-secret-key.sh
docker-compose up -d --build
powershell
git clone https://github.com/brokermr810/QuantDinger.git
cd QuantDinger
Copy-Item backend_api_python\env.example -Destination backend_api_python\.env
$key = py -c "import secrets; print(secrets.token_hex(32))"
(Get-Content backend_api_python\.env) -replace '^SECRET_KEY=.*$', "SECRET_KEY=$key" | Set-Content backend_api_python\.env -Encoding UTF8
docker-compose up -d --build

After startup:

  • Frontend: http://localhost:8888
  • Backend health check: http://localhost:5000/api/health
  • Default login: quantdinger / 123456
Before production
The backend container will not start if SECRET_KEY still uses the default value. Always regenerate it and change the admin password before exposing the service to the internet.

Prerequisites #

  • Docker 20+ and Docker Compose v2
  • A clean host with at least 2 vCPU / 4 GB RAM / 20 GB disk for small teams
  • (Optional) Nginx on the host for custom domains, SSL, and reverse proxying — the stack already ships with its own Nginx for local use

Node.js is not required for deployment because this repository ships the prebuilt frontend in frontend/dist.

Docker Compose #

The default stack includes frontend, backend, postgres, and redis, wired together with health checks. The main application config lives in backend_api_python/.env.

Spin it up

bash
docker-compose up -d --build

Stop and remove

bash
docker-compose down
# keep data (Postgres/Redis volumes); pass -v to wipe volumes
docker-compose down -v

Common commands #

bash
docker-compose ps                    # show container status
docker-compose logs -f backend       # tail backend logs
docker-compose restart backend       # restart only the backend
docker-compose up -d --build         # rebuild after config/code changes
docker-compose exec backend bash     # open a shell inside the backend container
Faster rebuilds
If you only changed backend_api_python/.env, you can just run docker-compose restart backend — there is no need to rebuild the image.

Optional root .env #

If you need custom ports or image mirrors, create a root .env next to docker-compose.yml:

ini
FRONTEND_PORT=3000
BACKEND_PORT=127.0.0.1:5001
IMAGE_PREFIX=docker.m.daocloud.io/library/

Binding the backend to 127.0.0.1:5001 means only your host Nginx can reach it — recommended for production where a reverse proxy terminates SSL.

Configuration overview #

All runtime configuration happens through backend_api_python/.env. Start from env.example in the same folder and change values in place.

AreaExample keys
AuthenticationSECRET_KEY, ADMIN_USER, ADMIN_PASSWORD
DatabaseDATABASE_URL
LLM / AILLM_PROVIDER, OPENAI_API_KEY, OPENROUTER_API_KEY
OAuthGOOGLE_CLIENT_ID, GITHUB_CLIENT_ID
SecurityTURNSTILE_SITE_KEY, ENABLE_REGISTRATION
BillingBILLING_ENABLED, BILLING_COST_AI_ANALYSIS
MembershipMEMBERSHIP_MONTHLY_PRICE_USD, MEMBERSHIP_MONTHLY_CREDITS
USDT paymentsUSDT_PAY_ENABLED, USDT_TRC20_XPUB, TRONGRID_API_KEY
ProxyPROXY_URL
WorkersENABLE_PENDING_ORDER_WORKER, ENABLE_PORTFOLIO_MONITOR, ENABLE_REFLECTION_WORKER
AI tuningENABLE_AI_ENSEMBLE, ENABLE_CONFIDENCE_CALIBRATION, AI_ENSEMBLE_MODELS

Authentication #

The SECRET_KEY signs all JWT tokens. It must be a cryptographically-random 64-char hex string — the backend refuses to start with the default placeholder.

env
SECRET_KEY=<run: python -c "import secrets; print(secrets.token_hex(32))">
ADMIN_USER=quantdinger
ADMIN_PASSWORD=change-me-before-going-live

# Token lifetime in seconds (default 7 days)
JWT_EXPIRATION=604800
Rotating SECRET_KEY
Rotating the key invalidates every user token immediately. All logged-in users will be forced to sign in again. Do this before launch, not during.

Database #

Point DATABASE_URL at PostgreSQL 16. The default value targets the in-stack postgres service from Docker Compose:

env
DATABASE_URL=postgresql://quantdinger:quantdinger@postgres:5432/quantdinger

Migrations run automatically on startup from backend_api_python/migrations/init.sql. For managed database providers (Supabase, Neon, AWS RDS…), replace the URL and make sure inbound connections are allowed from your host.

AI providers #

QuantDinger ships adapters for several LLM providers. Pick any subset that matches your budget and compliance posture.

ProviderKeyTypical use
OpenRouterOPENROUTER_API_KEYAggregator — best first choice
OpenAIOPENAI_API_KEYGPT-4o / GPT-5 family
AnthropicCLAUDE_API_KEYClaude 3.x / 4.x
GoogleGEMINI_API_KEYGemini 1.5 / 2.0
DeepSeekDEEPSEEK_API_KEYCost-effective reasoning
xAIGROK_API_KEYGrok family

Ensemble and calibration

For teams that want more robust AI outputs, turn on ensemble voting and confidence calibration:

env
ENABLE_AI_ENSEMBLE=true
AI_ENSEMBLE_MODELS=openai:gpt-4o,anthropic:claude-3-5-sonnet,google:gemini-2.0-flash
ENABLE_CONFIDENCE_CALIBRATION=true

Google / GitHub OAuth #

OAuth keeps the login experience modern and removes password friction. Register an application on each provider, then set:

env
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

# Whitelist of frontend origins the backend will redirect back to after OAuth.
# Include every domain you serve the app from (PC, mobile, staging).
OAUTH_ALLOWED_REDIRECTS=https://ai.quantdinger.com,https://m.quantdinger.com

# Primary frontend URL for redirect fallback
FRONTEND_URL=https://ai.quantdinger.com
Callback URL
The callback URL registered with Google / GitHub is always the backend's /api/auth/oauth/<provider>/callback, not the frontend URL. The backend then redirects back to the frontend origin listed in OAUTH_ALLOWED_REDIRECTS.

If the mobile app is served on a different origin, make sure that origin is added to OAUTH_ALLOWED_REDIRECTS before you test — otherwise the backend will redirect users to the fallback FRONTEND_URL.

Cloudflare Turnstile #

Turnstile gates the login, register, and password-reset flows to keep bots out. Enable it once you go public:

env
TURNSTILE_ENABLED=true
TURNSTILE_SITE_KEY=0x4AAA...
TURNSTILE_SECRET_KEY=0x4AAA...
CSP must allow challenges.cloudflare.com
If you added a Content-Security-Policy header (via Nginx or Cloudflare), the widget will fail to load. Whitelist https://challenges.cloudflare.com for both script-src and frame-src.

Billing & membership #

The backend ships with first-class memberships, credits, and per-action pricing. Start with a conservative default, then adjust as you see real usage:

env
BILLING_ENABLED=true
BILLING_COST_AI_ANALYSIS=1
BILLING_COST_STRATEGY_GEN=3
BILLING_COST_BACKTEST=2

MEMBERSHIP_MONTHLY_PRICE_USD=29
MEMBERSHIP_MONTHLY_CREDITS=1500
MEMBERSHIP_YEARLY_PRICE_USD=299
MEMBERSHIP_YEARLY_CREDITS=21000

USDT payments #

The USDT TRC20 flow generates a unique deposit address per user and polls TronGrid for confirmations. Fund credits are issued automatically when the network confirms.

env
USDT_PAY_ENABLED=true
USDT_TRC20_XPUB=xpub6...    # BIP-32 extended public key (TRC20 HD derivation)
TRONGRID_API_KEY=...
USDT_CONFIRMATIONS_REQUIRED=12
Hot-wallet hygiene
The xpub only produces receiving addresses — never the corresponding private keys. Keep the matching cold wallet on an offline machine and sweep balances out periodically.

Notifications #

Each user can wire their own Telegram / Email / SMS / Discord / Webhook from the in-app Profile → Notifications page. The system-level fallbacks (admin alerts, cron digests) are configured here:

env
# Email (SMTP)
SMTP_HOST=smtp.zoho.com
SMTP_PORT=465
SMTP_USER=alerts@yourdomain.com
SMTP_PASSWORD=...
SMTP_FROM=QuantDinger <alerts@yourdomain.com>

# Telegram (used for admin alerts; per-user bots are set in app)
TELEGRAM_BOT_TOKEN=...
TELEGRAM_ADMIN_CHAT_ID=...

# SMS (Twilio-compatible)
SMS_PROVIDER=twilio
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_FROM_NUMBER=+1555...

Worker toggles #

Background workers run inside the backend container. Disable any that you do not need — they save CPU and reduce startup noise:

env
ENABLE_PENDING_ORDER_WORKER=true    # watches pending limit orders across exchanges
ENABLE_PORTFOLIO_MONITOR=true       # P&L, risk, alert triggers
ENABLE_REFLECTION_WORKER=false      # long-horizon AI self-review
ENABLE_BACKTEST_QUEUE=true          # async backtest runner

Strategy Development #

QuantDinger supports two complementary strategy authoring models. Pick the one that matches the shape of your logic:

IndicatorStrategy

Dataframe-based Python. Transform the df and return buy/sell boolean columns. Great for research, signal prototyping, and chart overlays.

ScriptStrategy

Event-driven. Implement on_init(ctx) / on_bar(ctx, bar) and call ctx.buy() / ctx.sell() / ctx.close_position() explicitly. Great for stateful, execution-oriented logic.

IndicatorStrategy #

A minimal dual-moving-average crossover:

python
# @param sma_short int 14 Short moving average
# @param sma_long  int 28 Long moving average

sma_short_period = params.get('sma_short', 14)
sma_long_period  = params.get('sma_long',  28)

my_indicator_name        = "Dual Moving Average Strategy"
my_indicator_description = f"SMA {sma_short_period}/{sma_long_period} crossover"

df = df.copy()
sma_short = df["close"].rolling(sma_short_period).mean()
sma_long  = df["close"].rolling(sma_long_period).mean()

buy  = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))

df["buy"]  = buy.fillna(False).astype(bool)
df["sell"] = sell.fillna(False).astype(bool)

Inputs available to the script:

  • df — pandas DataFrame with at least open, high, low, close, volume, ts.
  • params — dict of the user-supplied parameters declared via # @param.

Expected outputs:

  • df["buy"], df["sell"] — boolean signal columns (required).
  • my_indicator_name, my_indicator_description — display metadata (optional).
  • Any additional float columns on df — plotted as indicators on the chart.

ScriptStrategy #

Skeleton of a grid-style bot in ScriptStrategy form:

python
def on_init(ctx):
    ctx.grid_pct     = float(ctx.param("grid_pct", 0.012))
    ctx.grid_levels  = int(  ctx.param("grid_levels", 6))
    ctx.order_pct    = float(ctx.param("order_pct", 0.08))
    ctx.anchor_price = None
    ctx.log("Init grid bot: grid_pct=%.4f levels=%d"
            % (ctx.grid_pct, ctx.grid_levels))


def on_bar(ctx, bar):
    price = float(bar.close)
    if ctx.anchor_price is None:
        ctx.anchor_price = price
        return

    lower = ctx.anchor_price * (1.0 - ctx.grid_pct)
    upper = ctx.anchor_price * (1.0 + ctx.grid_pct)

    if price <= lower:
        amount = (float(ctx.balance) * ctx.order_pct) / price
        if amount > 0:
            ctx.buy(price, amount)
            ctx.anchor_price = price

    elif price >= upper and ctx.position:
        ctx.sell(price, float(ctx.position["size"]))
        ctx.anchor_price = price


def on_stop(ctx):
    ctx.log("Grid bot stopped")

Context API

MemberDescription
ctx.param(name, default)User-configurable parameter lookup.
ctx.balanceAvailable quote-currency balance.
ctx.equityTotal account equity (balance + open positions marked-to-market).
ctx.positionCurrent open position as {size, entry_price, side} or None.
ctx.bars(n)Most recent n bars (list, oldest first).
ctx.buy(price, amount)Submit a buy order.
ctx.sell(price, amount)Submit a sell order.
ctx.close_position()Flatten the current position.
ctx.log(msg)Write to the strategy log.

Lifecycle hooks

  • on_init(ctx) — called once when the strategy starts.
  • on_bar(ctx, bar) — called for each new closed candle.
  • on_order_filled(ctx, order) — called after an order is executed.
  • on_stop(ctx) — called once on shutdown.

Declaring parameters #

Both strategy types use the same comment syntax so the UI can render a typed form:

python
# @param sma_short  int    14     Short moving average
# @param sma_long   int    28     Long moving average
# @param use_filter bool   true   Require trend filter
# @param symbol     string BTCUSDT Instrument to trade

Supported types: int, float, bool, string. The last field is the human-readable label.

Backtesting #

Every backtest is pinned to:

  • the exact code hash of the script,
  • a frozen parameter snapshot,
  • the commission and slippage model you chose,
  • the market data range.

Outputs are persisted to PostgreSQL and include per-trade P&L, equity curve, max drawdown, Sharpe, win rate, and exposure time. Re-running with the same snapshot reproduces results deterministically.

Trading Bots #

Four built-in bot archetypes cover most mechanical-trading workflows. Each one is a first-class ScriptStrategy template with a guided UI.

Grid #

Classic range trader that lays a ladder of buy/sell orders around an anchor price. Best for sideways, mean-reverting instruments.

ParameterMeaning
grid_pctPrice step between grid levels, as a fraction of the anchor.
grid_levelsHow many levels exist on each side.
order_pctOrder size per level as a fraction of balance.
max_position_value_pctCap on total capital deployed.
take_profit_pctClose the whole stack when P&L crosses this threshold.

Martingale #

Averaging-down system that increases position size after adverse moves. Highest reward when markets mean-revert, highest risk when trends break out.

Use strict risk limits
Always configure max_daily_loss and max_position_value. Martingale can wipe out an account during a single prolonged trend.

Trend #

Break-out and moving-average crossover bot. Enters with the trend, rides momentum, and uses trailing stops to lock in gains. Best for directional markets.

DCA #

Dollar-cost averaging bot that buys a fixed notional on a schedule — daily, weekly, or on dips past a threshold. Ideal for long-horizon accumulation in spot markets.

Crypto exchanges #

VenueCoverage
BinanceSpot, Futures, Margin
OKXSpot, Perpetual, Options
BitgetSpot, Futures, Copy Trading
BybitSpot, Linear Futures
CoinbaseSpot
KrakenSpot, Futures
KuCoinSpot, Futures
Gate.ioSpot, Futures
DeepcoinDerivatives integration
HTXSpot, USDT-margined perpetuals

Add credentials from Profile → API keys. Keys are encrypted at rest and only decrypted in the execution path.

US stocks via IBKR #

Interactive Brokers is wired up through the standard TWS / IB Gateway bridge. Data is available through Yahoo Finance and Finnhub when a live IBKR subscription is not desired for research.

env
IBKR_HOST=127.0.0.1
IBKR_PORT=7497         # TWS paper: 7497, live: 7496. Gateway paper: 4002, live: 4001.
IBKR_CLIENT_ID=17
FINNHUB_API_KEY=...    # optional, richer fundamentals

Forex via MT5 #

MT5 integration runs through the Python MetaTrader 5 bridge. Both data and execution are supported. OANDA is supported as a read-only data source.

Polymarket #

Polymarket is currently supported as a research and analysis workflow — market lookup, divergence analysis, opportunity scoring, AI-assisted review — not as direct in-platform live execution.

Mobile web (H5) #

The mobile client is a separate Vue 3 + Vant app that talks to the same backend API as the desktop one. To build the H5 bundle for deployment:

bash
cd quantdinger_mobile
npm install          # first time only
npm run build        # outputs to ./dist

Serve dist/ with any static web server. A production Nginx config is documented in Mobile Nginx config below.

Android APK build #

The mobile app is wrapped with Capacitor. To produce a debug APK:

1. Install Android Studio

Install the stable release of Android Studio. It bundles the JDK (jbr/) and the Android SDK you need.

2. Set environment variables

powershell
$env:JAVA_HOME   = "C:\Program Files\Android\Android Studio\jbr"
$env:ANDROID_HOME = "$env:LOCALAPPDATA\Android\Sdk"
$env:Path = "$env:JAVA_HOME\bin;$env:ANDROID_HOME\platform-tools;$env:Path"

3. Build the web bundle and sync

bash
npm run build
npx cap sync android

4. Point Gradle at the SDK

Create android/local.properties:

ini
sdk.dir=C\:\\Users\\YourName\\AppData\\Local\\Android\\Sdk

5. Run the Gradle build

bash
cd android
./gradlew assembleDebug     # Linux/macOS
.\gradlew.bat assembleDebug # Windows

The APK lands at android/app/build/outputs/apk/debug/app-debug.apk.

Gradle download times out?
Swap the Gradle mirror inside android/gradle/wrapper/gradle-wrapper.properties — for example https://mirrors.cloud.tencent.com/gradle/gradle-8.2.1-all.zip. This is the most common fix in regions where services.gradle.org is slow.

Release APK

Generate a keystore, add a signingConfigs block in android/app/build.gradle, then run ./gradlew assembleRelease. Output: android/app/build/outputs/apk/release/app-release.apk.

Mobile Nginx config #

Reference Nginx config for serving the H5 bundle on m.quantdinger.com, with SPA history-mode fallback and reverse-proxy to the backend:

nginx
server {
    listen 80;
    server_name m.quantdinger.com;
    location ^~ /.well-known/acme-challenge { allow all; root /usr/share/nginx/html; }
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl http2;
    server_name m.quantdinger.com;

    ssl_certificate     /www/sites/m.quantdinger.com/ssl/fullchain.pem;
    ssl_certificate_key /www/sites/m.quantdinger.com/ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_session_cache  shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=31536000" always;

    root  /www/sites/m.quantdinger.com/index;
    index index.html;

    location /api/ {
        proxy_pass         http://127.0.0.1:5000/api/;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 120s;
    }

    location ~* \.(?:js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico|webp|map)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
        access_log off;
    }

    location = /index.html {
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        expires off;
    }

    # SPA history-mode fallback — required for routes like /login, /trading, etc.
    location / {
        try_files $uri $uri/ /index.html;
    }
}

Production checklist #

  • Regenerate SECRET_KEY with 64 random hex bytes.
  • Change ADMIN_USER / ADMIN_PASSWORD from the defaults.
  • Bind the backend to 127.0.0.1 and terminate TLS at the host Nginx.
  • Enable Turnstile on public login/register/reset endpoints.
  • Point DATABASE_URL at a managed PostgreSQL with automated backups — or snapshot the Docker volume daily.
  • Allowlist mobile + desktop origins in OAUTH_ALLOWED_REDIRECTS.
  • Turn off any worker you don't use (reflection, portfolio monitor).
  • Rotate exchange API keys quarterly; create read-only keys wherever possible.

PC site Nginx #

For the desktop app at ai.quantdinger.com, use a similar config but proxy the frontend container (the prebuilt Vue app lives inside the frontend service on port 80 by default):

nginx
server {
    listen 443 ssl http2;
    server_name ai.quantdinger.com;

    ssl_certificate     /etc/letsencrypt/live/ai.quantdinger.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ai.quantdinger.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location /api/ {
        proxy_pass http://127.0.0.1:5000/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location / {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
    }
}

SSL / HTTPS #

Use Let's Encrypt with certbot or your hosting panel's built-in ACME. Keep only TLSv1.2 and TLSv1.3 — older versions (TLSv1, TLSv1.1) are rejected by modern browsers and will surface as ERR_SSL_PROTOCOL_ERROR.

OAuth allowed redirects #

After OAuth, the backend issues a short-lived oauth_token and redirects the user to the frontend origin. For that redirect to succeed:

  • The frontend origin must be listed in OAUTH_ALLOWED_REDIRECTS.
  • The frontend route must exist — for history-mode SPAs, ensure /login is reachable and not a 404 (see the SPA fallback rule).
  • If your desktop client uses hash-mode routing, the backend preserves the hash (/#/user/login) automatically.

Troubleshooting #

Backend refuses to start: "SECRET_KEY is unsafe"

Regenerate the key and restart:

bash
python -c "import secrets; print(secrets.token_hex(32))"
# copy the output into backend_api_python/.env  →  SECRET_KEY=...
docker-compose restart backend

Google / GitHub OAuth redirects to the wrong domain

  • Add the exact origin (including scheme and port if non-default) to OAUTH_ALLOWED_REDIRECTS.
  • Rebuild the backend: docker-compose up -d --build backend.
  • Clear cookies for the OAuth provider and retry.

Mobile H5 shows 404 on /login after OAuth

The Nginx config is missing the SPA fallback. Add try_files $uri $uri/ /index.html; inside location /.

Turnstile widget fails to load (code 600010)

  • Check the response headers for Content-Security-Policy.
  • Whitelist https://challenges.cloudflare.com under script-src and frame-src, or remove the CSP header entirely while you debug.
  • Confirm the Site Key's Hostname Management in the Cloudflare dashboard includes the domain you are serving from.

Gradle wrapper download times out

Edit android/gradle/wrapper/gradle-wrapper.properties and swap distributionUrl to a nearby mirror:

properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.2.1-all.zip
networkTimeout=60000

ERR_SSL_PROTOCOL_ERROR in the browser

  • Restrict ssl_protocols to TLSv1.2 TLSv1.3 only — drop TLSv1 and TLSv1.1.
  • Verify the certificate files exist at the paths configured in Nginx.
  • Reload Nginx (nginx -t && nginx -s reload).

FAQ #

Is QuantDinger really self-hosted?

Yes. The default deployment model is your own Docker Compose stack with your own database, Redis instance, credentials, and environment configuration.

Is it only for crypto trading?

No. Crypto is a major focus, but the platform also includes IBKR workflows for US stocks, MT5 workflows for forex, and Polymarket research support.

Can I write strategies directly in Python?

Yes — both IndicatorStrategy and ScriptStrategy are raw Python. You can also use AI to generate a starting point and then edit it yourself.

Is this a research tool or a live trading platform?

Both. QuantDinger is built to connect AI research, charting, strategy development, backtesting, quick trade flows, and live execution operations in one system.

Can I use QuantDinger commercially?

The backend is Apache 2.0. The frontend has a separate source-available license — commercial use is supported but review the license files in the repository first.

License #

  • Backend: Apache License 2.0 (see LICENSE).
  • Frontend UI is distributed here as prebuilt files. The Vue source lives at QuantDinger-Vue under the QuantDinger Frontend Source-Available License v1.0.
  • Non-commercial and eligible qualified non-profit use of the frontend is free of charge; commercial use requires a separate commercial license.
  • Trademark, branding, attribution, and watermark usage are governed by TRADEMARKS.md.

Community & support #

Found a bug or have a feature request? Open an issue on GitHub — we triage weekly.