MetaTrader 5 from TypeScript: Connect, Stream, and Trade with @tickerall/sdk
A TypeScript quickstart for the TickerAll MetaTrader 5 API — connect a broker account, stream live ticks over a WebSocket, place a test order on a demo account, and read positions back, all with @tickerall/sdk and no terminal in the path.
Most MetaTrader 5 tutorials are Python. If your stack is Node — a trading bot, a dashboard backend, an alerting service — you've probably had to translate as you read. This one is TypeScript from top to bottom: install the SDK, connect a broker account, stream ticks over a WebSocket, place a test order on a demo account, and read the position back. No desktop terminal, no Wine, no bridge — your Node code talks HTTPS to a hosted API that holds the broker session for you.
Install
npm install @tickerall/sdk
Requires Node 18+ (the SDK uses the built-in global fetch) and has no native
dependencies. It ships types, so everything below is fully typed. Prefer Python?
The place-your-first-trade walkthrough
covers the same ground with the tickerall package.
Create a client
One client object holds your API key and exposes every namespace — sessions,
accounts, orders, positions, and the stream. Grab a key (cf_live_...) from your
TickerAll dashboard and keep it in an env var, never in source.
import { Tickerall } from '@tickerall/sdk'
const client = new Tickerall({ apiKey: process.env.TICKERALL_API_KEY! })
Do your first trade on a demo account — and check it
Verify the account is a demo before you place anything. The API will place against a funded live account just as happily if that's what your session points at. A test order sized for play money becomes a real position with real money behind it.
sessions.startreturns anisDemoflag — read it, and make your first trades conditional on it.
Connect a broker account
A session is the hosted side holding a logged-in connection to your broker. You
hand it credentials once and get back an accountId — the handle every later call
takes as its first argument.
const session = await client.sessions.start({
broker: 'mt5',
server: 'Exness-MT5Trial14', // a demo / trial server
account: 12345678, // your demo login
password: process.env.MT5_PASSWORD!,
})
const accountId = session.accountId
if (!session.isDemo) {
throw new Error('Refusing to run the demo walkthrough against a live account')
}
Read the account back
accounts.get returns a discriminated union: an online account carries its
account financials and positions; an offline one carries a hint instead.
Narrow on status and TypeScript hands you the right fields with no casts.
const detail = await client.accounts.get(accountId)
if (detail.status === 'offline') {
console.log('account is cold:', detail.hint)
} else {
// TS now knows `detail` is the online variant
console.log('balance', detail.account?.balance, detail.account?.currency)
console.log('equity', detail.account?.equity, 'free margin', detail.account?.freeMargin)
console.log('open positions:', detail.positions.length)
}
Stream live ticks over a WebSocket
Prices are a firehose — the right tool is a push socket, not a poll loop. Connect once, register a handler, and subscribe to the symbols you care about. One socket carries every symbol.
import type { TickEvent, PositionEvent } from '@tickerall/sdk'
const stream = await client.stream.connect()
stream.on('tick', (t: TickEvent) => {
console.log(t.symbol, t.bid, t.ask, t.timestamp)
})
// Position opens / updates / closes on the same socket
stream.on('position', (e: PositionEvent) => {
console.log(e.event, e.position.ticket, 'P/L', e.position.profit)
})
// Reconnects are automatic; this is just observability
stream.on('reconnect', ({ attempt }) => console.log('stream reconnecting…', attempt))
stream.on('error', (err) => console.error('stream error', err.message))
await stream.subscribeTicks(accountId, ['BTCUSD', 'EURUSD', 'XAUUSD'])
await stream.subscribePositions(accountId)
Two things worth knowing up front:
- Ticks are irregular. Markets deliver them in bursts — a flurry on a news spike, then quiet. A dry second means nothing moved, not that the feed broke. Never build logic that expects a tick every N milliseconds.
- An open socket keeps Node alive on its own. Unlike a Python
while Trueloop, you don't need a keep-alive here — the live WebSocket handle refs the event loop. Just close it cleanly when you're done (below).
Need a one-shot price instead of a handler? waitForTick resolves with the next
tick (subscribing the symbol first if you pass an accountId):
const px = await stream.waitForTick('BTCUSD', { accountId, timeoutMs: 5000 })
console.log('BTCUSD is', px.bid, '/', px.ask)
Place a test order
We'll buy 0.10 lots of BTCUSD at market with a stop-loss and take-profit
attached, so risk is bounded the instant the position opens.
const order = await client.orders.place(accountId, {
type: 'market',
symbol: 'BTCUSD',
side: 'BUY',
volume: 0.10,
stopLoss: 58000,
takeProfit: 72000,
})
console.log(order.ticket, order.status) // e.g. 4072808150 'open'
You get back a ticket (the broker's id for the order/position) and a status
('open' for a filled market order, 'pending' for a resting limit/stop).
type: 'limit' or 'stop' would be a pending order and would also require a
price.
Read it, modify it, close it
The order created a position. Read the account again to find it, tighten the stop if you like, then close — the mirror of opening, handing back the position ticket.
const after = await client.accounts.get(accountId)
if (after.status === 'online') {
for (const p of after.positions) {
console.log(p.ticket, p.symbol, p.side, p.volume, 'P/L', p.profit)
}
}
// Tighten risk after the fact
await client.positions.modify(accountId, order.ticket, { stopLoss: 60000 })
// Close it — full close, or pass a smaller volume to scale out
await client.positions.close(accountId, order.ticket)
// await client.positions.close(accountId, order.ticket, { volume: 0.05 })
Handle errors by type
The SDK throws typed errors, so you can branch on what actually went wrong instead
of parsing messages. The one distinction that matters for retries:
TickerallServiceUnavailableError carries transient: true (a connectivity blip —
safe to retry), while a validation or broker error means the request itself was
wrong or refused, so retrying sends the same bad request.
import {
TickerallServiceUnavailableError,
TickerallValidationError,
TickerallBrokerError,
TickerallAuthError,
} from '@tickerall/sdk'
try {
await client.orders.place(accountId, {
type: 'market', symbol: 'BTCUSD', side: 'BUY', volume: 0.10,
})
} catch (err) {
if (err instanceof TickerallServiceUnavailableError && err.transient) {
// TickerAll momentarily unreachable — safe to retry.
// Every state-changing call carries an Idempotency-Key, so a retry
// of the same intent can't double-execute.
} else if (err instanceof TickerallValidationError) {
// Bad field — wrong symbol, size below the broker's minimum, …
} else if (err instanceof TickerallBrokerError) {
// The broker refused it — market closed, not enough margin, …
} else if (err instanceof TickerallAuthError) {
// Key or session problem.
} else {
throw err
}
}
The whole thing end to end
import { Tickerall } from '@tickerall/sdk'
import type { TickEvent } from '@tickerall/sdk'
async function main() {
const client = new Tickerall({ apiKey: process.env.TICKERALL_API_KEY! })
const session = await client.sessions.start({
broker: 'mt5',
server: 'Exness-MT5Trial14',
account: 12345678,
password: process.env.MT5_PASSWORD!,
})
const accountId = session.accountId
if (!session.isDemo) throw new Error('Not a demo account — stopping')
const stream = await client.stream.connect()
stream.on('tick', (t: TickEvent) => console.log(t.symbol, t.bid, t.ask))
await stream.subscribeTicks(accountId, ['BTCUSD'])
const order = await client.orders.place(accountId, {
type: 'market', symbol: 'BTCUSD', side: 'BUY', volume: 0.10,
stopLoss: 58000, takeProfit: 72000,
})
console.log('opened', order.ticket, order.status)
await client.positions.close(accountId, order.ticket)
console.log('closed', order.ticket)
// Clean shutdown: drop the socket, then end the broker session.
await stream.close()
await client.sessions.end(accountId)
}
main().catch((err) => {
console.error(err)
process.exit(1)
})
That's the full loop in one file: session up, ticks streaming, an order opened with bounded risk, read back, closed, socket and session down — all typed, all from Node.
Where to go next
Running the same strategy across several accounts? Fan one call out to all of them with the Bulk API, or mirror a master account to scaled followers with Copy Trading.
Full API reference: tickerall.com/docs