๐Ÿ“ˆ Strategy & Systems

IB Python Momentum Strategy: Live Trading Code

โš ๏ธ Disclosure: Some links on this page are affiliate links. If you sign up through them, I may earn a commission โ€” at no extra cost to you. I only review tools I actually use.
Interactive Brokers โ€” institutional-grade API for systematic forex trading
Interactive Brokers โ€” institutional-grade API for systematic forex trading

Why Interactive Brokers for Systematic Forex Trading?

Before diving into the code, let me address the obvious question: why IBKR?

1. API access is free. Unlike some brokers who charge for data feeds or API tiers, IBKR includes TWS API access with any funded account.

2. Forex spreads are competitive. For USDJPY in particular, IBKR's interbank-style pricing is noticeably better than most retail FX brokers. 3. The Python library is mature. ibapi has been around long enough that most weird edge cases have Stack Overflow answers. The newer ib_insync wrapper is excellent for async workflows. 4. Real account data is the same API as paper trading. You can develop against a paper account and flip one config line to go live. No environment parity surprises.

If you're evaluating brokers for algo trading, Interactive Brokers currently offers up to $1,000 in IBKR stock to new referral signups.

The Strategy Architecture

My strategy is a monthly momentum + seasonality model on USDJPY. The logic is simple in concept but the implementation has a lot of moving parts:

The seasonal component is specific to JPY carry dynamics: Simple? Yes. But "simple" is a feature, not a bug, for a production system you'll be running unattended.

Setting Up the IBKR Python Environment

You'll need:

pip install ib_insync pandas numpy

Note: The official ibapi package is distributed as source code from IBKR's website. ib_insync wraps it with asyncio and is far easier to work with in production.

Connecting to TWS / IB Gateway

from ib_insync import *

ib = IB()
ib.connect('127.0.0.1', 7497, clientId=1)
# 7497 = TWS paper trading port
# 7496 = TWS live trading port
# 4002 = IB Gateway live port
# 4001 = IB Gateway paper port

The production gotcha: TWS disconnects you after 24 hours by default. Go to Global Configuration โ†’ API โ†’ Settings and set the auto-logoff time carefully. For always-on strategies, running IB Gateway as a systemd service (lighter than full TWS) is the better approach.

Fetching Historical Data for Momentum Calculation

async def get_usdjpy_history(ib: IB, days: int = 70) -> pd.DataFrame:
    contract = Forex('USDJPY')
    bars = await ib.reqHistoricalDataAsync(
        contract,
        endDateTime='',
        durationStr=f'{days} D',
        barSizeSetting='1 day',
        whatToShow='MIDPOINT',
        useRTH=True,
        formatDate=1
    )
    df = util.df(bars)[['date', 'open', 'high', 'low', 'close', 'volume']].copy()
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)
    return df

def calculate_momentum(df: pd.DataFrame, lookback: int = 60) -> float:
    current_price = df['close'].iloc[-1]
    past_price = df['close'].iloc[-lookback]
    return (current_price - past_price) / past_price

One nuance: IBKR's reqHistoricalData returns unadjusted midpoint for Forex pairs (unlike adjusted stock data). For FX momentum this is correct โ€” no adjustment needed.

The Seasonality Filter

SEASONAL_MAP = {
    2: "LONG",   # February โ€” fiscal year carry unwind
    6: "LONG",   # June โ€” H1 close carry positioning
    7: "SHORT",  # July โ€” summer carry unwind
    10: "LONG",  # October โ€” Q3 institutional rebalancing
}

def get_signal(momentum: float, rate_diff_declining: bool = False) -> str:
    month = date.today().month
    bias = SEASONAL_MAP.get(month, "NEUTRAL")
    
    if bias == "LONG" and momentum > 0:
        return "LONG"
    elif bias == "SHORT" and momentum < 0 and rate_diff_declining:
        return "SHORT"
    else:
        return "HOLD"

The rate_diff_declining parameter pulls 2Y US Treasury vs JGB yield data from FRED daily. This guards the July short signal against firing when the rate differential is still widening (which would suppress yen appreciation).

Production Order Execution

async def place_order(ib: IB, action: str, quantity: float, account: str) -> Trade:
    contract = Forex('USDJPY')
    await ib.qualifyContractsAsync(contract)
    
    order = MarketOrder(action=action, totalQuantity=quantity, tif='DAY', account=account)
    trade = ib.placeOrder(contract, order)
    
    timeout, elapsed = 30, 0
    while not trade.isDone() and elapsed < timeout:
        await asyncio.sleep(1)
        elapsed += 1
    
    if not trade.isDone():
        ib.cancelOrder(order)
        raise TimeoutError(f"Order not filled within {timeout}s")
    
    return trade

async def close_existing_position(ib: IB, account: str) -> None:
    """Close any open USDJPY position before entering new one"""
    for pos in await ib.reqPositionsAsync():
        if pos.contract.symbol == 'USD' and pos.contract.currency == 'JPY' and pos.position != 0:
            action = 'SELL' if pos.position > 0 else 'BUY'
            await place_order(ib, action, abs(pos.position), account)

๐Ÿ’ก Interactive Brokers

Like what you're reading? Try it yourself โ€” this link supports ChartedTrader at no cost to you.

Open an IBKR Account โ€” Earn up to $1,000 in IBKR Stock โ†’

Lesson learned the hard way: Always close existing positions before entering a new signal via API. A reconnection bug during development caused the daemon to lose track of an open position โ€” without this guard, the next signal would have doubled up. The API is the source of truth, not local state.

What's Actually Hard in Production

After running since December 2024, here are the real friction points no tutorial mentions:

1. IB Gateway requires 2FA on every restart. Fully unattended startup isn't possible without mobile app approval. Practical workaround: keep sessions alive and minimize restarts. 2. Historical data has pacing limits. Hit too many requests and you get:

Error 162: Historical data request pacing violation

Fix: cache history in SQLite and only fetch incremental bars each cycle.

3. reqAccountValues() returns hundreds of fields. Always filter explicitly by tag, currency, and account == 'All' to get the number you actually want. 4. Market order slippage is real. For a slow monthly momentum signal it's minor (0.1โ€“0.5 pips). For faster strategies, use limit orders with a small offset from mid.

Key API Reference

OperationMethodNotes
Connectib.connect(host, port, clientId)7497=TWS paper, 4001=Gateway paper
Historical dataib.reqHistoricalData()Cache results; pacing limits apply
Place orderib.placeOrder(contract, order)Returns Trade object
Check positionsib.positions()Use API as source of truth
Account valuesib.accountValues()Filter by tag + currency
Cancel orderib.cancelOrder(order)Pass the original Order object

Results

I started this strategy in December 2024 with SGD 200 per signal. The system has run through multiple signal cycles including live through JPY volatility in early 2025. The infrastructure is more reliable than expected โ€” once IB Gateway is up and the daemon is configured, it genuinely just runs. Interventions have been limited to 2FA logins on gateway restart and one config tweak when IBKR updated their port handling.

This isn't a get-rich-fast strategy. It captures multi-week moves driven by carry dynamics and seasonal flows. The monthly stop-loss at -3% keeps losses bounded.

> ๐Ÿ’ก Did you know? Interactive Brokers pays $200 cash for each friend you refer, and your friend gets up to $1,000 in IBKR stock. Learn how the IBKR referral program works โ†’

Getting Started

If you don't have an IBKR account yet, Interactive Brokers has a paper trading environment functionally identical to live. New funded accounts via referral can receive up to $1,000 in IBKR stock.

Quick setup path:

1. Open account โ†’ enable paper trading

2. Install IB Gateway 3. Enable API: Edit โ†’ Global Configuration โ†’ API โ†’ Settings 4. pip install ib_insync 5. Test against paper port 7497 6. Run on paper for 30 days before going live

*Risk disclaimer: This article describes a live trading system for informational and educational purposes only. All trading involves risk of loss. Past performance is not indicative of future results. The SGD 200/signal allocation is specific to this personal experiment, not a sizing recommendation. Forex trading carries significant risk including potential loss of your entire invested capital. Always trade within your risk tolerance.*

> New to IBKR? Open an account through our link and get up to $1,000 in free IBKR stock (NASDAQ: IBKR). No catch โ€” it is part of their official referral program.

---

Just Signed Up? Here's What to Do Next

Interactive Brokers: Follow our IBKR setup guide โ€” application, funding (you can get up to $1,000 in IBKR stock), and your first trade.

---

Interactive Brokers

Ready to get started? Use the link below โ€” it helps support ChartedTrader at no cost to you.

Open an IBKR Account โ€” Earn up to $1,000 in IBKR Stock โ†’
๐Ÿ“ˆ

About the author

I'm a systematic trader running live strategies on IB (USDJPY momentum) and Hyperliquid (crypto perps). Every tool reviewed here is something I've used with real capital. Questions? Reach out.

๐Ÿ“š Related Articles

๐Ÿ“ˆ Strategy & Systems

Hyperliquid Latency: How Tokyo AWS Servers Give Traders a 200ms Edge (And How to Optimize Your Speed in 2026)

All 24 Hyperliquid validators sit in AWS Tokyo, giving nearby traders a 200ms latency edge. Learn how to reduce your order fill time with a Tokyo VPS, non-validating node, and six practical optimizations.

April 4, 2026 โฑ 11 min read
๐Ÿ“ˆ Strategy & Systems

Hyperliquid Net Deflation Explained: HYPE Buybacks Now Exceed Staking Rewards (2026)

On March 27, Hyperliquid hit net deflation for the first time โ€” buybacks removed 34,495 HYPE while only 26,784 were distributed. Here's what this structural shift means for HYPE supply, price, and your trading strategy.

April 1, 2026 โฑ 13 min read
๐Ÿ“ˆ Strategy & Systems

TradingView Pine Script: Footprint Order Flow Strategies โ€” Detect Institutional Buying With Code (2026)

Learn 4 proven order flow strategies using Pine Script's request.footprint() โ€” delta divergence, POC pullbacks, imbalance clusters, and Value Area fades. Full copy-paste code for each strategy.

March 23, 2026 โฑ 17 min read

๐Ÿ“ฌ Get weekly trading insights

Real trades, honest reviews, no fluff. One email per week.