Rule-based forex scalping bot for IC Markets via the cTrader Open API. The current implementation runs one active strategy, ny_asian_continuation, on M5 candles with H1 EMA trend context, KES-denominated risk controls, optional live auto-execution, historical backtesting, and HTML reporting.
Risk notice: This is experimental trading software. Start in
demo, run in monitor-only mode first, and do not deploy with real funds unless you understand the strategy, execution risks, API behavior, and all configuration values.
| Area | Files |
|---|---|
| Live bot loop | index.js |
| cTrader Open API client | icmarkets.js, OpenApi*.proto |
| Strategy and indicators | indicators.js |
| Runtime configuration | config.js |
| Risk and position sizing | risk-manager.js, position-sizing.js |
| OAuth token helper | auth.js |
| Symbol lookup helper | get-symbols.js |
| Historical data download | download-history.js |
| Backtesting | backtest-multi.js, history_*.json |
| Analysis and reporting | trade-analyzer.js, generate-report.js, report-metrics.js |
| Process/deployment | ecosystem.config.cjs, Dockerfile |
| Tests | tests/ |
| Runtime state | state.json, risk-state.json, activity.log, logs/ |
Runtime/generated files such as .env, state.json, risk-state.json, history_*.json, trades_backtest.json, report.html, logs, and node_modules/ are ignored by git.
graph TD
A[Start index.js] --> B[Load config.js and .env]
B --> C[Connect cTrader WebSocket]
C --> D[Authenticate app + account]
D --> E[Resolve symbols and reconcile broker positions/orders]
E --> F[Subscribe to live ticks]
F --> G[Poll every 10 seconds]
G --> H{Weekend or outside configured UTC session?}
H -- yes --> I[Sleep / skip]
H -- no --> J[Refresh closed M5 entry candles]
J --> K[Refresh closed H1 candles]
K --> L[Detect H1 EMA200 trend]
L --> M[Build today's Asian range]
M --> N[Generate NY Asian continuation setup]
N --> O{No signal?}
O -- yes --> I
O -- stop setup --> P{Auto execute enabled?}
P -- no --> Q[Print monitor signal only]
P -- yes --> R[Risk gate + spread gate]
R --> S[Place broker stop order or local pending stop]
S --> T[Track pending/active trade in state.json]
T --> U[Execution events update RiskManager]
index.js is the main process.
- Loads environment variables through
config.jsusingdotenv/config. - Creates an
ICMarketsClientand aRiskManager. - Connects to cTrader:
demo.ctraderapi.com:5035whenCTRADER_ENV=demoor unset.live.ctraderapi.com:5035whenCTRADER_ENV=live.
- Authenticates with:
CTRADER_CLIENT_IDCTRADER_CLIENT_SECRETCTRADER_ACCESS_TOKENCTRADER_ACCOUNT_ID
- For every configured pair in
TRADING_PAIRS, it:- fetches/validates broker symbol details where possible,
- reconciles existing broker positions and pending orders,
- subscribes to live tick events.
- Starts a 10-second poll loop.
- During configured session windows, it refreshes closed M5 and H1 candles, runs the strategy, and either logs signals or executes them depending on
--auto-execute.
| Mode | Command | Places orders? | Use case |
|---|---|---|---|
| Monitor only | npm start |
No | Safest first run. Connects, authenticates, subscribes, prints status/signals. |
| Auto execute | npm run auto |
Yes for NY; London only if explicitly enabled | Runs NY auto-execution plus the cleaned London monitor profile in one process. |
| NY Asian monitor profile | npm run start:ny-asian |
No | Same strategy with explicit environment overrides. |
| NY Asian auto profile | npm run auto:ny-asian |
Yes | PM2/Docker-style live execution profile. |
Supported STRATEGY_MODE values in config.js are:
STRATEGY_MODE=ny_asian_continuation
STRATEGY_MODE=london_asian_fake_break_reversal
STRATEGY_MODE=combined_ny_londonny_asian_continuation is the current live-capable strategy. Its implementation lives in indicators.js and is called by both index.js and backtest-multi.js.
indicators.js also contains a tested London fake-break generator, generateLondonAsianFakeBreakReversalSignal(), for the proposed London module. It is wired into backtesting only through STRATEGY_MODE=london_asian_fake_break_reversal; live execution in index.js intentionally blocks this mode until a later monitor/live phase.
combined_ny_london runs both live routers in the same process so account reconciliation, risk gates, trade slots, and local state stay shared. London remains monitor-only unless LONDON_LIVE_EXECUTION_ENABLED=true is explicitly set.
| Pair type | Stop Loss (pips) | Take Profit (pips) | R:R Ratio |
|---|---|---|---|
| FX (EUR/USD, GBP/USD, USD/JPY) | 5–15 pips | 7.5–22.5 pips | 1:1.5 |
| Gold (XAU/USD) | 200–2,000 pips | 600–6,000 pips | 1:3.0 |
Most common FX setups:
- 5 pip SL → 7.5 pip TP (333 backtested trades)
- 10 pip SL → 15 pip TP (247 trades)
Gold note: 1 pip = $0.01, so 400 pips = $4.00 SL and 1,200 pips = $12.00 TP
- Build the Asian range from M5 candles between
NY_ASIAN_START_UTCandNY_ASIAN_END_UTC.- Defaults:
00:00–07:00 UTC.
- Defaults:
- Trade only during the NY continuation window.
- Defaults:
12:30–15:30 UTC. - The current preferred-time filter waits until
13:00 UTCby default.
- Defaults:
- Require the first clean NY break of the Asian high or low.
- Default minimum break:
3.0pips.
- Default minimum break:
- Optionally require H1 alignment.
- Default: enabled.
- BUY requires H1
bull; SELL requires H1bear.
- H1 trend is detected using closed H1 candles:
- close above rising EMA200 =
bull, - close below falling EMA200 =
bear, - otherwise
neutral.
- close above rising EMA200 =
- Generate a stop-entry setup:
buy_stopabove Asian high, orsell_stopbelow Asian low.
- Validate stop distance.
- Default min risk:
5pips. - Default max risk:
12pips inconfig.js. - PM2 overrides this to
10pips inecosystem.config.cjs.
- Default min risk:
- Set reward using
NY_ASIAN_RR_RATIO.- Default:
1.2R.
- Default:
- Expire pending entries after
NY_ASIAN_PENDING_EXPIRY_BARSM5 bars.- Default:
3bars.
- Default:
- Close active trades by:
- broker SL/TP,
- time exit after
NY_ASIAN_TIME_EXIT_BARS, default12, or - force exit after
NY_ASIAN_FORCE_EXIT_UTC, default16:00 UTC.
SL and TP are not fixed — they adapt to the width of the Asian range (the breakout setup). Wider ranges produce wider stops and targets; tighter ranges produce tighter ones.
| FX (EUR/USD, GBP/USD, USD/JPY) | Gold (XAU/USD) | |
|---|---|---|
| Stop Loss | 5–15 pips (avg 8) | 200–2,000 pips (avg 401) |
| Take Profit | 7.5–22.5 pips (avg 12) | 600–6,000 pips (avg 1,203) |
| R:R ratio | 1 : 1.5 (fixed) | 1 : 3.0 (fixed) |
For gold, 1 pip = $0.01, so 400 pips = $4.00 SL and 1,200 pips = $12.00 TP.
Most common FX setups: 5 pip SL / 7.5 pip TP (333 backtested trades), 10 pip SL / 15 pip TP (247 trades).
How SL/TP are determined:
- The Asian range (high/low between 00:00–07:00 UTC) is measured.
- Price must break out of that range by at least 3 pips (FX) or 100 pips (Gold).
- The entry is placed just beyond the break (entry + buffer).
- The SL is placed on the other side of the break.
- The TP is set at
risk × R:R ratio.
Session windows are configured in config.js using SESSION_WINDOW_MODE.
| Mode | UTC windows | Notes |
|---|---|---|
ny_only |
12:30–16:00 |
NY overlap only. |
ny_quality |
12:30–16:00 |
Same window, alternate profile name for testing. |
ny_trimmed |
12:45–15:45 |
Slightly narrower NY window. |
london_only |
07:00–10:00 |
London open only. Used by London fake-break backtests. |
all_windows |
07:00–10:00, 12:30–16:00 |
Default in config.js; strategy defaults still allow ny_overlap only. |
Important distinction:
SESSION_WINDOW_MODEcontrols when the bot wakes up and evaluates pairs.- Strategy-specific allowed-session variables such as
NY_ASIAN_ALLOWED_SESSIONSandLONDON_FAKE_BREAK_ALLOWED_SESSIONScontrol whether a strategy is allowed to trade in a named active window. - By default,
NY_ASIAN_ALLOWED_SESSIONS=ny_overlap, so London evaluations do not become London trades unless you explicitly change that strategy setting.
Strategies:
- NY Asian Continuation — allowed session:
ny_overlaponly → 12:30–16:00 UTC window, signals from 13:00–15:30 UTC (prefer-after filter) - London Fake-Break Reversal — allowed session:
london_open→ 7:00–9:00 UTC window, pairs: EUR/USD + USD/JPY only
| Day | London (07:00–09:00 UTC) | NY Overlap (13:00–15:30 UTC) |
|---|---|---|
| Mon | blocked (weekday) | NY continuation — all pairs |
| Tue | London fake-break — EUR/USD, USD/JPY | NY continuation — all pairs |
| Wed | London fake-break — EUR/USD, USD/JPY | NY continuation — all pairs |
| Thu | London fake-break — USD/JPY only (EUR/USD excluded) | NY continuation — all pairs |
| Fri | blocked (weekday) | NY continuation — all pairs |
| Sat/Sun | closed | closed |
GBP/USD is excluded from London entirely (LONDON_FAKE_BREAK_ALLOWED_PAIRS=EUR_USD,USD_JPY).
Strategy: NY Asian Continuation only, allowed sessions: london_open + ny_overlap, max 2 trades per session
| Day | London open (07:00–10:00 UTC) | NY Overlap (12:30–16:00 UTC) |
|---|---|---|
| Mon–Fri | NY continuation (up to 2 trades) | NY continuation (up to 2 trades) |
| Sat/Sun | closed | closed |
- H1 EMA200 trend alignment required (FX only; Gold has it disabled)
- Max 1 trade per pair per session (FX); Max 2 trades per session (Gold)
- London module brake: stop after 1 loss per day
- Daily KES stop-loss and profit-target gates
- Spread and quote-freshness gates
Backtested against tick data from data/ticks.sqlite. Each month starts from $1,172.13 balance.
| Month | NY only (1 trade/session) | Both sessions (2 trades/session) | ||
|---|---|---|---|---|
| Net | Win% | Net | Win% | |
| Jan | +$52.45 | 50.0% | +$232.17 | 59.3% |
| Feb | +$95.50 | 75.0% | +$154.36 | 64.7% |
| Mar | +$46.36 | 50.0% | +$106.31 | 43.5% |
| Apr | +$9.14 | 30.8% | +$142.81 | 45.2% |
| May | +$63.13 | 50.0% | +$91.46 | 40.7% |
| TOTAL | +$266.58 | +$727.11 |
Decision: Both sessions + 2 trades/session produces 2.7x more profit with acceptable drawdown. Max drawdown increased from 2.76% to 3.4% in worst month (April), but remains within safe bounds.
Risk is managed by risk-manager.js and position-sizing.js.
| Setting | Default | Meaning |
|---|---|---|
accountCapitalKES |
250000 |
Sizing reference capital in KES. |
RISK_PER_TRADE_PERCENT |
0.5 |
Risk percent per trade. |
ENFORCE_DAILY_STOP_LOSS |
true |
Disable trading after daily realized loss threshold. |
DAILY_STOP_LOSS_KES |
3000 |
Daily realized loss gate. |
DAILY_PROFIT_TARGET_KES |
5000 |
Daily realized profit gate. |
maxLeverage |
100 |
Position sizing cap. |
usdKesRate |
129.0 |
Conversion rate used for KES sizing/reporting. |
MAX_TOTAL_TRADES |
3 |
Account-level active + pending slot cap. |
MAX_TRADES_PER_PAIR |
1 |
Pair-level active trade cap. |
maxPositionSizeUnits |
100000 |
Absolute unit cap. |
MAX_SLIPPAGE_PIPS |
0.5 |
cTrader market-order slippage setting. |
MAX_SPREAD_PIPS |
1.5 |
Live spread gate before execution. |
MAX_QUOTE_AGE_MS |
5000 |
Live quote freshness requirement. |
Position sizing formula:
risk amount KES = accountCapitalKES × riskPerTradePercent / 100
units = risk amount KES / (stop loss pips × pip value per unit in KES)
Then units are capped by leverage and maxPositionSizeUnits.
| File | Written by | Purpose |
|---|---|---|
state.json |
index.js |
Active trades and pending orders, so restarts can reconcile/adopt existing broker state. |
risk-state.json |
risk-manager.js |
Daily realized P&L, daily risk gates, open trade count, intra-day trade log. |
activity.log |
icmarkets.js |
Connection health alerts. |
logs/out.log, logs/err.log |
PM2 | PM2 stdout/stderr when using ecosystem.config.cjs. |
risk-state.json resets daily counters when the UTC day changes.
- Node.js 20+ recommended. The Docker image uses
node:20-slim. - npm or pnpm.
- IC Markets cTrader account linked to a cTrader ID.
- cTrader Open API application credentials.
- A cTrader Open API access token.
Install dependencies:
npm installor, if you prefer the existing lockfile workflow:
pnpm install --frozen-lockfileCreate .env from .env.example:
cp .env.example .envRequired values:
CTRADER_CLIENT_ID=your-client-id
CTRADER_CLIENT_SECRET=your-client-secret
CTRADER_ACCOUNT_ID=your-ctrader-account-id
CTRADER_ACCESS_TOKEN=your-access-token
CTRADER_ENV=demoRecommended defaults for first runs:
CTRADER_ENV=demo
TRADING_PAIRS=EUR_USD,GBP_USD,USD_JPY
SESSION_WINDOW_MODE=all_windows
STRATEGY_MODE=ny_asian_continuation
RISK_PER_TRADE_PERCENT=0.5Never commit .env; it is ignored by .gitignore.
- Create/login to a cTrader ID at
https://id.ctrader.com. - Log in to IC Markets cTrader web at
https://ct.icmarkets.com. - Confirm your trading account is visible.
- Note your cTrader account ID/account number for
CTRADER_ACCOUNT_ID.
- Go to
https://openapi.ctrader.com. - Add a new application.
- Use this redirect URI because
auth.jslistens locally on port3000:
http://localhost:3000/callback
- Copy the client ID and client secret into
.env.
Run:
npm run authWhat happens:
auth.jsprints an authorization URL.- Open it in your browser.
- Approve access.
- Browser redirects to
http://localhost:3000/callback. - The script exchanges the code for tokens.
- Copy the printed value into
.env:
CTRADER_ACCESS_TOKEN=printed-access-tokenThe script also prints CTRADER_REFRESH_TOKEN; the current bot reads CTRADER_ACCESS_TOKEN only, so re-run npm run auth when the access token expires.
Run:
npm run symbolsCopy the printed ctraderSymbolIds block into config.js if your broker account returns IDs that differ from the current defaults.
npm startThis connects to cTrader, authenticates, reconciles account state, subscribes to ticks, and logs strategy status. It does not place trades.
npm run autonpm run auto uses STRATEGY_MODE=combined_ny_london with the NY strategy live-capable and the cleaned London profile enabled for observation:
STRATEGY_MODE=combined_ny_london SESSION_WINDOW_MODE=all_windows \
LONDON_MONITOR_ENABLED=true \
LONDON_FAKE_BREAK_TRADE_END_UTC=9 \
LONDON_FAKE_BREAK_ALLOWED_WEEKDAYS=Wed \
LONDON_FAKE_BREAK_ALLOWED_PAIRS=EUR_USD,USD_JPY \
LONDON_MAX_LOSSES_PER_DAY=1 \
node index.js --auto-executeAuto-execute can place broker stop orders or market orders when the strategy, risk, session, quote, and spread gates pass. London signals are logged but not placed unless LONDON_LIVE_EXECUTION_ENABLED=true is set.
TRADING_PAIRS=EUR_USD npm startMAX_TOTAL_TRADES=1 MAX_TRADES_PER_PAIR=1 npm run autoMAX_SPREAD_PIPS=1.0 MAX_QUOTE_AGE_MS=3000 npm run autoNY_ASIAN_REQUIRE_H1_ALIGNMENT=false npm run backtest:ny-asianHistorical files are named:
history_<PAIR>.json
Examples:
history_EUR_USD.json
history_GBP_USD.json
history_USD_JPY.json
Default download uses configured TRADING_PAIRS, M5 granularity, and 30 days:
npm run downloadDownload specific pairs/days:
node download-history.js --pair EUR_USD,GBP_USD --days 180 --resume --chunk-size 2500Long EUR/USD helper:
npm run download:eurusd:3ynpm run backtestNY Asian profile:
npm run backtest:ny-asianMulti-pair NY Asian profile:
npm run backtest:ny-asian:multiLondon fake-break research/backtest profile:
npm run backtest:london-fake-break
npm run backtest:london-fake-break:multi
npm run backtest:london-fake-break:usdjpy
npm run backtest:london-fake-break:eurusd-usdjpy
npm run backtest:london-fake-break:usdjpy:braked
npm run backtest:london-fake-break:eurusd-usdjpy:braked
npm run backtest:london-fake-break:usdjpy:cleaned
npm run backtest:london-fake-break:eurusd-usdjpy:cleanedThe default London mode tests Candidate B from LONDON_FAKE_BREAK_IMPLEMENTATION_PLAN.md:
- London
07:00–10:00 UTC - Tue/Wed/Thu only
- 4-pip minimum Asian-range break
- close back inside within 2 M5 bars
- 5–10 pip risk band
- stop beyond fake-break extreme
- TP at the opposite side of the Asian range
- 12-bar time-exit fallback
Candidate A time-exit scripts are also available for comparison:
npm run backtest:london-fake-break:a:multi
npm run backtest:london-fake-break:a:usdjpy
npm run backtest:london-fake-break:a:eurusd-usdjpyCandidate A uses the strongest cross-pair research row:
- 2-pip minimum break
- close back inside within 3 M5 bars
- 5–10 pip risk band
- Tue/Wed/Thu only
- H1 reversal-aligned / break-counter-H1 filter
- time-exit target model
Both London profiles are not live-enabled yet.
Phase 5 monitor-only London observation can be started with:
npm run start:london-monitor:cleanedThis uses the cleaned research profile:
TRADING_PAIRS=EUR_USD,USD_JPY
SESSION_WINDOW_MODE=london_only
LONDON_MONITOR_ENABLED=true
LONDON_FAKE_BREAK_TRADE_END_UTC=9
LONDON_FAKE_BREAK_ALLOWED_WEEKDAYS=Wed
LONDON_MAX_LOSSES_PER_DAY=1
London live execution is still disabled unless LONDON_LIVE_EXECUTION_ENABLED=true is explicitly set; keep it false during Phase 5.
Current Phase 3 comparison read:
- Candidate B is stronger than Candidate A in full backtests.
- Best London-only result so far is Candidate B on
USD_JPYonly. - Candidate B on
EUR_USD,USD_JPYis positive butEUR_USDis much weaker thanUSD_JPY. - Candidate A time-exit did not survive full backtesting well and should stay research-only for now.
- If prioritizing total portfolio net, the current London candidate is Candidate B on
EUR_USD,USD_JPYwithLONDON_MAX_LOSSES_PER_DAY=1. - Diagnostics found
09:00–10:00 UTCandEUR_USDThursdays were damaging. The latest cleaned backtest also found Tuesday and Thursday weak overall, sobacktest:london-fake-break:eurusd-usdjpy:cleanednow keeps Candidate B onEUR_USD,USD_JPYwithLONDON_FAKE_BREAK_TRADE_END_UTC=9,LONDON_FAKE_BREAK_ALLOWED_WEEKDAYS=Wed, andLONDON_MAX_LOSSES_PER_DAY=1.
The backtester:
- loads local
history_*.jsonfiles, - builds H1 candles from M5 data for EMA200 filtering,
- simulates spread and slippage using
BACKTEST_SPREAD_PIPSandBACKTEST_SLIPPAGE_PIPS, - can replay live-style sizing with
BACKTEST_FIXED_BALANCE_USD, which keeps position sizing fixed instead of compounding wins/losses, - can use downloaded tick data with
BACKTEST_INTRABAR_MODE=tickto resolve SL/TP order inside an M5 candle, - applies KES daily risk gates,
- writes
trades_backtest.json.
Default simulation costs:
BACKTEST_SPREAD_PIPS=0.7
BACKTEST_SLIPPAGE_PIPS=0.3Live-replay sizing example, useful when comparing a specific real trade against the backtest:
BACKTEST_FIXED_BALANCE_USD=1191.99 BACKTEST_START_DATE=2026-07-17 BACKTEST_END_DATE=2026-07-18 npm run backtest:goldThis aligns position sizing and trade eligibility with a live account balance. Exact exits can still differ from live fills when both SL and TP are touched inside the same M5 candle, because the backtest does not have tick-level ordering.
Tick replay example for a specific gold trade window:
npm run download:ticks -- --pair XAU_USD --from 2026-07-17T12:40:00Z --to 2026-07-17T13:05:00Z
BACKTEST_START_DATE=2026-07-17 BACKTEST_END_DATE=2026-07-18 npm run backtest:gold:fixed:tickdownload:ticks writes daily gzip cache files under data/ticks/<PAIR>/<YYYY-MM-DD>.json.gz. By default, it also populates a local SQLite database at data/ticks.sqlite for faster multi-year querying.
Tick source configuration:
BACKTEST_TICK_SOURCE=sqlite # default, uses data/ticks.sqlite
BACKTEST_TICK_SOURCE=files # uses daily .json.gz cache filesThe tick cache must cover the backtest window you want to inspect; if tick data is missing, tick mode falls back to the normal M5 candle sequencing for that interval.
One-command accurate backtest workflow:
npm run backtest:accurate -- --preset gold --from 2020-01-01 --to 2020-01-08 --reportThis downloads the needed tick cache, validates tick coverage against local M5 history, then runs the backtest with BACKTEST_INTRABAR_MODE=tick and BACKTEST_TICK_MISSING=strict.
Long-range tick cache example for the main trading universe:
npm run backtest:accurate -- --preset all --from 2020-01-01 --to 2026-07-23Use BACKTEST_TICK_MISSING=strict when you want the backtest to fail instead of falling back to M5 candles whenever tick coverage is incomplete.
--to is exclusive, so use the next UTC day when downloading a complete final day.
Useful presets:
npm run backtest:accurate -- --preset gold --from 2020-01-01 --to 2020-02-01
npm run backtest:accurate -- --preset gold-fixed --from 2020-01-01 --to 2020-02-01 --fixed-balance 1191.99
npm run backtest:accurate -- --preset fx --from 2026-07-01 --to 2026-08-01
npm run backtest:accurate -- --preset fx-fixed --from 2026-07-01 --to 2026-08-01 --fixed-balance 1191.99
npm run backtest:accurate -- --preset all --from 2020-01-01 --to 2020-02-01The fx/fx-fixed presets run EUR_USD, GBP_USD, USD_JPY with STRATEGY_MODE=combined_ny_london and the same LONDON_* settings as the live ic-scalping-bot PM2 process (ecosystem.config.cjs), so the backtest mirrors what's actually running live rather than a plain NY Asian continuation test. ny is kept as a backward-compatible alias for fx.
npm run analyzeThis runs:
node trade-analyzer.js && node generate-report.jsOutputs:
- terminal daily performance and robustness summaries,
report.htmldashboard.
Open the generated report in your browser:
open report.htmlmarket-regime-analysis.js reads a local history file and prints exploratory regime/session statistics.
Example:
npm run research:regimes -- --pair EUR_USD --file history_EUR_USD.json --cost-pips 0.7Phase 1 London fake-break research uses the same runner with additional fake-break tables:
npm run research:london-fake-break -- --pair EUR_USD --file history_EUR_USD.json --cost-pips 0.7Useful research parameters:
npm run research:london-fake-break -- \
--pair GBP_USD \
--file history_GBP_USD.json \
--fake-break-min-break-pips 2 \
--fake-break-confirm-bars 2 \
--fake-break-lookahead-bars 12 \
--fake-break-stop-buffer-pips 0.5Filter-sweep mode ranks combinations before any strategy implementation:
npm run research:london-fake-break:sweep -- \
--pair EUR_USD \
--file history_EUR_USD.json \
--cost-pips 0.7 \
--min-obs 80 \
--sweep-top 20Sweep dimensions can be overridden with comma-separated values:
npm run research:london-fake-break:sweep -- \
--pair EUR_USD \
--file history_EUR_USD.json \
--sweep-min-break-pips 2,3,4,5 \
--sweep-confirm-bars 0,1,2,3 \
--sweep-risk-bands all,4-8,5-10,6-12,8-15 \
--sweep-weekdays all,TueWedThu,Mon,Tue,Wed,Thu,Fri \
--sweep-h1 all,break_with_h1,reversal_with_h1,break_counter_h1,reversal_counter_h1 \
--sweep-directions all,up,down \
--sweep-targets time_exit,asian_midpoint,rr_1_0,rr_1_2,asian_oppositeCompare sweep rows across multiple pairs and rank only filter sets that appear on every pair:
npm run research:london-fake-break:compare -- \
--pairs EUR_USD,GBP_USD,USD_JPY \
--cost-pips 0.7 \
--min-obs 80 \
--top 20Any --sweep-* option can be passed through to the comparison runner. For example, to compare only fixed/level-based target models:
npm run research:london-fake-break:compare -- \
--pairs EUR_USD,GBP_USD,USD_JPY \
--cost-pips 0.7 \
--min-obs 80 \
--top 10 \
--sweep-targets asian_midpoint,rr_1_0,rr_1_2,asian_oppositeCompare London Candidate B against the current NY module at day level:
npm run research:london-ny:correlationThis runs NY-only plus London USD_JPY and London EUR_USD,USD_JPY, writes outputs under analysis/, and simulates simple London module brakes. Current read:
- London
USD_JPYadds net profit with limited calendar overlap against NY. - London
EUR_USD,USD_JPYadds more net but needs a module brake because drawdown is materially higher. - The best tested London brake so far is stopping London after the first same-day London loss.
GBP_USDremains excluded for London Candidate B.- The latest cleaned backtest favors London Candidate B on
EUR_USD,USD_JPYwith07:00–09:00 UTC, Wednesday-only entries, andLONDON_MAX_LOSSES_PER_DAY=1. This is the current best research profile for London backtest profitability, but it remains monitor-only/backtest-only until the live-monitor phase is implemented.
London diagnostics by pair, weekday, hour, break direction, risk band, and exit reason:
npm run research:london:diagnosticsThis writes analysis/london_diagnostics.json.
Selected combined research profile:
npm run research:combined:selectedThis reruns the NY/London correlation analysis, extracts the selected row london_eurusd_usdjpy_cleaned + max_1_loss, and writes analysis/selected_combined_profile.md.
It also writes analysis/selected_combined_profile.json with combined trade-level metrics:
- combined win rate,
- combined profit factor,
- combined max consecutive losses,
- monthly distribution of combined profits.
ecosystem.config.cjs runs one forked process named ic-scalping-bot with --auto-execute and production-oriented strategy/risk environment overrides.
Start:
npm run pm2:startStatus/logs:
npm run pm2:status
npm run pm2:logsRestart/stop/delete:
npm run pm2:restart
npm run pm2:stop
npm run pm2:deletePM2 writes logs to:
logs/out.log
logs/err.log
Before PM2 auto-execution, verify .env is present in the working directory and run monitor-only mode successfully.
The Dockerfile uses Node 20, installs pnpm, installs dependencies from pnpm-lock.yaml, copies the project, and defaults to monitor mode:
docker build -t icmarkets-scalping-bot .Monitor mode:
docker run --rm --env-file .env icmarkets-scalping-botAuto-execute:
docker run --rm --env-file .env icmarkets-scalping-bot node index.js --auto-executeIf you want state files to persist outside the container, mount the project or a writable data directory and make sure state.json and risk-state.json are preserved between restarts.
Most values are configured in config.js; many can be overridden with environment variables.
| Variable | Required | Default | Used by |
|---|---|---|---|
CTRADER_CLIENT_ID |
Yes | empty | auth.js, icmarkets.js, get-symbols.js |
CTRADER_CLIENT_SECRET |
Yes | empty | auth.js, icmarkets.js |
CTRADER_ACCESS_TOKEN |
Yes for API calls | empty | icmarkets.js, get-symbols.js |
CTRADER_ACCOUNT_ID |
Yes for API calls | 0 |
icmarkets.js, get-symbols.js |
CTRADER_ENV |
No | demo |
Selects demo/live host |
| Variable | Default | Notes |
|---|---|---|
TRADING_PAIRS |
EUR_USD,GBP_USD,USD_JPY |
Comma-separated pair list. Must have symbol IDs in config.ctraderSymbolIds. |
STRATEGY_MODE |
ny_asian_continuation |
ny_asian_continuation is live-capable; combined_ny_london runs NY plus London monitor routing; london_asian_fake_break_reversal can be observed alone. |
SESSION_WINDOW_MODE |
all_windows |
One of ny_only, ny_quality, ny_trimmed, london_only, all_windows. |
NY_ASIAN_ALLOWED_SESSIONS |
ny_overlap |
Comma-separated session names allowed by the strategy. |
NY_ASIAN_START_UTC |
0 |
Asian range start. |
NY_ASIAN_END_UTC |
7 |
Asian range end. |
NY_ASIAN_TRADE_START_UTC |
12.5 |
Strategy trade window start. |
NY_ASIAN_TRADE_END_UTC |
15.5 |
Strategy trade window end. |
NY_ASIAN_PREFER_AFTER_UTC |
13.0 |
Blocks setups before this time. |
NY_ASIAN_FORCE_EXIT_UTC |
16.0 |
Force-close time. |
NY_ASIAN_REQUIRE_H1_ALIGNMENT |
true |
Requires H1 trend alignment. |
NY_ASIAN_ENTRY_BUFFER_PIPS |
0.5 |
Stop entry buffer. |
NY_ASIAN_STOP_BUFFER_PIPS |
0.5 |
Stop loss buffer. |
NY_ASIAN_MIN_BREAK_PIPS |
3.0 |
Minimum clean break beyond Asian range. |
NY_ASIAN_MIN_RISK_PIPS |
5 |
Minimum stop distance (pips). |
NY_ASIAN_MAX_RISK_PIPS |
12 |
Maximum stop distance (pips). |
NY_ASIAN_RR_RATIO |
1.2 |
Reward/risk ratio (TP = SL × 1.2). |
NY_ASIAN_PENDING_EXPIRY_BARS |
3 |
Pending stop expiry in M5 bars. |
NY_ASIAN_TIME_EXIT_BARS |
12 |
Trade time exit in bars. |
NY_ASIAN_MAX_TRADES_PER_SESSION |
1 |
Per-pair/per-session setup count. |
NY_ASIAN_LOOKBACK_CANDLES |
220 |
M5 lookback for live strategy context. |
LONDON_FAKE_BREAK_ALLOWED_SESSIONS |
london_open |
Session names allowed by London fake-break backtests. |
LONDON_FAKE_BREAK_ALLOWED_PAIRS |
empty | Optional London pair filter; npm run auto sets EUR_USD,USD_JPY. |
LONDON_FAKE_BREAK_ALLOWED_WEEKDAYS |
Tue,Wed,Thu |
Candidate B weekday filter. |
LONDON_FAKE_BREAK_EXCLUDED_PAIR_WEEKDAYS |
empty | Optional PAIR:Day exclusions, e.g. EUR_USD:Thu. |
LONDON_FAKE_BREAK_TRADE_START_UTC |
7.0 |
London fake-break trade window start. |
LONDON_FAKE_BREAK_TRADE_END_UTC |
10.0 |
London fake-break trade window end. |
LONDON_FAKE_BREAK_MIN_BREAK_PIPS |
4.0 |
Minimum Asian-range break for Candidate B. |
LONDON_FAKE_BREAK_CONFIRM_BARS |
2 |
Bars allowed to close back inside the Asian range. |
LONDON_FAKE_BREAK_MIN_RISK_PIPS |
5 |
Minimum stop distance. |
LONDON_FAKE_BREAK_MAX_RISK_PIPS |
10 |
Maximum stop distance. |
LONDON_FAKE_BREAK_TARGET_MODE |
asian_opposite |
Candidate B target model. |
LONDON_FAKE_BREAK_TIME_EXIT_BARS |
12 |
Time-exit fallback used by the backtester. |
LONDON_MAX_LOSSES_PER_DAY |
0 |
London module brake; 1 stops London after the first same-day London loss. |
LONDON_MAX_DAILY_LOSS_USD |
0 |
Optional London module daily USD loss brake; 0 disables. |
COOLDOWN_CANDLES_AFTER_LOSS |
1 |
Per-pair cooldown after SL. |
| Variable | Default | Notes |
|---|---|---|
USE_BROKER_STOP_ORDERS |
true |
Places cTrader STOP orders for stop-entry setups. |
FALLBACK_TO_LOCAL_STOPS |
false |
If broker stop placement fails, optionally simulate stop triggers locally. |
MAX_SPREAD_PIPS |
1.5 |
Live spread gate. |
MAX_QUOTE_AGE_MS |
5000 |
Reject stale quote snapshots. |
MAX_SLIPPAGE_PIPS |
0.5 |
cTrader market-order slippage value. |
DEBUG_ORDER_PAYLOAD |
false |
Prints raw order payloads. |
CTRADER_NON_TRADE_MIN_INTERVAL_MS |
750 |
Throttles non-trade requests. |
CTRADER_MAX_NON_TRADE_REQUESTS_PER_MINUTE |
40 |
Non-trade request cap. |
CTRADER_RATE_LIMIT_BACKOFF_MS |
30000 |
Backoff after cTrader rate-limit errors. |
| Variable | Default | Notes |
|---|---|---|
BACKTEST_SPREAD_PIPS |
0.7 |
Simulated spread. |
BACKTEST_SLIPPAGE_PIPS |
0.3 |
Simulated slippage. |
BACKTEST_FIXED_BALANCE_USD |
empty | Optional fixed USD balance for live-replay sizing; when set, trade P&L is still recorded but does not compound position size. |
BACKTEST_INTRABAR_MODE |
conservative |
Use tick to replay downloaded daily tick cache data inside each M5 candle. Missing ticks fall back to conservative M5 sequencing unless strict mode is enabled. |
BACKTEST_TICK_CACHE_DIR |
data/ticks |
Directory containing daily tick cache files created by download:ticks. |
BACKTEST_TICK_MISSING |
fallback |
Use strict to fail the backtest when tick data is missing instead of falling back to M5 sequencing. |
Gold uses different pip values than FX pairs. These are set via environment variables in PM2 or npm scripts:
| Variable | Gold Value | FX Default | Notes |
|---|---|---|---|
NY_ASIAN_MIN_BREAK_PIPS |
100 |
3.0 |
Minimum clean break beyond Asian range |
NY_ASIAN_ENTRY_BUFFER_PIPS |
10 |
0.5 |
Entry buffer pips |
NY_ASIAN_STOP_BUFFER_PIPS |
10 |
0.5 |
Stop loss buffer pips |
NY_ASIAN_MIN_RISK_PIPS |
200 |
5 |
Minimum stop distance |
NY_ASIAN_MAX_RISK_PIPS |
2000 |
12 |
Maximum stop distance |
NY_ASIAN_RR_RATIO |
3.0 |
1.2 |
Reward/risk ratio |
BACKTEST_SPREAD_PIPS |
40 |
0.7 |
Simulated spread |
BACKTEST_SLIPPAGE_PIPS |
5 |
0.3 |
Simulated slippage |
Quick reference:
- Gold: 200 pip SL → 600 pip TP (R:R 1:3)
- Gold: 400 pip SL → 1,200 pip TP (R:R 1:3)
- 1 pip = $0.01 for gold, so 400 pips = $4.00
Run the local test suite:
npm testCurrent tests check:
- risk-state loading/reset behavior,
- daily stop-loss behavior,
- package scripts point to existing files,
- stale removed strategy names do not reappear,
- key config defaults remain as expected.
Your .env is missing one or more required cTrader values. Confirm:
grep '^CTRADER_' .envDo not paste secrets into logs or issues.
Run:
npm run symbolsThen update config.js → ctraderSymbolIds.
Possible causes:
- market closed,
- invalid symbol ID,
- token/account mismatch,
- cTrader data unavailable for that range,
- rate limiting.
Try one pair first:
TRADING_PAIRS=EUR_USD npm starticmarkets.js has heartbeat, ping keepalive, health monitoring, and reconnect logic. If repeated reconnects happen, check network stability, credentials, cTrader environment, and whether the API is rate-limiting requests.
Check the log reason. Common valid blockers:
- outside UTC session windows,
- weekend,
- no valid Asian range yet,
- before preferred NY time,
- H1 trend neutral or opposite direction,
- risk too small/large,
- max trades reached,
- daily stop/profit gate hit,
- no fresh quote,
- spread above
MAX_SPREAD_PIPS.
Before using --auto-execute:
-
.envusesCTRADER_ENV=demo. -
npm testpasses. -
npm startconnects and runs without credential/symbol errors. -
TRADING_PAIRScontains only pairs you intend to trade. -
config.ctraderSymbolIdsmatches your cTrader account. -
RISK_PER_TRADE_PERCENT, daily gates, and max-trade caps are acceptable. - You understand that
npm run auto, PM2, and Docker auto-execute commands can place real orders ifCTRADER_ENV=liveand live account credentials are used.