Skip to content

IG Markets SDK ​

Complete reference for ctx.ig — programmatic CFD, forex, indices, commodities, and equities trading on IG Markets from worker code.

IG REST + Lightstreamer

This SDK wraps the IG REST Trading API for orders and account data, plus the Lightstreamer streaming API (NOT WebSocket) for real-time prices and charts. Server-only.

Setup ​

  1. Add an IG Markets trading block to your workspace canvas.
  2. Open the inspector → API Keys → pick a key (Settings → API Keys → IG Markets to add new). IG keys consist of API Key + Username (identifier) + Password + a Demo toggle.
  3. Connect the block to your Worker block via an edge.
  4. ctx.ig is now available.

Demo vs Live

IG separates demo and live accounts at the API key level. The block reads is_demo from your stored credentials and routes to demo-api.ig.com or api.ig.com automatically. No per-call override.

python
def tick(ctx):
    accounts = ctx.ig.get_accounts()
    ctx.log.info(f"Accounts: {len(accounts.get('accounts', []))}")

If no IG block is connected:

RuntimeError: No trading block connected. Connect an IG Markets block to this worker.

Instrument Naming (Epics) ​

IG calls instruments epics — opaque codes like CS.D.EURUSD.TODAY.IP. There are tens of thousands. Two ways to discover them:

MethodUse when
search_marketsYou know a name fragment (e.g. "Gold", "FTSE")
get_market_navigationYou want to browse the hierarchy (Indices > UK > FTSE 100)

Once you have an epic, get_market_info gives full instrument details (tick size, dealing rules, margin requirement).

Account & History ​

get_accounts ​

All accounts on this login (live + demo per session, multi-account users get one row per account).

python
ctx.ig.get_accounts() -> dict

get_account_activity ​

Recent trade / order activity (last 6 months, paginated).

python
ctx.ig.get_account_activity(
    from_date: str,                   # ISO 8601: "2026-01-01T00:00:00"
    to_date: str = None,
    detailed: bool = False,
    deal_id: str = None,
    filter: str = None,               # FIQL filter expression
) -> dict
python
import time
since = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(time.time() - 86400 * 7))
acts = ctx.ig.get_account_activity(from_date=since, detailed=True)

get_account_history ​

Account transaction history: deposits, withdrawals, dividends, fees, interest.

python
ctx.ig.get_account_history(
    from_date: str,
    to_date: str = None,
    type: str = None,                 # "ALL" | "DEPOSIT" | "WITHDRAWAL"
) -> dict

Market Discovery ​

search_markets ​

Search for an epic by name fragment.

python
ctx.ig.search_markets(search_term: str) -> dict
python
results = ctx.ig.search_markets("Gold")
# → {"markets": [{"epic": "CS.D.CFDGOLD.CFD.IP", "instrumentName": "Spot Gold", ...}]}

get_market_info ​

Detailed info for one epic: tick size, lot size, margin requirements, dealing rules, current prices.

python
ctx.ig.get_market_info(epic: str) -> dict

get_market_navigation ​

Browse the market hierarchy.

python
ctx.ig.get_market_navigation(node_id: str = None) -> dict
python
# Root: list of categories (Indices, Forex, Commodities, ...)
root = ctx.ig.get_market_navigation()

# Drill: list contents of one category by id
indices = ctx.ig.get_market_navigation(node_id="184733")

get_historical_prices ​

OHLC bars + volume for an epic.

python
ctx.ig.get_historical_prices(
    epic: str,
    resolution: str = "HOUR",
    max_points: int = 100,
    from_date: str = None,
    to_date: str = None,
) -> dict
resolutionValues
Sub-minute"SECOND"
Minutes"MINUTE", "MINUTE_2", "MINUTE_3", "MINUTE_5", "MINUTE_10", "MINUTE_15", "MINUTE_30"
Hours"HOUR", "HOUR_2", "HOUR_3", "HOUR_4"
Days+"DAY", "WEEK", "MONTH"
python
bars = ctx.ig.get_historical_prices("CS.D.EURUSD.TODAY.IP",
                                     resolution="HOUR", max_points=24)
closes = [p["closePrice"]["bid"] for p in bars["prices"]]

Quota

IG enforces a strict quarterly historical-data quota — see bars["allowance"]. Don't burn through it polling every tick.

get_client_sentiment ​

IG's "% of clients are long / short" indicator — a contrarian-friendly signal.

python
ctx.ig.get_client_sentiment(market_id: str = None) -> dict

TIP

market_id is not the same as epic. It's an IG-internal id you find under get_market_info(epic)["snapshot"]["marketId"] or by browsing.

python
s = ctx.ig.get_client_sentiment(market_id="EURUSD")
ctx.log.info(f"Long: {s['longPositionPercentage']}%")

Streaming (Lightstreamer) ​

These methods read from the long-lived Lightstreamer stream that the IG worker maintains in the background. They return cached snapshots; no REST calls.

get_streaming_market ​

Latest snapshot for one epic (bid, offer, change, MID).

python
ctx.ig.get_streaming_market(epic: str) -> dict | None

get_streaming_candles ​

Live-updating candle history (5-minute or 1-minute resolution).

python
ctx.ig.get_streaming_candles(epic: str, scale: str = "1MINUTE") -> list[dict]
# scale: "1SECOND" | "1MINUTE" | "5MINUTE" | "1HOUR"

get_streaming_ticks ​

Recent tick stream (per-trade granularity).

python
ctx.ig.get_streaming_ticks(epic: str) -> list[dict]

get_trade_confirms ​

Recent trade confirmations from the streaming connection.

python
ctx.ig.get_trade_confirms() -> list[dict]

add_streaming_epics ​

Subscribe to additional epics on the running stream.

python
ctx.ig.add_streaming_epics(epics: list[str])

Lightstreamer not WebSocket

IG uses Lightstreamer (a streaming protocol layered over HTTP) — not WebSocket. The adapter handles the protocol details for you; just subscribe and read the cache.

PRO+ workers maintain the stream automatically. Free-plan workers don't have streaming; methods return None / [].

Positions ​

get_positions ​

All open positions.

python
ctx.ig.get_positions() -> dict

open_position ​

Open a market or limit position.

python
ctx.ig.open_position(
    epic: str,
    direction: str,                # "BUY" | "SELL"
    size: float,
    order_type: str = "MARKET",
    currency_code: str = "USD",
    expiry: str = "DFB",
    force_open: bool = True,
    guaranteed_stop: bool = False,
    stop_distance: float = None,
    limit_distance: float = None,
    stop_level: float = None,
    limit_level: float = None,
) -> dict
python
# Market BUY 1 unit of EUR/USD with 50-point SL / 100-point TP
r = ctx.ig.open_position(
    epic="CS.D.EURUSD.TODAY.IP",
    direction="BUY", size=1.0,
    stop_distance=50.0,
    limit_distance=100.0,
)
deal_ref = r.get("dealReference")

# Get the actual dealId via confirmation
conf = ctx.ig.confirm_deal(deal_reference=deal_ref)
deal_id = conf.get("dealId")

Two-step deals

IG's API responds with a dealReference immediately, but the actual dealId only comes back from confirm_deal(dealReference). Always confirm before using a dealId.

close_position ​

Close (fully or partially) by dealId.

python
ctx.ig.close_position(
    deal_id: str,
    direction: str,               # opposite of open direction
    size: float,
    order_type: str = "MARKET",
) -> dict

update_position ​

Update SL / TP on an open position.

python
ctx.ig.update_position(
    deal_id: str,
    stop_level: float = None,
    limit_level: float = None,
) -> dict

confirm_deal ​

Get full details of a deal by dealReference.

python
ctx.ig.confirm_deal(deal_reference: str) -> dict

Working (Pending) Orders ​

get_orders ​

All working (unfilled) orders.

python
ctx.ig.get_orders() -> dict

create_working_order ​

Place a pending limit or stop order.

python
ctx.ig.create_working_order(
    epic: str,
    direction: str,                # "BUY" | "SELL"
    size: float,
    level: float,                  # the trigger price
    order_type: str = "LIMIT",     # "LIMIT" | "STOP"
    time_in_force: str = "GOOD_TILL_CANCELLED",  # or "GOOD_TILL_DATE"
    good_till_date: str = None,
    currency_code: str = "USD",
    expiry: str = "DFB",
    force_open: bool = True,
    guaranteed_stop: bool = False,
    stop_distance: float = None,
    limit_distance: float = None,
    stop_level: float = None,
    limit_level: float = None,
) -> dict

update_working_order ​

Modify a pending order's level, SL, TP, or expiry.

python
ctx.ig.update_working_order(
    deal_id: str,
    level: float = None,
    stop_distance: float = None,
    limit_distance: float = None,
    stop_level: float = None,
    limit_level: float = None,
    time_in_force: str = None,
    good_till_date: str = None,
    type: str = None,
) -> dict

delete_working_order ​

Cancel a pending order.

python
ctx.ig.delete_working_order(deal_id: str) -> dict

Common Patterns ​

Demo during development ​

Set the API-key is_demo: true in Settings → API Keys → IG Markets. All worker calls auto-route to demo-api.ig.com. IG's demo accounts come pre-funded with virtual money.

Error handling ​

python
def tick(ctx):
    try:
        positions = ctx.ig.get_positions()
    except Exception as e:
        ctx.log.error(f"IG unreachable: {e}")
        return

IGAPIError carries status and msg attributes. Network failures raise httpx.HTTPStatusError.

Cloud-Run proxy ​

PRO+ workers route IG REST traffic through the Cloud Run proxy with rotating exit IPs. Lightstreamer connects directly (it's a long-lived TCP connection, not pooled).

Rate limits ​

IG enforces 60 req/min on the trading API. Repeated 403 responses with exceeded-api-key-allowance invalidate your session for 1-2 minutes. The adapter caches the session for 55 minutes — re-using the same session avoids the per-login rate limit.

Recipes ​

Mean-reversion on EUR/USD with bracket exits ​

python
def tick(ctx):
    bars = ctx.ig.get_historical_prices(
        "CS.D.EURUSD.TODAY.IP",
        resolution="MINUTE_15", max_points=20,
    )
    closes = [(p["closePrice"]["bid"] + p["closePrice"]["ask"]) / 2
              for p in bars["prices"]]
    sma = sum(closes) / len(closes)
    last = closes[-1]
    z = (last - sma) / (sma * 0.001)   # rough z-score in pips

    pos = ctx.ig.get_positions()
    has_pos = any(p["market"]["epic"] == "CS.D.EURUSD.TODAY.IP"
                  for p in pos.get("positions", []))

    if z < -2 and not has_pos:
        # Cheap — buy with a 30-pt stop, 60-pt limit (1:2 R:R)
        ctx.ig.open_position(
            epic="CS.D.EURUSD.TODAY.IP", direction="BUY", size=1.0,
            stop_distance=30.0, limit_distance=60.0,
        )
        ctx.log.info(f"Long entry @ {last:.5f}, z={z:.2f}")

Contrarian on extreme client sentiment ​

python
def tick(ctx):
    s = ctx.ig.get_client_sentiment(market_id="EURUSD")
    long_pct = float(s.get("longPositionPercentage", 50))

    pos = ctx.ig.get_positions()
    has_pos = any(p["market"]["epic"].startswith("CS.D.EURUSD")
                  for p in pos.get("positions", []))

    # When >75% of retail are long, fade them
    if long_pct > 75 and not has_pos:
        ctx.ig.open_position(
            epic="CS.D.EURUSD.TODAY.IP", direction="SELL", size=1.0,
            stop_distance=40.0, limit_distance=80.0,
        )
        ctx.log.info(f"Fading {long_pct}% long crowd")

Resting order ladder around current price ​

python
def setup(ctx):
    info = ctx.ig.get_market_info("CS.D.EURUSD.TODAY.IP")
    px = float(info["snapshot"]["bid"])

    # Cancel any leftover working orders first
    open_orders = ctx.ig.get_orders().get("workingOrders", [])
    for o in open_orders:
        if o["marketData"]["epic"] == "CS.D.EURUSD.TODAY.IP":
            ctx.ig.delete_working_order(deal_id=o["workingOrderData"]["dealId"])

    # Lay down a 5-rung buy ladder, 5 pips apart
    for i in range(1, 6):
        ctx.ig.create_working_order(
            epic="CS.D.EURUSD.TODAY.IP",
            direction="BUY", size=1.0,
            level=round(px - i * 0.0005, 5),
            order_type="LIMIT",
            stop_distance=20.0, limit_distance=40.0,
        )
    ctx.log.info(f"Buy ladder placed below {px:.5f}")

Reference ​

IG endpointSDK method
GET /accountsget_accounts
GET /history/activityget_account_activity
GET /history/transactionsget_account_history
GET /markets?searchTerm=search_markets
GET /markets/{epic}get_market_info
GET /marketnavigationget_market_navigation
GET /prices/{epic}get_historical_prices
GET /clientsentimentget_client_sentiment
GET /positionsget_positions
POST /positions/otcopen_position
DELETE /positions/otc (body)close_position
PUT /positions/otc/{dealId}update_position
GET /confirms/{ref}confirm_deal
GET /workingordersget_orders
POST /workingorders/otccreate_working_order
PUT /workingorders/otc/{dealId}update_working_order
DELETE /workingorders/otc/{dealId}delete_working_order
Lightstreamer: MARKET:{epic}get_streaming_market
Lightstreamer: CHART:{epic}:{scale}get_streaming_candles
Lightstreamer: CHART:{epic}:TICKget_streaming_ticks
Lightstreamer: TRADE:{accountId}get_trade_confirms

NodeGraph Documentation