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

AiSpinner Documentation