Auto-Ingest Data

Send your strategy output as JSON to visualize it on QuantMaxi. Use AI to adapt your existing code in seconds.

🤖 Adapt Your Code with AI

Copy the prompt below, paste it into ChatGPT or Claude, then paste your strategy code right after it. The AI will return your code adapted to the QuantMaxi format.

You are a trading-strategy code assistant.
I have an existing strategy script that produces chart data.
Adapt ONLY the output / export step so it produces a JSON payload matching the QuantMaxi format below.
Do NOT change any strategy logic, indicators, or calculations.

--- QuantMaxi Payload Contract ---

Top-level shape (TypeScript):
{
  ticker: string          // e.g. "BTC-USD"
  interval: string        // e.g. "1d", "1h", "15m"
  columns: string[]       // must include "time" + every data column name
  data: Array<Record<string, number | null>>
  metadata: Record<string, object>
}

Data rules:
- "time" = Unix seconds (UTC integer). If you have milliseconds, divide by 1000.
- Rows sorted ascending by time, no duplicates.
- All plotted values must be numeric. Replace NaN / Infinity with null or 0.
- Max 200 000 rows, 512 columns per payload.

Metadata rules:
- One key per data column you want to plot.
- Each key = { "plot": true, "type": "<type>", ... }
- Supported types and their required fields:

  candlestick  ->  4 entries with "component": "open" | "high" | "low" | "close"
  line         ->  "color": "#hex", "paneIndex": 0
  area         ->  "color": "#hex", "paneIndex": 0
  histogram    ->  "upColor": "#hex", "downColor": "#hex", "paneIndex": 0
  markers      ->  "color": "#hex", "shape": "circle", "position": "aboveBar"

- paneIndex 0 = main chart pane. Use 1, 2, ... for separate panes below.
- Candlestick always goes on pane 0 (no paneIndex needed).
- Volume histogram typically goes on pane 0 with upColor/downColor.
- Legacy "chart: N" maps to paneIndex: N-1 automatically.

Return ONLY:
1. The adapted code (minimal changes to my script).
2. One example JSON output snippet showing the payload structure.

--- My existing code is below ---
The prompt includes the full payload contract. Paste your code directly after --- My existing code is below --- and the AI will handle the rest.

Payload Reference

TypeScript shape
{
  ticker: string,
  interval: string,
  columns: string[],
  data: Array<Record<string, number | string | null>>,
  metadata: Record<string, any>
}
  • time = Unix seconds (divide ms by 1000, ns by 1e9)
  • Rows sorted ascending by time, no duplicates
  • All plotted values must be numeric — clean NaN / Infinity first
  • Candlestick requires all 4 OHLC components in metadata
  • Max 200k rows, 512 columns per payload
Legacy chart: N is auto-mapped to paneIndex: N-1. Use paneIndex directly for new code.
Valid payload example
{
  "ticker": "BTC-USD",
  "interval": "1d",
  "columns": ["time","Open","High","Low","Close","Volume","SMA20","Equity"],
  "data": [
    {
      "time": 1713657600,
      "Open": 64123.4, "High": 64980.2, "Low": 63881.7, "Close": 64720.1,
      "Volume": 22812.6, "SMA20": 63110.2, "Equity": 1000000.0
    }
  ],
  "metadata": {
    "Open":   { "plot": true, "type": "candlestick", "component": "open" },
    "High":   { "plot": true, "type": "candlestick", "component": "high" },
    "Low":    { "plot": true, "type": "candlestick", "component": "low" },
    "Close":  { "plot": true, "type": "candlestick", "component": "close" },
    "SMA20":  { "plot": true, "type": "line", "color": "#f07b01", "paneIndex": 0 },
    "Volume": { "plot": true, "type": "histogram", "upColor": "#089981", "downColor": "#f23645", "paneIndex": 0 },
    "Equity": { "plot": true, "type": "area", "color": "#7c83fd", "paneIndex": 1 }
  }
}

Series Types

TypeRequired FieldsStyle KeysNotes
candlestick4 entries: component: open|high|low|closebuilt-inAlways pane 0
linenumeric columncolorpaneIndex
areanumeric columncolorpaneIndex
histogramnumeric columnupColor, downColorpaneIndex
markersnumeric columncolor, shape, positionpaneIndex

Working Example

A complete SMA-crossover strategy script with auto-upload.

Install dependencies
pip install yfinance pandas numpy requests
Show full strategy.py script
#!/usr/bin/env python3
import os, json, requests
import yfinance as yf
import pandas as pd
import numpy as np

TICKER       = os.getenv("TICKER", "ETH-USD")
INTERVAL     = os.getenv("YF_INTERVAL", "1d")
PERIOD       = os.getenv("YF_PERIOD", "2y")
INITIAL_CASH = float(os.getenv("INITIAL_CASH", "1000000"))
PAYLOAD_OUT  = os.getenv("PAYLOAD_OUT", "quantmaxi_payload.json")

def build_payload() -> dict:
    data = yf.download(TICKER, interval=INTERVAL, period=PERIOD,
                       auto_adjust=False, progress=False)
    if data is None or data.empty:
        raise RuntimeError(f"No data for {TICKER}")
    if isinstance(data.columns, pd.MultiIndex):
        data.columns = data.columns.get_level_values(0)

    data["SMA20"] = data["Close"].rolling(20).mean()
    data["SMA50"] = data["Close"].rolling(50).mean()

    data["Signal"] = 0
    data.loc[(data["SMA20"] > data["SMA50"]) &
             (data["SMA20"].shift(1) <= data["SMA50"].shift(1)), "Signal"] = 1
    data.loc[(data["SMA20"] < data["SMA50"]) &
             (data["SMA20"].shift(1) >= data["SMA50"].shift(1)), "Signal"] = -1

    data["Position"] = data["Signal"].shift(1).fillna(0)
    data["Units"] = (data["Position"].diff().fillna(0) *
                     INITIAL_CASH / data["Close"]).cumsum()
    data["Holdings"] = data["Units"] * data["Close"]
    data["Cash"] = INITIAL_CASH - (data["Units"].diff().fillna(0) *
                                    data["Close"]).cumsum()
    data["Equity"] = data["Holdings"] + data["Cash"]

    data.index = pd.to_datetime(data.index, utc=True)
    data["time"] = (data.index.view("int64") // 10**9).astype("int64")

    cols = ["time","Open","High","Low","Close","Volume",
            "SMA20","SMA50","Signal","Holdings","Cash","Equity"]
    data = data[cols]
    data.replace([np.inf, -np.inf], np.nan, inplace=True)
    data.fillna(0, inplace=True)
    data.sort_values("time", inplace=True)

    return {
        "ticker": TICKER, "interval": INTERVAL,
        "columns": cols,
        "data": data.to_dict(orient="records"),
        "metadata": {
            "ticker": TICKER, "interval": INTERVAL,
            "Open":     {"plot": True, "type": "candlestick", "component": "open"},
            "High":     {"plot": True, "type": "candlestick", "component": "high"},
            "Low":      {"plot": True, "type": "candlestick", "component": "low"},
            "Close":    {"plot": True, "type": "candlestick", "component": "close"},
            "SMA20":    {"plot": True, "type": "line", "color": "#f07b01", "paneIndex": 0},
            "SMA50":    {"plot": True, "type": "line", "color": "#2962ff", "paneIndex": 0},
            "Volume":   {"plot": True, "type": "histogram",
                         "upColor": "#089981", "downColor": "#f23645", "paneIndex": 0},
            "Equity":   {"plot": True, "type": "area", "color": "#7c83fd", "paneIndex": 1},
            "Holdings": {"plot": True, "type": "line", "color": "#22c55e", "paneIndex": 2},
        },
    }

def upload(payload: dict) -> None:
    token = os.getenv("API_TOKEN", "").strip()
    chart_name = os.getenv("CHART_NAME", "default").strip() or "default"
    if not token:
        print("No API_TOKEN -> upload skipped (JSON saved locally).")
        return
    resp = requests.post(
        "https://www.quantmaxi.com/api/ingest",
        headers={"Authorization": f"Bearer {token}",
                 "Content-Type": "application/json"},
        json={"chartName": chart_name, "payload": payload},
        timeout=60)
    print(f"Upload: {resp.status_code} {resp.text}")

if __name__ == "__main__":
    payload = build_payload()
    with open(PAYLOAD_OUT, "w") as f:
        json.dump(payload, f, indent=2)
    print(f"Saved {PAYLOAD_OUT}")
    upload(payload)
Env VarDefaultDescription
API_TOKEN—Required for upload. Skips upload if missing.
CHART_NAMEdefaultName of the chart to create/update.
TICKERETH-USDAsset symbol for yfinance.
Run
API_TOKEN=<YOUR_TOKEN> CHART_NAME=default python3 strategy.py

API Endpoints

EndpointAuthBody Shape
/api/ingestBearer tokenWrapped: {"chartName":"...","payload":{...}}
/api/charts/by-id/{id}/ingestBearer tokenRaw payload
/api/charts/{name}/ingestBrowser sessionRaw payload
Show curl examples
# /api/ingest  (token auth, wrapped body)
curl -X POST "https://www.quantmaxi.com/api/ingest" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chartName":"default","payload":{...}}'

# /api/charts/by-id/{id}/ingest  (token auth, raw payload body)
curl -X POST "https://www.quantmaxi.com/api/charts/by-id/<ID>/ingest" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{...payload...}'

# /api/charts/{name}/ingest  (browser session only, raw payload body)
curl -X POST "https://www.quantmaxi.com/api/charts/default/ingest" \
  -H "Content-Type: application/json" \
  -d '{...payload...}'
Common mistake: sending raw payload to /api/ingest (needs wrapper) or wrapped payload to /api/charts/by-id/ (needs raw). Never share real tokens in docs or screenshots.

Troubleshooting

ErrorCauseFix
missing tokenNo auth header on /api/ingestAdd Authorization: Bearer <TOKEN>
invalid payloadWrong payload shapeUse full envelope with payload.data[]
Invalid JSON bodyBody is not valid JSONValidate JSON before sending
invalid tokenToken hash/lookup failedRegenerate token and retry
UnauthorizedMissing auth contextSign in or use bearer token
ForbiddenChart belongs to another userUse your own chart ID
Chart not foundInvalid chart IDCopy chart ID from dashboard
Connection refusedNetwork / domain issueCheck URL and internet access
time is millisecondsTimestamps too largeDivide by 1000 for seconds
Non-numeric plotted fieldString/NaN in data columnEnsure all plotted values are numbers
Incomplete candlestickMissing OHLC metadata entriesInclude all 4 component entries