Back to Blog
AI & Automation6 min read

Claude Code Skills Found 3 Critical Anti-Patterns in My TWS API Code

Alex Ozhima
|March 26, 2026

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_async connection 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:

#SeverityIssueImpact
1CRITICALUsing ib_insync instead of ib_asyncSilent disconnections
2CRITICALNo exponential backoff on reconnectGateway rate limiting
3CRITICALMissing reqOpenOrders() after reconnectStale order state, duplicate orders
4HIGHUsing waitOnUpdate() in async codeBlocks the event loop
5HIGHNo try/finally around reqMktDataLeaked market data subscriptions
6MEDIUMUnbatched option chain requestsHitting 100-ticker limit
7MEDIUMNo expiration validation on reqSecDefOptParamsUsing expired strikes
8MEDIUMNo 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:

  1. Ask first — check if a public skill exists for your domain
  2. Use the skill builder — point it at docs, known pitfalls, and best practices
  3. Keep it small — 512 tokens is plenty for encoding anti-patterns and conventions
  4. Install per-project — skills activate contextually, only when relevant code is touched
  5. 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.

Alex Ozhima

Alex Ozhima

Founder & CEO at Katlextech

Ready to Ship Your Product?

Let's discuss how we can implement these strategies for your business