Strategy Code Reference
Every strategy generated by QuantifyMe is a self-contained Python file. This guide explains how to read it, what each section does, and how to make common edits — especially if you're coming from PineScript.
/llms.txt is a plain-text version of this documentation written specifically for AI assistants (ChatGPT, Claude, Gemini).
Paste the URL into any AI chat and it can instantly help you write or modify your strategy code.
How it works
Each strategy file has 7 numbered sections. You only need to edit Sections 2, 4, 5, and 6 for most changes. Sections 1, 3, and 7 are the engine — leave them alone unless you know what you're doing.
| Section | What it does | Edit it? |
|---|---|---|
| 1 ModelWrapper | Wraps the ML model, handles feature importances | No |
| 2 Feature Engineering | Computes your indicators (SMA, RSI, BB, etc.) | Yes — add indicators here |
| 3 ML Model | Builds and trains the classifier | Rarely |
| 4 Signal / Entry Logic | Converts model confidence into buy/sell/flat | Yes — tune threshold, add filters |
| 5 Optimisation | Hyperparameter config dict | Yes — change model settings |
| 6 Risk Management | Position sizing, stop loss, take profit | Yes — add your risk rules |
| 7 Backtest Engine | Loads data, runs the full pipeline, returns results | No |
PineScript vs Python — side by side
If you know PineScript, here's how the concepts map across.
| Concept | PineScript | Python (this file) |
|---|---|---|
| Go long | strategy.entry("L", strategy.long) | signal = 1 |
| Go short | strategy.entry("S", strategy.short) | signal = -1 |
| Close / flat | strategy.close_all() | signal = 0 |
| Current close | close | close or ohlc['close'] |
| Previous close | close[1] | close.shift(1) |
| Current open | open | ohlc['open'] |
| Bar's high / low | high / low | ohlc['high'] / ohlc['low'] |
| SMA(close, 20) | ta.sma(close, 20) | close.rolling(20).mean() |
| EMA(close, 20) | ta.ema(close, 20) | close.ewm(span=20).mean() |
| RSI(close, 14) | ta.rsi(close, 14) | see Add an indicator |
| Crossover(fast, slow) | ta.crossover(fast, slow) | (fast > slow) & (fast.shift(1) <= slow.shift(1)) |
| Bar index | bar_index | df.index (datetime) |
| Day of week | dayofweek | df.index.dayofweek (0=Mon, 4=Fri) |
| Hour of day | hour | df.index.hour |
| Minute | minute | df.index.minute |
2 Feature Engineering
This is where indicators live. Anything you add here becomes an input to the ML model. The function receives df, close, open_, high, low and must return df with new columns added.
def feature_engineering(df, close, open_, high, low): df = df.copy() # SMA crossover feature df['sma_20'] = close.rolling(20).mean() df['sma_50'] = close.rolling(50).mean() df['sma_cross'] = (df['sma_20'] - df['sma_50']) / close # normalised gap # Bollinger Band position (0 = at lower band, 1 = at upper band) bb_mid = close.rolling(20).mean() bb_std = close.rolling(20).std() df['bb_position'] = (close - (bb_mid - 2*bb_std)) / (4*bb_std) # Candle body: positive = bullish bar, negative = bearish df['candle_body'] = (close - open_) / (high - low + 1e-9) return df
open, high, low, close, target.3 ML Model
build_model() trains the classifier and wraps it in a ModelWrapper for consistent inference. It reads hyperparameters from optimization_config().
def build_model(X_train, y_train): cfg = optimization_config() clf = XGBClassifier( n_estimators=cfg.get("n_estimators", 200), max_depth=cfg.get("max_depth", 5), learning_rate=cfg.get("learning_rate", 0.05), use_label_encoder=False, eval_metric="mlogloss", ) clf.fit(X_train, y_train) return ModelWrapper(clf, original_classes=le.classes_, n_features=X_train.shape[1])
ModelWrapper. This gives a uniform interface (predict_proba, predict, feature_importances_) that works for sklearn, XGBoost, and Keras.4 Signal / Entry Logic
The model outputs a probability for each direction: p_pos = probability of price going up, p_neg = probability of price going down. The threshold controls how confident the model needs to be before entering.
def generate_signals(model, X, thresh): proba = model.predict_proba(X) p_pos = ... # P(long) — ranges 0.0 to 1.0 p_neg = ... # P(short) — ranges 0.0 to 1.0 signal = pd.Series(0.0, index=X.index) # default: flat signal[p_pos > thresh] = 1.0 # long if confident enough signal[p_neg > thresh] = -1.0 # short if confident enough return signal, p_pos, p_neg
Default thresh = 0.55. Raise it (e.g. 0.65) for fewer but higher-conviction trades. Lower it for more trades.
5 Optimisation Target
optimization_config() returns a dict of hyperparameters that build_model() reads. Change values here to tune the model without touching the model code.
def optimization_config(): return { "objective": "Maximize Sharpe ratio", "notes": "Conservative depth, balanced class weights", "n_estimators": 200, "max_depth": 5, "learning_rate": 0.05, "class_weight": "balanced", }
objective and notes keys are required but informational. The model reads the numeric keys (n_estimators, max_depth, etc.) directly.6 Risk Management
After the signal is generated, apply_risk() can scale, filter, or override it. The default just applies position sizing.
def apply_risk(signal, close, pos_size=1.0): return signal * pos_size # scale position (0.5 = half size)
You can add stop loss and take profit logic here — see the stop loss example below.
7 Backtest Engine (read-only reference)
This is what runs when you click Run Backtest. Key things to know:
- Data is EURUSD ticks resampled to 15-minute OHLCV bars
- Signal executes on the next bar's open —
signal.shift(1)— no look-ahead bias - The target the model learns is:
sign(close[+4bars] - close[now])— direction 1 hour ahead - 70% of data = training, 30% = test (what you see in results)
- Costs: commission charged on every trade change, overnight swap at 00:00
shift(-4) in the target line of Section 7.Common edit: Buy on market open
To only take long entries in the first 30 minutes of a session (e.g. London open at 08:00 UTC), add a session filter to apply_risk() in Section 6:
def apply_risk(signal, close, pos_size=1.0): signal = signal.copy() # Only take NEW long entries in first 30 min of London open (08:00–08:30 UTC) london_open = (signal.index.hour == 8) & (signal.index.minute < 30) # Mask out longs that start outside that window new_long = (signal == 1) & (signal.shift(1).fillna(0) != 1) # entry bar only signal[new_long & ~london_open] = 0 return signal * pos_size
For NYSE open (14:30 UTC), change the hour to 14 and minute to < 50 for the first 20 minutes.
# NYSE open — first 20 minutes (14:30–14:50 UTC) nyse_open = (signal.index.hour == 14) & (signal.index.minute >= 30) & (signal.index.minute < 50)
Common edit: Session filter (trade only during specific hours)
Completely block trading outside certain hours:
def apply_risk(signal, close, pos_size=1.0): signal = signal.copy() # Only trade during London + NY overlap: 13:00–17:00 UTC in_session = (signal.index.hour >= 13) & (signal.index.hour < 17) signal[~in_session] = 0 # flat outside session return signal * pos_size
Common edit: Stop loss & Take profit
Add pip-based SL/TP inside apply_risk(). For EURUSD, 1 pip = 0.0001.
def apply_risk(signal, close, pos_size=1.0): signal = signal.copy() SL_PIPS = 0.0020 # 20 pip stop loss TP_PIPS = 0.0040 # 40 pip take profit (2:1 RR) position = 0 entry_price = 0.0 for i in range(len(signal)): price = close.iloc[i] sig = signal.iloc[i] if position == 0 and sig != 0: position = sig entry_price = price elif position != 0: move = (price - entry_price) * position if move <= -SL_PIPS or move >= TP_PIPS: signal.iloc[i] = 0 # exit position = 0 return signal * pos_size
Common edit: Add a custom indicator
RSI
# In feature_engineering(), add: delta = close.diff() gain = delta.clip(lower=0).ewm(com=13, adjust=False).mean() loss = (-delta.clip(upper=0)).ewm(com=13, adjust=False).mean() df['rsi_14'] = 100 - 100 / (1 + gain / loss.replace(0, float('nan')))
MACD
ema12 = close.ewm(span=12).mean() ema26 = close.ewm(span=26).mean() df['macd'] = ema12 - ema26 df['macd_signal'] = df['macd'].ewm(span=9).mean() df['macd_hist'] = df['macd'] - df['macd_signal']
ATR (Average True Range)
tr = pd.concat([
high - low,
(high - close.shift(1)).abs(),
(low - close.shift(1)).abs()
], axis=1).max(axis=1)
df['atr_14'] = tr.rolling(14).mean()
Shooting star candle pattern
# Upper wick > 2× body, small lower wick, bearish close body = (close - open_).abs() upper_wick = high - pd.concat([close, open_], axis=1).max(axis=1) lower_wick = pd.concat([close, open_], axis=1).min(axis=1) - low df['shooting_star'] = ( (upper_wick > 2 * body) & (lower_wick < body * 0.3) & (close < open_) # bearish bar ).astype(float) # 1.0 if pattern present, 0.0 if not
Common edit: Change the confidence threshold
In Section 7, the backtest calls generate_signals(model, X_test, thresh=0.55). Change that value:
# More selective — fewer trades, higher conviction only signal_test, p_pos, p_neg = generate_signals(model, X_test, thresh=0.65) # More active — more trades, lower bar signal_test, p_pos, p_neg = generate_signals(model, X_test, thresh=0.50)
API Reference
All endpoints live under /api/v1/. Authenticated endpoints require an X-API-Key header.
# Base URL https://api.quantifyme.ai/api/v1/ # Auth header X-API-Key: qm_xxxxxxxxxxxxxxxx
POST /api/v1/generate
Generate a full Python strategy from natural language. Each of the 7 sections accepts free-text descriptions. Optionally pass structured indicators for exact parameter control.
import requests resp = requests.post("https://api.quantifyme.ai/api/v1/generate", json={ "features": "MACD histogram slope, RSI divergence", "signals": "Long when MACD crosses above zero with RSI confirmation", "model": "XGBoost", "optimization": "Maximize Sharpe ratio", "risk": "Stop loss 0.3%, scale by ATR", "risk_function": "Skip trades on Fridays after 18:00 UTC", "indicators": { "macd_enabled": True, "macd_fast": 12, "macd_slow": 26, "macd_signal": 9, "rsi_enabled": True, "rsi_period": 14, "atr_enabled": True, "atr_period": 14, }, "pos_rules": {"max_pos": 1, "direction": "both", "on_opposite": "reverse", "cooldown": 0}, "start_date": "2025-01-01", "end_date": "2026-04-01", "train_split": 0.7, "claude_model": "sonnet", }, headers={"X-API-Key": "qm_xxx"}) code = resp.json()["code"]
GET /api/v1/indicator-schema for all available indicators.pos_rules options
| Field | Options | Default |
|---|---|---|
| max_pos | 1, 2, 3 | 1 |
| on_opposite | reverse, close_only, ignore | reverse |
| direction | both, long, short | both |
| cooldown | 0 — 100 (bars) | 0 |
POST /api/v1/generate/from-template
Generate strategy code from a curated template. Override any field — text fields replace, dict fields (indicators, pos_rules) merge.
resp = requests.post("https://api.quantifyme.ai/api/v1/generate/from-template", json={ "template": "scalper", "start_date": "2025-01-01", "end_date": "2026-04-01", "pos_rules": {"direction": "long"}, # override: long-only "indicators": {"rsi_period": 21}, # override: RSI 21 instead of 14 "claude_model": "sonnet", }, headers={"X-API-Key": "qm_xxx"})
Available templates
| Key | Name | Description |
|---|---|---|
| macd_momentum | MACD Momentum | Trend-following with MACD crossovers + RSI confirmation |
| mean_reversion | Mean Reversion | Fade extremes with Bollinger Bands + RSI |
| trend_following | Trend Following | EMA crossovers + ATR position sizing |
| scalper | Scalper | High-frequency with Stochastic + BB |
| conservative_long | Conservative Long-Only | SMA trend + strict drawdown control |
POST /api/v1/indicators
Compute built-in indicators on OHLC data without training a model. Returns indicator values as JSON.
resp = requests.post("https://api.quantifyme.ai/api/v1/indicators", json={ "indicators": {"rsi_enabled": True, "rsi_period": 14, "macd_enabled": True}, "symbol": "EURUSD", "timeframe": "15min", "start": "2026-04-01", "limit": 100, "format": "columns", # or "rows" }, headers={"X-API-Key": "qm_xxx"}) data = resp.json() # data["ind_rsi14"] → [45.2, 48.1, 52.3, ...] # data["timestamps"] → ["2026-04-01 00:00:00", ...]
Available indicators
| Indicator | Params | Features generated |
|---|---|---|
| SMA | sma_periods = "20,50,200" | sma_{P}, dm_sma_{P} |
| EMA | ema_periods = "9,21,50" | ema_{P}, dm_ema_{P} |
| Bollinger Bands | bb_period=20, bb_std=2.0 | bb_width, bb_pct |
| RSI | rsi_period = 14 | rsi_{P} |
| MACD | macd_fast=12, slow=26, signal=9 | macd_line, macd_hist |
| Stochastic | stoch_k=14, stoch_d=3 | stoch_k, stoch_d |
| ATR | atr_period = 14 | atr_{P}, natr_{P} |
POST /api/v1/indicators/custom
Convert Pine Script or natural language to a computed indicator via Claude. Returns overlays, panels, signals, and ML features.
# Natural language resp = requests.post("https://api.quantifyme.ai/api/v1/indicators/custom", json={ "source": "Supertrend indicator with ATR period 10 and multiplier 3", "timeframe": "15min", "fe_mode": "basic", # "full" | "basic" | "none" }, headers={"X-API-Key": "qm_xxx"}) # Pine Script resp = requests.post("https://api.quantifyme.ai/api/v1/indicators/custom", json={ "source": "//@version=5 indicator('My RSI') plot(ta.rsi(close, 14))", "provider": "anthropic", # "anthropic" | "groq" | "ollama" }, headers={"X-API-Key": "qm_xxx"}) result = resp.json() # result["name"] → "Supertrend" # result["overlays"] → [{id, color, data}] # result["panels"] → [{id, title, lines, h_lines}] # result["signals"] → [{time, type: "buy"|"sell"}] # result["features"] → {col_name: [{time, value}]}
Discovery Endpoints (no auth)
These endpoints are public — no API key required. Use them to discover available options before calling authenticated endpoints.
| Endpoint | Returns |
|---|---|
GET /api/v1/indicator-schema | All 8 indicators with param names, types, defaults, and generated feature columns |
GET /api/v1/presets | Chip presets (same as website UI), pos_rules schema, 5 strategy templates |
GET /api/v1/docs | Machine-readable JSON docs (all 16 endpoints) |
# Discover available indicators schema = requests.get("https://api.quantifyme.ai/api/v1/indicator-schema").json() # Discover templates + presets presets = requests.get("https://api.quantifyme.ai/api/v1/presets").json() # presets["templates"]["macd_momentum"]["config"] → ready for /generate
Full Example — Copy, Paste, Run
Complete end-to-end script: generate strategy → train model → poll until done → check results. Replace API_KEY with yours.
# QuantifyMe API - Full Example # 1. Generate strategy code from natural language # 2. Submit for training # 3. Poll until complete # 4. View results import requests, time # ────────────────────────────────────────────────────────────────── # CONFIG — paste your API key here # ────────────────────────────────────────────────────────────────── BASE = "https://api.quantifyme.ai" API_KEY = "qm_PASTE_YOUR_KEY_HERE" # ← get from API Endpoint page HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"} # ────────────────────────────────────────────────────────────────── # STEP 1: Generate strategy code # ────────────────────────────────────────────────────────────────── print("Generating strategy...") resp = requests.post(f"{BASE}/api/v1/generate", json={ # Natural language — describe your strategy "features": "MACD (12,26,9), RSI 14, ATR 14, Volume Z-score", "signals": "Buy when MACD crosses above zero and RSI < 60", "model": "XGBoost", "optimization": "Maximize Sharpe ratio, keep max drawdown under 15%", "risk": "Stop loss 0.3%, fixed 1% risk per trade", # Structured indicators — exact params (optional, overrides NL) "indicators": { "macd_enabled": True, "macd_fast": 12, "macd_slow": 26, "macd_signal": 9, "rsi_enabled": True, "rsi_period": 14, "atr_enabled": True, "atr_period": 14, }, # Position rules "pos_rules": { "max_pos": 1, "direction": "both", "on_opposite": "reverse", "cooldown": 0, }, # Date range + model "start_date": "2025-06-01", "end_date": "2026-04-01", "train_split": 0.7, "claude_model": "sonnet", }, headers=HEADERS) code = resp.json()["code"] print(f"Strategy generated ({len(code)} chars)") # ────────────────────────────────────────────────────────────────── # STEP 2: Submit for training # ────────────────────────────────────────────────────────────────── print("Submitting for training...") resp = requests.post(f"{BASE}/api/v1/train", json={ "code": code, "model_name": "API Test Model", "start_date": "2025-06-01", "end_date": "2026-04-01", "train_split": 0.7, "timeframe": "15min", }, headers=HEADERS) job = resp.json() job_id = job["job_id"] print(f"Job {job_id} queued") # ────────────────────────────────────────────────────────────────── # STEP 3: Poll until done # ────────────────────────────────────────────────────────────────── while True: resp = requests.get(f"{BASE}/api/v1/train/{job_id}", headers=HEADERS) status = resp.json() print(f" Status: {status['status']}") if status["status"] in ("done", "failed"): break time.sleep(3) # ────────────────────────────────────────────────────────────────── # STEP 4: View results # ────────────────────────────────────────────────────────────────── if status["status"] == "done": m = status.get("metrics", {}) print("Training complete!") print(f" Return: {m.get('total_ret', 0):.2%}") print(f" Sharpe: {m.get('sharpe_strat', 0):.2f}") print(f" Drawdown: {m.get('mdd', 0):.2%}") print(f" Trades: {m.get('n_trades', 0)}") else: print(f"Training failed: {status.get('error', 'unknown')}") # ────────────────────────────────────────────────────────────────── # BONUS: Use a template instead (no NL needed) # ────────────────────────────────────────────────────────────────── # resp = requests.post(f"{BASE}/api/v1/generate/from-template", json={ # "template": "scalper", # "start_date": "2025-06-01", # "end_date": "2026-04-01", # "claude_model": "sonnet", # }, headers=HEADERS) # ────────────────────────────────────────────────────────────────── # BONUS: Compute indicators without training # ────────────────────────────────────────────────────────────────── # resp = requests.post(f"{BASE}/api/v1/indicators", json={ # "indicators": {"rsi_enabled": True, "macd_enabled": True}, # "timeframe": "15min", "limit": 100, # }, headers=HEADERS) # print(resp.json()["ind_rsi14"][:5]) # first 5 RSI values
while True loop polls every 3 seconds until done.More Examples
Browse all API examples by section. Drag examples into the Strategy Composer to build and test a strategy.
Pick examples from each section, drag them into the composer, and hit "Try It" to generate & backtest a strategy.
All snippets below need an X-API-Key header.
Click below to copy yours.
Generate a strategy, train it, and see backtest results. Updates live from the Strategy Composer.
Deploy your trained model for live signal delivery via webhook or Telegram.
This proven strategy runs on EURUSD 15-min data (Jan-Apr 2026). Click "Run Now" to see the same results live.
import requests, time
BASE = "https://api.quantifyme.ai"
API_KEY = "qm_YOUR_KEY" # get from /api-docs
H = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# Strategy: EMA Trend + Session Filter by @malco
gen = requests.post(f"{BASE}/api/v1/generate", json={
"features": "RSI 14, EMA 50/200, ATR 14, Volume Z-score",
"signals": "Buy when EMA 50 crosses above EMA 200 and RSI between 40-60",
"model": "Random Forest",
"optimization": "Maximize Sharpe ratio, keep max drawdown under 8%",
"risk": "ATR-based stop loss (1.5x ATR), take profit 3x ATR",
"risk_function": "Only trade during London-NY overlap (13:00-17:00 UTC), skip Fridays after 16:00",
"pos_rules": {"max_pos": 1, "direction": "both", "on_opposite": "close_only", "cooldown": 3},
"start_date": "2026-01-01", "end_date": "2026-04-01",
"train_split": 0.7, "claude_model": "sonnet"
}, headers=H, timeout=120)
code = gen.json()["code"]
train = requests.post(f"{BASE}/api/v1/train", json={
"code": code, "model_name": "EMA Trend Session",
"start_date": "2026-01-01", "end_date": "2026-04-01",
"train_split": 0.7, "timeframe": "15min"
}, headers=H)
job_id = train.json()["job_id"]
while True:
s = requests.get(f"{BASE}/api/v1/train/{job_id}", headers=H).json()
print(s["status"])
if s["status"] == "done":
m = s["metrics"]
print(f"Return: {m['total_ret']:.2%}, Sharpe: {m['sharpe_strat']:.2f}")
break
elif s["status"] == "failed":
print(s.get("error", ""))
break
time.sleep(3)