TICKERALL!
← All posts
August 26, 20264 min read

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.

MetaTrader 5TypeScriptREST APIWebSocket

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.

Your Node code @tickerall/sdk Hosted API holds your session Broker MT5 account
REST for requests, a WebSocket for pushed data. The terminal runs on the API's side, so this is identical on a laptop, a Linux server, or in the cloud.

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.start returns an isDemo flag — 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 True loop, 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

Was this useful?

Comments

Leave a comment

Comments are public.
MetaTrader 5 from TypeScript: Connect, Stream, and Trade with @tickerall/sdk · Ticker All!