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.

AI-readable reference available
/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.
We follow the llms.txt standard — a plain-text spec file at the root of the domain that AI assistants can read. So when users ask ChatGPT "help me modify my QuantifyMe strategy", they paste the URL and the AI already understands our signal convention, section structure, and all common patterns. It's like having documentation that's written for both humans and machines.
View llms.txt quantifyme.ai/llms.txt
No PineScript here. The generated code is Python. The logic is the same — entries, exits, signals, indicators — but the syntax is different. This page maps the two side by side.

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.

SectionWhat it doesEdit it?
1 ModelWrapperWraps the ML model, handles feature importancesNo
2 Feature EngineeringComputes your indicators (SMA, RSI, BB, etc.)Yes — add indicators here
3 ML ModelBuilds and trains the classifierRarely
4 Signal / Entry LogicConverts model confidence into buy/sell/flatYes — tune threshold, add filters
5 OptimisationHyperparameter config dictYes — change model settings
6 Risk ManagementPosition sizing, stop loss, take profitYes — add your risk rules
7 Backtest EngineLoads data, runs the full pipeline, returns resultsNo

PineScript vs Python — side by side

If you know PineScript, here's how the concepts map across.

ConceptPineScriptPython (this file)
Go longstrategy.entry("L", strategy.long)signal = 1
Go shortstrategy.entry("S", strategy.short)signal = -1
Close / flatstrategy.close_all()signal = 0
Current closecloseclose or ohlc['close']
Previous closeclose[1]close.shift(1)
Current openopenohlc['open']
Bar's high / lowhigh / lowohlc['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 indexbar_indexdf.index (datetime)
Day of weekdayofweekdf.index.dayofweek (0=Mon, 4=Fri)
Hour of dayhourdf.index.hour
Minuteminutedf.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
Rule: every column you add becomes a feature. Name it clearly. The model automatically picks up all columns that aren't 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])
Always wrap the raw model in 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",
    }
Tip: the 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:

The target is 4 bars (1 hour) ahead. The model is predicting direction over the next hour, not the next candle. If you want a different horizon, change 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)
Tip: Watch your Sharpe ratio and profit factor when changing threshold. Higher confidence = fewer trades but each one should be more reliable. Lower = more trades but more noise.

API Reference

All endpoints live under /api/v1/. Authenticated endpoints require an X-API-Key header.

Logged in: copies your personal key. Otherwise: creates a trial key.
# Base URL
https://api.quantifyme.ai/api/v1/

# Auth header
X-API-Key: qm_xxxxxxxxxxxxxxxx
Quick start: Scroll to Full Example at the bottom for a complete copy-pastable script that covers generate → train → predict.

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"]
indicators is optional. When provided, Claude receives exact parameters (e.g. "RSI with period=14") instead of parsing free text. See GET /api/v1/indicator-schema for all available indicators.

pos_rules options

FieldOptionsDefault
max_pos1, 2, 31
on_oppositereverse, close_only, ignorereverse
directionboth, long, shortboth
cooldown0100 (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

KeyNameDescription
macd_momentumMACD MomentumTrend-following with MACD crossovers + RSI confirmation
mean_reversionMean ReversionFade extremes with Bollinger Bands + RSI
trend_followingTrend FollowingEMA crossovers + ATR position sizing
scalperScalperHigh-frequency with Stochastic + BB
conservative_longConservative Long-OnlySMA 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

IndicatorParamsFeatures generated
SMAsma_periods = "20,50,200"sma_{P}, dm_sma_{P}
EMAema_periods = "9,21,50"ema_{P}, dm_ema_{P}
Bollinger Bandsbb_period=20, bb_std=2.0bb_width, bb_pct
RSIrsi_period = 14rsi_{P}
MACDmacd_fast=12, slow=26, signal=9macd_line, macd_hist
Stochasticstoch_k=14, stoch_d=3stoch_k, stoch_d
ATRatr_period = 14atr_{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.

EndpointReturns
GET /api/v1/indicator-schemaAll 8 indicators with param names, types, defaults, and generated feature columns
GET /api/v1/presetsChip presets (same as website UI), pos_rules schema, 5 strategy templates
GET /api/v1/docsMachine-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
Tip: This script takes ~10-30 seconds total. Step 1 (generate) takes ~5s, Step 3 (training) takes ~5-15s depending on data size. The 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.

Demo
Try it out

Pick examples from each section, drag them into the composer, and hit "Try It" to generate & backtest a strategy.

Browse
Backtesting
Strategy Builder
API Workflows

    
Strategy Composer
Drag examples into slots or click "+ Add"
1. Features x
empty
2. Signals x
empty
3. Model x
empty
4. Optimisation x
empty
5. Risk x
empty
6. Position Rules x
empty
7. Filter x
empty
demo key — no account needed

  
0. Get your API key
checking...

All snippets below need an X-API-Key header. Click below to copy yours.

1. Build & Test

Generate a strategy, train it, and see backtest results. Updates live from the Strategy Composer.





Paste Strategy Code
(paste generated code here to skip generation)





2. Deploy & Automate

Deploy your trained model for live signal delivery via webhook or Telegram.





3. Community Script
M
EMA Trend + Session Filter
Shared by @malco
+1.01%
Return
39.83
Sharpe
-0.34%
Max DD
24
Trades
EMA 50/200 crossover with RSI filter (40-60 zone). ATR-based stop loss & take profit. London-NY overlap only, no Friday trades. 3-bar cooldown.

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)
4. Quick Deploy
From idea to live signals in 60 seconds
Describe your strategy in one line. We'll generate the code, train an ML model on the past 28 days, and deploy it live.
Try: