Operate TR-GAB from an AI agent.
TR-GAB ships a Model Context Protocol (MCP) server that exposes the product as tools: market data, mining, workflows, validation, library and exports. Everything the UI does has a tool behind it, and the tools talk to the same running application, so an agent and a human can work on the same data.
Same app, same data The server is a thin client of the TR-GAB app running on your machine. Nothing runs without the app.
Around 150 tools Read tools are safe to call freely; write tools mirror the UI buttons and are audited in AI Activity.
Contracts, not guesses Every tool description states its input shape. Unknown keys and invalid values fail with the valid list.
Setup
The MCP server is installed with TR-GAB. Point your MCP client (Claude Code, Codex, any MCP host) at it:
{
"mcpServers": {
"tr-gab-pro": {
"command": "node",
"args": ["C:/Users/<you>/AppData/Local/Programs/TR-GAB/resources/mcp-server/mcp-server/index.js"],
"env": { "TRGAB_PRO_URL": "http://localhost:30011" }
}
}
}
TRGAB_PRO_URLis the address of the running TR-GAB app (the installed app listens on port 30011).- Use exactly one MCP configuration per agent. Mixing two servers that point at different builds gives two behaviors and no error that explains them.
- After updating TR-GAB, restart the agent’s MCP process: a running server keeps the code it started with.
- Every write tool is logged in the app under AI Activity (
list_mcp_activity). Since 1.0.30 it is paginated and filterable:since/until(ISO date or epoch ms),tool,workflowId,limit(up to 500) andoffset; the answer carriestotalandnextOffsetso you can walk a long day without reading it whole. - The app can refuse connections for one or two seconds while it starts a long job (a miner, a materialization). The server retries network errors a couple of times by itself; if it still fails, the error says so and you can simply retry.
Tool map: what each screen is called
list_market_sources (take id and broker_utc_offset_sec from here), get_default_market_source (use marketSourceId for chaining), set_default_market_source, list_broker_symbols, resolve_broker_spread, calibrate_broker_symbol, get_default_experts_path (which MT5 terminal exports go to)list_market_vault_data (mode: "summary" = what is downloaded, per symbol and timeframe; "range" = first / last candle and count for one symbol + timeframe; "preview" = a few candles between from and to; the summary ignores symbol filters on purpose - ask for range when you want one symbol), list_symbols (what the broker offers; use search and limit; omit sourceId for the default source), sync_candles, get_sync_status, stop_synccreate_rule_mining_job, get_rule_mining_job, list_rule_mining_results (include: "summary" gives rule_label), rule_mining_feasibility_scancreate_pattern_mining_job, create_event_mining_job and their list_*_resultscreate_workflow, move_workflow_to_list, add_workflow_process (with target_databank_id / output_databank_id), update_workflow_process, create_workflow_databank, list_workflow_databanksrun_workflow, get_workflow_run (run.progress and processes[].status while it runs), cancel_workflow_run, list_workflow_run_items (filter with databankId), list_workflow_robustness, list_workflow_walkforward (include: "metrics", filter by processId)create_kpi_filter, list_kpi_filters, create_filter_groupsave_strategy_from_databank, list_strategies, get_strategy, rename_strategy, run_strategy (backtest a builder without saving; accepts projectId), get_strategy_zones (stagnation / win-lose / drawdown / macro zones of a saved strategy), get_strategy_regime_zones (in which market regimes the strategy wins or fails), rerun_and_save_strategy (re-run a saved builder, optionally patched, and persist fresh results in the same id)compute_regimes (D1 regimes of a symbol by slope in degrees, plus current), create_regime_projects (one Project Days project per regime)create_pattern_project, add_pattern_project_days, list_pattern_project_days, delete_pattern_project_day; then projectId in create_rule_mining_job / create_pattern_mining_job / create_event_mining_job, in the miner_* workflow processes and in run_strategycreate_portfolio (name, strategyIds, chartMode), update_portfolio (action: "addStrategy" and the other actions), get_portfolio, calculate_portfolio, export_portfolio_to_mt5export_mining_results_to_mt5, backtest_mining_result, scan_tester_batch, run_tester_batch, get_tester_batch_run (poll, no trades), get_tester_batch_replay (real MT5 trades vs engine trades)kpi_real (strategyName = the bot name; how a strategy is doing in LIVE trading: trades, net, PF, win rate, avg win / loss, max drawdown, first / last trade, stagnation - the same numbers the Real card and the cockpit Real panel show), list_real_strategies (every bot with a live log and its real KPIs)list_real_portfolios, create_real_portfolio (name, labels = bot names), update_real_portfolio (action: "addStrategy" | "removeStrategy" | "rename"), delete_real_portfolio, calculate_real_portfolio (real KPIs of the group and of each strategy; include: "full" adds the equity points). Groups of REAL strategies, apart from the Library portfolios aboveMining and testing only on chosen days (Project Days)
A Pattern Scanner project is a named bag of days of one symbol: the days you consider bullish, a breakout
list, a regime. Build it from the agent with create_pattern_project({ project: { name } }) (a plain
project: do not pass miningConfig) and add_pattern_project_days({ projectId, symbol, days: ["YYYY-MM-DD", { dayStart, dayEnd }], timeframe }); list_pattern_project_days returns the expanded list the engine will
trade. Then pass that projectId wherever a backtest runs:
create_rule_mining_job,create_pattern_mining_job,create_event_mining_joband theminer_rule/miner_pattern/miner_eventworkflow processes mine only on those days. The date range becomes the first and last saved day (anydataModeor lookback is ignored), 600 bars before the first day are loaded so indicators warm up, and the response echoesprojectDays: { projectId, name, days, from, to }. An empty or unknown project is a 400, never a full-range run.- Every mined strategy carries
dataMode: "project"and itsprojectId, so the downstream workflow stages (backtest,simple_retester,add_condition,evolver,robustness_filter,walkforward) keep the same days automatically. run_strategy({ builder, projectId })is the Strategy Lab retest in “Project Days” mode: trades open only on the saved days;summaryOnly(default true) keeps KPIs andmeta.projectDayswithout the trade list.
The exported MT5 expert does not filter by days: the Strategy Tester runs the whole range.
Reading a strategy in zones
get_strategy_zones({ strategyId }) returns, per type, the coloured bands the Strategy Lab “Zones” selector
paints: macro (red from an equity peak until a new high, only with 5 sessions, 5 trades and a 2% dip),
stagnation (every stretch without a new equity high lasting at least options.stagnationMinDays, default 5;
green = making new highs), trend (win/lose zigzag by options.trendMinMovePct, default 1% of equity; green
rising from a trough, amber falling from a peak, so a rebound is green before the old high is beaten) and
drawdown_peak_to_trough (peak to deepest trough of at least options.drawdownMinDepth, default 2%). Each
zone has kind, label, from, to, durationDays, tradesCount and depthMax; counts summarises
them. Days are operable days when the strategy is a Project Days one (since 1.0.30): the server reads
the saved project, stagnationMinDays and every durationDays count only the days the strategy could
trade, and the answer says daysBasis: "operable" with projectId and operableDays; for a full-range
strategy daysBasis is "calendar". So a funded rule like “60 operable days without a new equity high”
is read straight from the zones, no calendar arithmetic on your side. You can also pass equity[] + trades[] inline. The badPeriods inside get_strategy are the older
stagnation list with a 1-day threshold: they look “always red” by construction.
Market regimes by slope, and where a strategy wins or fails
A regime is the state of the market on each day: the slope of the price, in degrees. For every D1 bar the
platform fits a regression of the log price over the last window bars (default 40); the slope is a percent
per bar and the angle is atan(pctPerBar / refPctPerBar), so with refPctPerBar: 1 (default) 45 degrees
means the price gains 1% per bar. It is the inclination you see on the chart, the same for gold at 4,000 and
EURUSD at 1.10, and it does not depend on volatility. Days are bucketed into strong_up (angle at least
strongUpDeg, 20), up (at least upDeg, 8), flat, down (at most downDeg, -8) and strong_down (at most
strongDownDeg, -20);
hysteresisDeg (2) and minRunBars (3) remove flicker. The answer carries metric: { id: "slope-deg", version, unit: "degrees", since, formula, defaults }: the measure changed in 1.0.27 (from a percentage
channel to the slope in degrees) and Project Days created before that were bucketed with the old measure.
Compare metric before comparing regimes between two runs or two versions, and recreate old regime
projects (create_regime_projects with replace: true) when it differs. These eight knobs are the sliders of the Pattern
Scanner “Regimes” tab and of the strategy viewer: pass the values the user tuned in regimeParams, otherwise
the answer will not match what they see on screen.
compute_regimes({ symbol, timeframe: "D1", from, to, params })returnsdays[{ date, angleDeg, regime }],runs[](same-regime stretches, far apart in time still belong to the same regime),summary[],anglePercentiles(to place the thresholds) andcurrent= the regime of the last closed bar (regime,angleDegof the run,lastBarAngleDeg,since,days). Read-only.create_regime_projects({ ...same, regimes?, replace? })creates one Project Days project per regime whose items are the runs, then mine or retest only inside that regime withprojectId(previous section).get_strategy_regime_zones({ strategyId, zoneType?, zoneOptions?, regimeParams?, minTrades? })crosses, by date, the D1 regimes of the strategy symbol with the strategy zones and its trades. It is stateless: recomputed on every call, nothing stored, and the response echoes every parameter used.zoneTypepicks one zone family (trend= win / lose is the default;stagnation,drawdown_peak_to_trough,macro) andzoneOptionsits thresholds, exactly as inget_strategy_zones. The answer carriescross.pnlByRegime[](each trade attributed to the regime of the D1 bar before its entry day, so there is no lookahead: days, trades, net, profitFactor, winRate, expectancy, maxDrawdown, verdictgood | bad | neutral | insufficientwith fewer thanminTrades= 20 trades),cross.zoneShareByRegime[](share of each regime’s days spent in each zone,badShare),cross.badZonesByRegime[](the regime that dominated each bad stretch),goodRegimes,badRegimesandregimes.current.
Typical loop for an agent: compute_regimes to read current, get_strategy_regime_zones to see whether the
strategy is good or bad in that regime, then create_regime_projects + run_strategy / the miners to re-mine
or retest only in the good regimes. The verdict depends on the zone thresholds and the regime knobs you pass:
keep them in the conversation (or in the created projects, which store their params) so results are comparable.
Three details that make the daily loop close (since 1.0.28):
- The open bar does not count.
sync_candleswrites the bar of the current day while it is still forming;compute_regimes,create_regime_projectsandget_strategy_regime_zonesdrop it, socurrentis always the last closed bar and the answer saysopenBarExcluded: "YYYY-MM-DD". PassincludeOpenBar: trueto keep it. - Change a saved strategy without losing its backtest.
save_strategywith anidand noresultsnow keeps the stored equity, trades, KPIs and bad periods (resultsKept: true), and flagsresultsStale: truewhen the builder changed; sendresults: nullto drop them on purpose. To change the builder and get fresh results in the same id without moving the blob through the agent, callrerun_and_save_strategy({ id, builderPatch }), e.g.builderPatch: { moneyManagement: { riskAmount: 25 } }: the server re-runs the saved builder (Project Days mode included) and persists the new results under the same id. WithtoToday: true(since 1.0.30) the range is extended to the last candle in the vault for the strategy symbol and timeframe (the builder becomesdataMode: "range", start resolved from the saved mode,endDate= last vault day; the answer carriesdateRange), so the engine backtest covers the same period as the live account and both curves can be compared side by side. It does not apply to a Project Days strategy: add the new days to its project. - The zones read one equity point per trade.
get_strategy_zonesandget_strategy_regime_zoneswork on the per-trade equity (deposit + one point per closed trade). When a strategy was saved with the per-bar series (Strategy Lab,rerun_and_save_strategybefore 1.0.29) the server rebuilds it from the trades and saysequityBasis: "synthesized";"trades"means the stored series was already per trade. Note that the profit factor, and with it the verdict per regime, can change withriskAmounteven with the same trades: lots are rounded to the broker minimum, so cross at the risk you will trade. run_strategyreproduces the miner exactly when the builder is unchanged. Take the builder fromlist_workflow_run_items(include: "builder") and run it as is: same trades, profit factor and net (verified on project and range items). Two edits break parity on purpose:moneyManagement.riskAmountchanges the lots (rounded to the broker step, so a trade can appear or vanish at a limit), and a builder still indataMode: "vault_relative"resolves its window against the vault of today, not the day it was mined - pindataMode: "range"with the item’sstartDate/endDateto reproduce.provenance.swapModeis what the broker reports; swap is only charged whenprovenance.swapAppliedis true (builder.swap.enabled).- Decide at one risk, ship at that risk.
export_portfolio_to_mt5({ portfolioId, moneyManagement })overrides the money management of every exported EA (riskAmount,riskPerTrade,fixedLots,method…) without touching the Library; the answer echoesmoneyManagementApplied.
Comparing the engine with the real MetaTrader tester
The internal engine and the MetaTrader Strategy Tester are two independent measurements of the same strategy, and the platform keeps both:
- Export the candidates (
export_mining_results_to_mt5, or anexport_mt5process in a workflow), thenrun_tester_batchon that folder; pollget_tester_batch_run- each item returns the MT5 KPIs. The poll response does not carry per-trade arrays (passincludeEngineTrades: trueif you need them). Since 1.0.30 the run first checks that the MetaTrader terminal of the source answers: if it does not, the call fails at once with409 TERMINAL_UNREACHABLE(anderrorKindon the run) instead of a batch that stayspendingforever; open the terminal (the app launches it on/connectwhen it is closed) and retry, or passskipTerminalCheck: truewhen you know it is up. get_tester_batch_replay(runId, itemId)returnsmt5Tradesandmt5Equity(real tester output) next toengineTradesandengineEquityfor the same strategy: align them by entry time and compare fills, PnL per trade and where the curves diverge.- Inside workflows,
list_workflow_run_itemsexposeskpis(engine) andmt5Results(broker) per candidate after a tester process has run.
Compare trade counts before KPIs. The replay carries tradesMismatch when the engine and MT5 executed a
different number of trades (more than 10% apart). A better profit factor on the broker side usually means
the tester skipped trades - lot cap, margin, volume step - not a better strategy. Treat any disagreement
as a finding about costs, sessions, fills, lot sizing or pending-order handling, not as noise.
The minimal end-to-end cycle
list_market_sources- take the realsourceIdfromid(see the note on masking below), orget_default_market_sourceand usedefault.marketSourceId.list_market_vault_data(mode: "summary") - what symbols and timeframes are already downloaded. If the symbol is missing,sync_candlesfor the timeframe you will mine (H1, H4…) and pollget_sync_statusuntilstatusisdone. Sync the history before mining: three months of M15 can look like a strategy that five years prove to be noise.create_rule_mining_job- see the minimal example below. Pollget_rule_mining_job;completed_combinationsequalstotal_combinationswhen it finished.create_workflow, thenadd_workflow_processwith aminer_ruleas the first stage (same config as the job) followed byfilter,robustness_filter,walkforwardandexport_mt5, each with itstarget_databank_id(input) andoutput_databank_id(output) set in the same call. Thenrun_workflowand pollget_workflow_run. There is no tool that moves the results of a standalone job into a workflow databank: the job is for exploring, the workflow miner is the pipeline.list_workflow_run_itemswithdatabankIdto read one stage;save_strategy_from_databankfor the survivors.export_mining_results_to_mt5/backtest_mining_resultagainst the default broker.
Minimal create_rule_mining_job
{
"name": "GOLD H1 RSI probe",
"config": {
"sourceId": "<uuid from list_market_sources>",
"symbol": "GOLD",
"timeframe": "H1",
"dateFrom": "2024-01-01",
"dateTo": "2025-12-31",
"direction": "both",
"scans": [
{ "kind": "indicator_threshold", "enabled": true, "indicators": ["RSI"], "periods": [14], "operators": [">", "<"], "thresholds": [30, 70] }
],
"slAtrRange": { "min": 1, "max": 3, "step": 1 },
"tpAtrRange": { "min": 1.5, "max": 4, "step": 1 },
"atrPeriod": 14,
"minTrades": 30,
"mmMethod": "fixed_risk_amount",
"riskAmount": 100,
"initialCapital": 10000,
"entryOrder": "market"
}
}
The three scans[] kinds (keys differ per kind)
[
{ "kind": "indicator_threshold", "indicators": ["RSI"], "periods": [14], "operators": [">", "<"], "thresholds": [30, 70] },
{ "kind": "price_vs_indicator", "priceField": "close", "indicators": ["EMA"], "periods": [20, 50], "operators": [">", "<"] },
{ "kind": "indicator_vs_indicator", "leftIndicators": ["EMA"], "leftPeriods": [10], "rightIndicators": ["EMA"], "rightPeriods": [50], "operators": [">", "<"] }
]
priceFieldis one string (close,open,high,low), not an array.periodsandthresholdsare arrays of numbers, not ranges. Indicators:RSI,CCI,ATR,SMA,EMA,VWAPfor thresholds;SMA,EMA,VWAPagainst price;SMA,EMAagainst each other.- A scan without
kind, with the keys of another kind, or aregimes[].kindoutsidetrend | momentum | cci | chop | hurst | entropyis rejected with the valid list.regimes[]entries are{ kind, enabled, indicator?, period?, mode?: "above" | "below" | "both", tf? }. directiondefaults tobothin the job and in the feasibility scan.entryOrder:market(default when omitted),stop(breakout),limit(pullback) orsweep(tries market, stop and limit per candidate and keeps the best - about three times the work). Pending orders takependingOffsetRange(ATR multiples) andpendingExpirationBars.sessionFilteris{ enabled, timeFrom: "HH:MM", timeTo: "HH:MM" }in broker time; other keys are rejected. It applies while mining, in the miner screens and in workflow miners alike.- Costs:
spreadandslippageare in price units (the symbol quote: 0.49 on GOLD with point 0.01 is 49 points).spreadModedefaults toauto, which uses the broker spread and ignoresspreadwhen the broker has one;manualalways uses yourspread. The job and scan responses returnspreadAppliedwith the value the engine actually used, its source and its units. - A finished job with zero results is not an error: check
total_combinations, then lowerminTradesor the KPI filter, or widen the date range or the scan.
Feasibility scan
rule_mining_feasibility_scan takes the same config plus timeframes[], maxVariantsPerTf,
includeSlTpVariation and scanOrder (an array such as ["scans", "sltp"], required when more rows pass
than maxVariantsPerTf). Each row carries ruleName, direction, scanCondition, slAtr, tpAtr,
rawSignals and the KPIs. It persists nothing.
Contracts every tool follows
sourceIdis masked in read tools.list_market_vault_data,list_workflow_run_items,get_workflowand anyconfig_jsonreturn"__SANITIZED__"in place of source ids (privacy policy). Take the real id fromlist_market_sources(id) orget_default_market_source(marketSourceId) and put it back before re-sending a config.brokerIdis masked everywhere because it embeds the account number: the broker tools (list_broker_symbols,calibrate_broker_symbol, symbol overrides) acceptsourceIdinstead, or nothing for the default source.- Response size levels.
list_workflow_robustness,list_workflow_walkforwardandlist_rule_mining_resultstakeinclude: "summary" | "metrics" | "full".summaryhas no numbers;metricsis what an agent needs (percentiles, in-sample/out-of-sample bundles,rule_jsonandrule_label) in a few KB per row;fullcarries per-simulation curves, every walk-forward window with all its candidates, or every trade - hundreds of KB per row. - Time is broker time. Every candle and trade timestamp is the broker server’s time with a trailing
Zthat is only a serialization artifact.list_market_sourcesreturnsbroker_utc_offset_sec(broker minus UTC) so you can translate sessions;sessionFilterwindows are broker time too. - KPI ids are
snake_caseas in the KPI registry:net_profit,profit_factor,max_drawdown_pct,win_rate,rrr,expectancy,sharpe,sortino,total_trades… The camelCase names you see in engine results (profitFactor,maxDrawdownPct,totalNetProfit,percentProfitable) are accepted as aliases. An unknown id is rejected with the valid list; it never silently matches nothing. - KPI filter clauses are always combined with AND.
opis one of><>=<==!=.modeis optional: omit it for observed KPIs, or use{ "type": "robustness", "level": 25 }/{ "type": "wfo", "period": "oos" }to evaluate Monte Carlo or walk-forward results. - Workflows are created in the default group and list. Use
move_workflow_to_listafterwards. - One run alive per workflow. Calling
run_workflowwhile a run is in progress returns409 RUN_IN_PROGRESSand does not touch the live run. Pollget_workflow_runor cancel it explicitly withcancel_workflow_run. - Process configs.
filter,robustness_filterandwalkforwardreject unknown keys with the valid list.add_condition,backtest,simple_retester,export_mt5,tester_batch_runandclearare documented in theadd_workflow_processdescription (add_conditiontakesconditions[], notscans[]). Validation processes without afilterPayloadevaluate nothing: their rows come back withevaluated: falseandpassed: null. - Process identity.
processes[].process_id(fromget_workflow/get_workflow_run) is stable; use it asprocessIdwhen reading robustness or walk-forward rows.processIdxis the position in the run plan and changes when you run a subset withprocessIds. - Robustness needs perturbation to mean anything.
probabilityis per parameter (default 0.10); with a one-condition rule most simulations come out identical to the original and the row says so inwarnings_jsonandidentical_iterations. Raise it to 0.5-1.0 for simple rules. sync_candlesis asynchronous. It returns a job id immediately; pollget_sync_status. It downloads what the broker serves: some brokers keep only a few months of M1, so sync H1/H4 directly when you need years of history. By default a sync also re-calibrates the broker spread override from the ticks it downloaded, which changes the spread of every later backtest; passcalibrateBroker: falseto keep it, and readwarnings[]inget_sync_status.- Feature gates. Mount-on-broker tools (
mount_mt5_bot,get_mt5_mount_statusand the NinjaTrader and cTrader equivalents) are off in the installed app and answer404withreason: "feature-disabled". Feasibility scans and Easy mode are available since 1.0.16.
Reading validation results
list_workflow_robustness and list_workflow_walkforward return one row per candidate with evaluated
and passed. passed only means something when evaluated is true, that is, when the process had a
filterPayload. A row with passed: null was measured but not judged. Ask for include: "metrics" to
read by how much a candidate passed: Monte Carlo percentiles (5, 25, 50, 75, 95) of every KPI, and the
in-sample / out-of-sample / average bundle of the walk-forward.
max_drawdown_pct is the largest peak-to-trough fall of the closed-trade equity curve, relative to the
peak, not to the initial capital. A value above 100% means the equity went below zero from its peak
(the account was blown), not a calculation error.
Treat the tool descriptions as the contract: they are generated from the same code that validates your input. When something is rejected, the error carries the valid values - correct once instead of retrying.