My options trading app was slow, disconnected randomly, and silently dropped orders after reconnects.
I'd been wrestling with the IB TWS API for weeks. The code "worked" — until it didn't. Reconnections failed at 2 AM. Market data subscriptions leaked. Order state went stale after network blips.
Then I built a Claude Code skill in 20 minutes. It found 3 critical anti-patterns and 5 additional issues I hadn't even considered. The app is now 5x faster and hasn't dropped a connection in production.
Here's exactly how it happened.
Step 1: "Do We Have a TWS Skill?"
First thing I did was ask Claude Code: do we have an IB TWS skill?
No public skills existed. There were a couple of MCP servers for Interactive Brokers, but MCPs are the wrong abstraction here. An MCP gives Claude the ability to call an API — I didn't need Claude to trade for me. I needed Claude to understand TWS API best practices while reviewing my code.
Skills are knowledge. MCPs are tools. That distinction matters.
Step 2: Build the Skill in 20 Minutes
Claude Code has a built-in skill builder. I pointed it at the ib_async documentation, the IBKR API reference, and a few hard-won Stack Overflow threads about common pitfalls.
The skill builder digested everything and produced a compact skill file — 512 tokens. That's it. Smaller than this section of the blog post.
What those 512 tokens encode:
- The correct
ib_asyncconnection lifecycle (connect, disconnect, reconnect patterns) - Market data subscription rules (the 100-ticker limit, proper cleanup)
- Order management after reconnection (the
reqOpenOrders()requirement) - Error code semantics (which codes are fatal, which are transient)
- Async patterns that work vs. patterns that block the event loop
I installed it to my project with a single command. Now every time Claude Code touches my TWS integration code, it has domain expertise loaded automatically.
Step 3: The Audit
I asked Claude to review my TWS integration. With the skill active, it immediately flagged issues I'd been debugging for weeks:
Critical: Still Using ib_insync
# What I had
from ib_insync import IB, Stock, Option
# What the skill caught
# ib_insync is unmaintained. Migrate to ib_async —
# same API, active development, proper async support.
I knew ib_insync was the "old" name but hadn't prioritized the migration. The skill flagged this as critical because ib_insync has unpatched connection handling bugs that cause the exact silent disconnection issues I was seeing.
Critical: No Reconnection Strategy
My reconnection logic was a simple retry with a fixed 5-second delay:
# What I had — naive reconnection
async def reconnect(self):
await asyncio.sleep(5)
await self.ib.connectAsync('127.0.0.1', 7497, clientId=1)
The skill flagged this immediately. TWS gateway has rate limiting on reconnection attempts. A fixed delay either hammers the gateway (too short) or wastes time (too long). The fix:
# Exponential backoff with jitter
async def reconnect(self):
for attempt in range(self.max_retries):
delay = min(2 ** attempt + random.uniform(0, 1), 60)
await asyncio.sleep(delay)
try:
await self.ib.connectAsync(
self.host, self.port, clientId=self.client_id
)
# Critical: re-request open orders after reconnect
self.ib.reqOpenOrders()
return
except Exception:
continue
raise ConnectionError("Failed to reconnect after max retries")
Critical: Stale Order State After Reconnect
This was the silent killer. After reconnecting, my app assumed it still had the correct order state. It didn't. TWS doesn't push order updates to a newly reconnected client — you have to explicitly request them.
# Must call after EVERY reconnect, not just startup
self.ib.reqOpenOrders()
Without this, the app would think orders were still pending when they'd already filled. Or worse — it wouldn't know about partial fills and would submit duplicate orders.
The Full Hit List
Beyond the 3 critical issues, the skill-powered review found 5 more:
| # | Severity | Issue | Impact |
|---|---|---|---|
| 1 | CRITICAL | Using ib_insync instead of ib_async | Silent disconnections |
| 2 | CRITICAL | No exponential backoff on reconnect | Gateway rate limiting |
| 3 | CRITICAL | Missing reqOpenOrders() after reconnect | Stale order state, duplicate orders |
| 4 | HIGH | Using waitOnUpdate() in async code | Blocks the event loop |
| 5 | HIGH | No try/finally around reqMktData | Leaked market data subscriptions |
| 6 | MEDIUM | Unbatched option chain requests | Hitting 100-ticker limit |
| 7 | MEDIUM | No expiration validation on reqSecDefOptParams | Using expired strikes |
| 8 | MEDIUM | No error code handling (162, 200, 354, -1) | Swallowing fatal errors |
Issue #4 alone explained half my performance problems. waitOnUpdate() is a synchronous-style blocking call that has no place in an asyncio application. Replacing every instance with proper asyncio.sleep() and event-driven callbacks gave me the single biggest speed improvement.
Issue #5 was a slow resource leak. Every time I fetched option chain prices without proper cleanup, I kept a market data subscription alive. Hit the 100-ticker limit, and all new requests silently fail. I'd been restarting the app daily to "fix" this without understanding why.
Why Skills, Not MCPs
MCPs (Model Context Protocol servers) are great for giving Claude the ability to do things — query databases, call APIs, manage infrastructure. But for domain expertise, they're overkill and wrong-shaped.
A skill is:
- Tiny — 512 tokens loaded into context, not a running server process
- Passive — activates automatically when Claude touches relevant code
- Knowledge, not capability — teaches Claude how to think about a domain, not what actions to take
- Zero infrastructure — a file in your project, not a server to maintain
An MCP for TWS would let Claude place trades. A skill for TWS makes Claude understand the API's sharp edges. One of these is useful for code review. The other is terrifying.
The 5x Performance Improvement
After fixing all 8 issues:
- Market data fetching: 12s → 2.4s (batched requests + proper async)
- Reconnection time: 30-60s → 2-8s (exponential backoff converges faster than fixed retry)
- Memory: steady-state instead of climbing (no more leaked subscriptions)
- Uptime: from daily restarts to continuous operation
The 5x headline number comes from the market data path — the hot loop that prices option chains. Batching requests and removing waitOnUpdate() blocking was the difference.
The Workflow
For anyone building domain-specific Claude Code skills:
- Ask first — check if a public skill exists for your domain
- Use the skill builder — point it at docs, known pitfalls, and best practices
- Keep it small — 512 tokens is plenty for encoding anti-patterns and conventions
- Install per-project — skills activate contextually, only when relevant code is touched
- Iterate — as you find new pitfalls, update the skill
The entire process from "I wonder if there's a skill for this" to "my production app is 5x faster" was under an hour. Most of that was applying the fixes, not finding them.
The Takeaway
AI coding assistants are only as good as their domain knowledge. Generic Claude is excellent at Python and async patterns. But it doesn't know that reqOpenOrders() must be called after TWS reconnection, or that waitOnUpdate() will deadlock an asyncio event loop, or that the 100-ticker limit fails silently.
512 tokens of domain expertise turned "Claude helps me write Python" into "Claude catches TWS-specific production bugs before they hit."
Skills are the highest-leverage feature in Claude Code that nobody is using yet.
