Strategy Builder
Configure a new strategy top-to-bottom · save it or fire it straight to the broker.New Strategy
(no watch instruments yet — conditions can only reference what you add here)
(both times blank — no overnight hold; the strategy enters and exits the same day)
(overnight protection off — short legs carry the overnight gap unhedged)
| Leg ID | Idle | Hedge | Side | Fut/Opt | Type | Strike Selection | Strike Value | Expiry Selection | Expiry | Qty (Lots) |
|---|
(no legs yet — add one row per order)
(rules disabled — the position is never auto-adjusted)
Contract Rollover
Strategy Risk Settings
(no combined wait — legs place as soon as the entry fires)
(set Combined SL above — preview rebuilds as you type)
(set Combined Target above — preview rebuilds as you type)
Combined trailing — once basket MTM reaches ₹Trigger, for every further ₹Step of favourable move the trail floor rises by ₹Lock. The floor sits below the peak; you choose how much to give back, not where the floor lands. For an absolute floor you name yourself, use the Profit Lock & Trailing tab.
(enable trailing and fill Trigger / Step / Lock to arm it)
Locks an absolute floor you name, then ratchets it up as profit grows — unlike Combined Trailing, where the floor is always peak minus your give-back. Works on the basket's combined ₹ P&L and squares off every open leg when P&L falls back to the floor.
(set "when P&L reaches ₹" above to arm the profit lock)
Order sequencing & failure policy
buy_first (the default) puts the hedges on before the shorts they protect, avoiding the SPAN spike — the mirror of the exit, which always closes shorts before releasing hedges. parallel fires everything at once and accepts the margin spike.Futures/Synthetic use the forward (premium/discount to spot) — e.g. spot 20000 but futures 20080 picks a higher ATM strike. Backtest: spot uses the cash series, futures/synthetic use the archived synthetic-future.cancel_all unwinds the already-placed legs (safest). keep_placed trades a partial basket.retry_failed. After this many attempts, the policy falls back to cancel_all.Risk + Timing
Days + Capital Sizing
Notifications
config.yml → notify.telegram. Empty config = log-only.Order execution
(default execution — parallel legs, market entries)
Show summary
Strategy View
Two views of the same data — by strategy, or grouped by broker account. 🔗 Broker deploys to an account; 📦 Portfolio optionally groups. Expand any row for its positions, orders, trades & logs.Broker Accounts
Connect once, trade everywhere — accounts are shared across Options, Trade & Algo. Enable an account to let live strategies place orders through it; copy-trading mirrors them to your other accounts.Copy Trading
Place once on the leader; the engine mirrors to followers with a size multiplier. Accounts are managed on the Broker Accounts page.⇄ Copy Trading settings
Add Broker Account
Credentials live in the engine's SQLite store and are masked in every read response. Kite Connect requires API Key + Secret from kite.trade; Access Token expires daily and must be refreshed.
Portfolios
Deploy saved Strategies on a Broker with a Multiplier. Portfolio-level risk caps stop the whole container when the daily loss cap is hit.Signals
Create a signal → it gets its own webhook URL. Send it from TradingView, Python, Amibroker, NinjaTrader or any program (or fire it manually). On hit, the linked strategies run / square-off on their broker accounts. We only receive signals — we don't generate them.Signal Configuration
Strategy Selection
| Strategy | Signal Types | Remove |
|---|---|---|
| add a strategy to configure its signal types | ||
Link Brokers
| Strategy | Broker Accounts (× multiplier) |
|---|---|
| add a strategy first | |
Signal Dashboard
Cumulative Signal Logs
New Portfolio
A Portfolio bundles one or more saved Strategies, each pinned to a Broker with a size Multiplier. Risk caps below stop the whole container when breached — the engine will not start any further Strategy in this Portfolio for the day.
Assign Broker
Assign one saved Strategy to one Broker Account with a size Multiplier. The Multiplier scales the leg lots and any ₹-based SL / Target (PNL-mode rungs); points / % / underlying-move / delta stay unchanged. Each broker account can be used once per strategy — no duplicate deployments.
Assign Broker
Assign this strategy to an account. Pick a 🧪 Demo account to forward-test (free, simulated fills at live prices — shows under Forward Test) or a 🟢 Real broker to trade live (shows under Algo). The Multiplier scales leg lots and ₹-based SL/Target. Then click ▶ Deploy on the row to start it.
Deploy portfolio
Pick an account for EACH strategy — a Demo (paper) account forward-tests with simulated fills at live prices (free); a Real broker account trades live (your 1st deployed strategy is free, each additional one is 100 credits for 30 days or Algo Unlimited). Leave a row on “— skip —” to not launch it.
Include Strategy
Group a saved Strategy into this Portfolio. This is organisation only — the strategy goes live once you Assign Broker. Portfolio risk caps then apply to all its deployed strategies.
Webhook
Fire your strategies from TradingView, Chartink, Python or any HTTP source. We only receive alerts — we never generate them.Build your trigger
Sample bodies — copy & fill
{
"signal": {
"signal_type": "LONG ENTRY", // the one field to always send
"symbol": "TCS", // tradable root
"fut_opt": "FUT", // FUT (front month, auto-rolls) | CASH
"qty": 2, // LOTS — the engine converts
"sl": 30, "tgt": 50 // in the unit your strategy uses
}
}
{
"signal": {
"signal_type": "ENTRY",
"legs": [
{ "underlying":"NIFTY", "expiry":"current",
"option_type":"PE", "strike_offset":0,
"side":"SELL", "qty":1 },
{ "underlying":"NIFTY", "expiry":"current",
"option_type":"CE", "strike_offset":0,
"side":"SELL", "qty":1 }
]
}
}
// strike_offset = ATM ± N strikes (needs strike_selection "relative"
// on the saved strategy). Replaces the saved legs — and their SL/TGT.
{ "signal": { "signal_type": "LONG EXIT" } }
// Closes the whole position. Add a qty to book only part
// of it and leave the rest running:
{ "signal": { "signal_type": "LONG EXIT", "qty": 1 } }
Every parameter you can send
"signal". Only signal_type matters — every other field is optional, and anything you omit keeps the strategy's saved value.| Parameter | Sent on | Values | What it does |
|---|---|---|---|
| The type — the one field to always send | |||
signal_typealias action | always | LONG ENTRY · SHORT ENTRYLONG EXIT · SHORT EXITENTRY · EXITshort codes LE/SE/LX/SX/E/X also work, as do buy, stoploss, tp, square_off | What this alert is. Single-leg strategies use the LONG / SHORT forms; multi-leg use plain ENTRY / EXIT. The Signal must have a row wired for it — if it does not, the alert is refused and logged rather than doing something else. Omit it and every wired row fires, which is how existing integrations keep working. |
| Entry — what to trade, and its risk | |||
symbol | entry | NIFTY · TCS | Underlying / tradable root on every leg. |
fut_optalias instrument | entry | FUT · CASH · OPT | FUT resolves the front-month contract and auto-rolls; CASH is equity/spot. |
expiry | entry | current · next · monthly · YYYY-MM-DD | Contract expiry. |
option_type | entry | CE · PE | Option type on every leg. |
strike_offset | entry | integer | ATM ± N strikes. Needs relative strike selection on the strategy. |
side | entry | BUY · SELL | Direction. Usually unnecessary — LONG ENTRY / SHORT ENTRY already say it. |
qty | entry | integer | In lots — the same unit the strategy is configured in. The engine multiplies by the contract size before the order reaches the broker; the route's multiplier scales it after. Use qty_units to send raw contract units instead. |
qty_units | entry | integer | Raw contract units, skipping the lot conversion — for senders that would rather compute the size themselves. |
sl | entry | number | Stop for this entry, in whatever unit the strategy already uses — percent of entry premium by default, points if the strategy is set that way. Use sl_pct or sl_points to state the unit explicitly. |
tgtalias target | entry | number | Target for this entry, same unit rule as sl. Explicit forms: tgt_pct, tgt_points. |
trailingalias trail | entry | {enabled, trigger_pct, | Trailing stop for this entry, as % of entry premium ("unit":"pts" for points). |
legs | entry | array of leg objects | Multi-leg only. Replaces the saved legs wholesale, their risk settings included. Leg fields: underlying, expiry, option_type, strike_offset, side, qty. |
| Exit — how much to close | |||
qty | exit | integer | Omit to close the whole position. Send a quantity in lots to release just that much of every open leg and leave the rest running — a 4-lot straddle sent 2 comes back as 2 lots CE + 2 lots PE, still a straddle. A quantity at or above what is open closes it anyway. |
qty_pct | exit | 1–100 | The same partial exit, as a percentage of what is still open, instead of an absolute quantity. qty wins if you send both. |
| Update risk — change a position already open (advanced) | |||
sl_inr · target_inr | update | number, rupees | Basket stop / target. 0 removes the limit — and {"sl_inr": 0} is how you move to breakeven. |
trailing | update | {enabled, trigger_inr, | Basket give-back trail: arm at trigger, floor at peak − lock, ratcheting every step. |
profit_lock | update | {profit_lock_reach_inr, | Basket absolute-floor lock. Can close the position — see the note below. |
force | update | true | Apply a profit lock whose floor is already breached. Default refuses and logs floor / peak / MTM. |
leg | update | integer, first leg = 0 | Required with any of the three per-leg fields below. |
sl_price · target_price | update | number, premium | That leg's stop / target as a premium price. |
trail | update | {enabled, trigger_pct, | That leg's own trailing stop. |
0 is a real value that removes a limit. An empty object ("trailing": {}) reads as omitted, so it cannot wipe a live trail.Numbers may arrive as strings — TradingView substitutes its
{{…}} placeholders as text, and that is accepted.Anything not listed here can still be driven through
{{signal.*}} placeholders in the saved params.
One URL — the alert names its type
signal_type, and the Signal's strategy rows decide what it is allowed to be — so a type you never wired is refused and logged, not quietly turned into something else. An alert with no signal_type fires every wired row, exactly as before.| signal_type | Row to wire | What happens |
|---|---|---|
LONG ENTRY | Long Entry (or Entry) | Open a long. A repeat while one is already open is a pyramid add if the Signal allows it — see below. |
SHORT ENTRY | Short Entry (or Entry) | Open a short. |
LONG EXIT | Long Exit (or Exit) | Close the long — fully, or partly if the alert carries qty. |
SHORT EXIT | Short Exit (or Exit) | Close the short, same rule. |
ENTRY / EXIT | Entry / Exit | Multi-leg strategies, which have no single side. |
| Update risk | Update risk | Advanced — change the stop, target, trailing or profit lock of a position that is already open, without opening or closing anything. |
LX, SE: close the long, then open the short.Quantity
- Send lots — the same unit you configured the strategy in.
"qty": 2means two lots; the engine multiplies by that contract's size before the order goes to the broker, so you never have to know it. - On an entry,
qtysets the size of every leg. On an exit it is optional: omit it to close the position, or send lots to release just that much and leave the rest running. - Raw contract units —
qty_units— for senders that would rather compute the size themselves. It skips the lot conversion entirely. - Per-route multiplier. Each strategy row on the Signals page has a multiplier that scales the final quantity — the way to run one alert at 1× on one broker and 3× on another.
Repeat alerts
Placeholders — {{signal.*}}
"sl_pct":"{{signal.sl}}", "target_pct":"{{signal.tgt}}", "strike_offset":"{{signal.offset}}", or the whole "legs":"{{signal.legs}}". Dotted and indexed paths resolve: {{signal.legs[0].strike_offset}}. A value that is exactly one token keeps its JSON type (numbers stay numbers); a token inside a longer string is substituted as text. An unresolved token is left in place so the misconfiguration is visible rather than silent.LE/SE single-leg side, then the explicit override fields (so an override always wins over a placeholder), and finally the route's quantity multiplier.What a webhook can and cannot change mid-trade
Scenarios — exactly what to pass
signal_type is what separates them.1 · Go long
{ "signal": { "signal_type": "LONG ENTRY" } }
// …or let the alert decide the instrument, size and risk:
{ "signal": { "signal_type": "LONG ENTRY",
"symbol": "NIFTY", "fut_opt": "FUT",
"qty": 1, "sl": 30, "tgt": 50 } }
2 · Go short
{ "signal": { "signal_type": "SHORT ENTRY", "symbol": "NIFTY",
"fut_opt": "FUT", "qty": 1 } }
3 · Close the position
{ "signal": { "signal_type": "LONG EXIT" } }
4 · Book part of the position
{ "signal": { "signal_type": "LONG EXIT", "qty": 1 } }
// …or as a share of what is still open
{ "signal": { "signal_type": "LONG EXIT", "qty_pct": 50 } }
5 · Add to a winner (pyramid)
{ "signal": { "signal_type": "LONG ENTRY", "qty": 1 } } // first add
{ "signal": { "signal_type": "LONG ENTRY", "qty": 1 } } // second add
6 · Reverse
{ "signal": { "signal_type": "SHORT ENTRY", "qty": 1 } }
7 · Multi-leg — enter
legs only when the alert genuinely decides the structure, because it replaces the saved legs and their risk settings.{ "signal": { "signal_type": "ENTRY" } }
{ "signal": { "signal_type": "ENTRY", "legs": [
{ "underlying":"NIFTY", "expiry":"current", "option_type":"PE",
"strike_offset":0, "side":"SELL", "qty":1 },
{ "underlying":"NIFTY", "expiry":"current", "option_type":"CE",
"strike_offset":0, "side":"SELL", "qty":1 } ] } }
8 · Multi-leg — exit, whole or part
{ "signal": { "signal_type": "EXIT" } } // close it
{ "signal": { "signal_type": "EXIT", "qty": 1 } } // halve a 150-lot basket
9 · One alert, several brokers at different sizes
{ "signal": { "signal_type": "LONG ENTRY", "qty": 1 } }
// row 1 · Broker A ×1 → 1 lot
// row 2 · Broker B ×3 → 3 lots
10 · Change the stop or target on an open position
{ "signal": { "signal_type": "UPDATE", "sl_inr": 8000 } } // basket ₹
{ "signal": { "signal_type": "UPDATE", "sl_inr": 0 } } // breakeven
{ "signal": { "signal_type": "UPDATE", "leg": 0, "sl_price": 142.5 } }
{ "signal": { "signal_type": "UPDATE", "trailing": {
"enabled": true, "trigger_inr": 25000,
"step_inr": 5000, "lock_inr": 5000 } } }
"force": true only if immediate booking is what you want.11 · Wiring it in TradingView
// TradingView substitutes its own {{…}} placeholders before sending,
// so they can fill our fields. Numbers may arrive as strings; fine.
{ "signal": { "signal_type": "LONG ENTRY",
"qty": "{{strategy.order.contracts}}" } }
{{ticker}} into symbol for derivatives — a charting ticker like NIFTY1! is not the tradable root. Send the root, or leave symbol out and let the strategy hold it.Test it from a terminal
curl -X POST 'https://algo.fnotrader.com/api/webhook/sig/<signal-token>' \
-H 'Content-Type: application/json' \
-d '{"signal":{"signal_type":"LONG ENTRY","symbol":"NIFTY","fut_opt":"FUT","qty":1}}'
# → {"signal":"NIFTY breakout","started":1,"squared_off":0,"updated":0}
# The counts tell you what the alert actually did.
# Point it at a paper broker account first.
Backtest
Backtest Report
Backtest History
Every backtest you run is saved here. Click any row to reload the full report — no need to re-replay the bars.| Run (IST) | Strategy | Symbol | Period | Cost / Slip | Net PnL | Trades | Win % | Max DD | Status | Actions | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| No backtests yet — run one from the Backtest tab. | |||||||||||
🗑 Trash
Strategies and backtests you deleted. Restore puts an item back where it came from; Delete permanently removes it from this list. Nothing is lost from your account's records either way.Deleted strategies
| Strategy | Type | Deleted (IST) | Actions | |
|---|---|---|---|---|
| Loading… | ||||
Deleted backtests
| Strategy | Symbol | Period | Net PnL | Deleted (IST) | Actions | |
|---|---|---|---|---|---|---|
| Loading… | ||||||
✨ AI Access
Connect your AI assistant to FNOTrader MCP — build and backtest strategies by describing them in plain English.Build & backtest by asking your AI
Connect Claude, ChatGPT or any AI assistant to FNOTrader MCP and describe strategies in plain English — the AI creates them and runs backtests on the same engine, data and credits as this app.
Connect in three steps.
Open Claude.
Go to Settings → Connectors and choose Add custom connector. Added once on the web, it works in the desktop and mobile apps too.
https://mcp.fnotrader.com/mcpAdd the FNOTrader URL.
Name it FNOTrader and paste the connector URL. No token to copy.
Sign in with FNOTrader.
Authorize once in your browser with your regular fnotrader.com login. Enable FNOTrader in any chat and just ask.
Open your terminal.
Anywhere a claude session runs — no settings screen needed.
Add the FNOTrader server.
One command. Leave the token out — Claude Code signs you in.
Authenticate.
Run /mcp, pick fnotrader → Authenticate. Your browser opens the FNOTrader sign-in; approve it and you're connected.
Turn on Developer mode.
Settings → Connectors → Advanced → enable Developer mode. Plus, Pro and Business plans.
https://mcp.fnotrader.com/mcpCreate the connector.
Name FNOTrader, Authentication OAuth, and this MCP server URL.
Sign in with FNOTrader.
Approve once, then in a chat use + → Developer mode and tick FNOTrader. The “unverified connector” notice is expected for every custom connector.
Open Cursor Settings.
Settings ⚙ → Cursor Settings → Tools & MCP → Add Custom MCP, or edit ~/.cursor/mcp.json directly.
Add the FNOTrader server.
Paste this into mcp.json — no key, Cursor detects OAuth.
Sign in with FNOTrader.
Cursor opens your browser to authorize. The tools then appear under MCP — use them from Agent chat.
Pick your Gemini.
Web app: Settings → Connected apps → Add custom app (rolling out). CLI: edit ~/.gemini/settings.json. Enterprise: your admin adds it as a custom MCP server.
Add the FNOTrader server.
The web app takes the URL and signs you in. The CLI takes a token from the token page:
Verify with /mcp.
Run /mcp in the CLI (or open a chat on the web) — FNOTrader should list its tools. Then just ask.
Open your MCP client.
VS Code Copilot, Windsurf, Grok, LangChain / agno pipelines, your own agent — anything that speaks MCP over streamable HTTP.
https://mcp.fnotrader.com/mcpPoint it at the URL.
One endpoint, every client. Streamable HTTP — no SSE shim needed.
Authenticate.
Use OAuth if the client offers a sign-in. Otherwise send a token from the token page as Authorization: Bearer fntmcp_…, or append ?key=fntmcp_… to the URL.
Other ways to connect — config files, headless & CI
claude_desktop_config.json (needs Node.js), then restart Claude.Your usage
Journal
Performance from every executed run — overall, per portfolio, per strategy. Captured automatically when a run finishes; gate-blocked runs are excluded from the metrics.Day Summary
Strategy-wise
| Strategy | Mode | Accounts | Runs | Orders | Positions | PnL ₹ |
|---|
Account-wise
| Account / Strategy | Mode | Runs | Orders | Positions | PnL ₹ |
|---|
Equity curve
cumulative realized P&L by day, with the drawdown band — same read as the backtest report.Portfolio level
Holding-time analysis
Strategy level
Historical Charts
Replay any trading day's CE / PE option candles and combined premium straight from the bar archive, with backtest entry/exit markers. Cursor is synced across all three charts.| Date | ATM | Expiry | DTE | Open | Close | Reason | PnL ₹ | Cum PnL ₹ | DD ₹ | Legs (entry → exit) |
|---|---|---|---|---|---|---|---|---|---|---|
| Select a backtest result above to see its per-day trades. Click any row to load that day's CE/PE + combined charts with entry/exit markers. | ||||||||||