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/Infinityfirst - 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
| Type | Required Fields | Style Keys | Notes |
|---|---|---|---|
candlestick | 4 entries: component: open|high|low|close | built-in | Always pane 0 |
line | numeric column | color | paneIndex |
area | numeric column | color | paneIndex |
histogram | numeric column | upColor, downColor | paneIndex |
markers | numeric column | color, shape, position | paneIndex |
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 Var | Default | Description |
|---|---|---|
API_TOKEN | — | Required for upload. Skips upload if missing. |
CHART_NAME | default | Name of the chart to create/update. |
TICKER | ETH-USD | Asset symbol for yfinance. |
Run
API_TOKEN=<YOUR_TOKEN> CHART_NAME=default python3 strategy.py
API Endpoints
| Endpoint | Auth | Body Shape |
|---|---|---|
/api/ingest | Bearer token | Wrapped: {"chartName":"...","payload":{...}} |
/api/charts/by-id/{id}/ingest | Bearer token | Raw payload |
/api/charts/{name}/ingest | Browser session | Raw 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
| Error | Cause | Fix |
|---|---|---|
missing token | No auth header on /api/ingest | Add Authorization: Bearer <TOKEN> |
invalid payload | Wrong payload shape | Use full envelope with payload.data[] |
Invalid JSON body | Body is not valid JSON | Validate JSON before sending |
invalid token | Token hash/lookup failed | Regenerate token and retry |
Unauthorized | Missing auth context | Sign in or use bearer token |
Forbidden | Chart belongs to another user | Use your own chart ID |
Chart not found | Invalid chart ID | Copy chart ID from dashboard |
Connection refused | Network / domain issue | Check URL and internet access |
time is milliseconds | Timestamps too large | Divide by 1000 for seconds |
Non-numeric plotted field | String/NaN in data column | Ensure all plotted values are numbers |
Incomplete candlestick | Missing OHLC metadata entries | Include all 4 component entries |