.cursor/skills/ibkr-tws-cpp-engineer/SKILL.md
Builds and debugs Interactive Brokers TWS API integration in C++ (twsapi)—connectivity, EReader/EClientSocket, contract builders (STK/OPT/FUT/BAG), market data and snapshot pricing, order CRUD and combo orders, EWrapper execution hooks, Docker/SDK/CMake, env IBKR_HOST/IBKR_PORT/IBKR_CLIENT_ID, tws/ibkr/ and controllers. Use when implementing or changing IBKR trading, pricing, contracts, or gateway wiring in the tws service.
npx skillsauth add Pk13055/tws-drogon-docker-template ibkr-tws-cpp-engineerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expertise for the tws service: official IBKR TWS API (twsapi), reader thread + signal loop, contract builders (STK, OPT, FUT, BAG), market data (streaming vs snapshot), order CRUD and combo (BAG) execution, and safe use behind Drogon (microservice isolation, production-minded defaults). Pair with drogon-cpp-engineer for HTTP/async discipline.
ibkr::Client (tws/ibkr/ibkr_client.{h,cc}): connect/disconnect, EWrapper overrides, reader lifecycle.ibkr::probeConnection (tws/ibkr/ibkr_probe.{h,cc}): connectivity probes, client-id backoff (e.g. error 326).find_package(twsapi CONFIG REQUIRED), tws/CMakeLists.txt, tws/Dockerfile, tws/cmake/tws-sdk/.IBKR_HOST, IBKR_PORT, IBKR_CLIENT_ID; compose host ibkr, gateway port default 8888.tws/controllers/orders/, tws/controllers/pricing/, /tws/ping in tws/main.cc.Contract / Order / ComboLeg construction, reqMktData / reqContractDetails, placeOrder / cancelOrder, BAG + smart combo routing, execDetails reconciliation.DefaultEWrapper, EClientSocket, EReader, EReaderOSSignal (IB headers).ibkr::Client::instance() — singleton; connect / disconnect own EReader, reader_thread_, EReaderOSSignal (waitForSignal → processMsgs).req* / placeOrder: EClientSocket *sock = ibkr::Client::instance().client(); then if (!sock) return; (only valid while connected; see Threading and Drogon before calling from HTTP threads)./usr/local/include/twsapi, protobufUnix — see tws/CMakeLists.txt.twsapi; repo uses -Wl,--allow-shlib-undefined for optional symbols in the IB shared library.std::atexit in main.cc calls ibkr::Client::instance().disconnect().Patterns below use raw EClientSocket* client; in this repo, pass ibkr::Client::instance().client() after a null check, or extend ibkr::Client so all req* calls share one threading policy.
Base contract
Contract BaseContract(int conId,
const std::string& symbol,
const std::string& secType,
const std::string& exchange = "SMART",
const std::string& currency = "USD") {
Contract c;
c.conId = conId;
c.symbol = symbol;
c.secType = secType;
c.exchange = exchange;
c.currency = currency;
return c;
}
STK
Contract BuildStock(int conId, const std::string& symbol) {
return BaseContract(conId, symbol, "STK");
}
OPT
Contract BuildOption(int conId,
const std::string& symbol,
const std::string& expiry,
double strike,
const std::string& right) {
Contract c = BaseContract(conId, symbol, "OPT");
c.lastTradeDateOrContractMonth = expiry;
c.strike = strike;
c.right = right; // "C" or "P"
c.multiplier = "100";
return c;
}
FUT
Contract BuildFuture(int conId,
const std::string& symbol,
const std::string& expiry) {
Contract c = BaseContract(conId, symbol, "FUT");
c.lastTradeDateOrContractMonth = expiry;
return c;
}
Combo leg
ComboLeg BuildComboLeg(int conId,
int ratio,
const std::string& action,
const std::string& exchange = "SMART") {
ComboLeg leg;
leg.conId = conId;
leg.ratio = ratio;
leg.action = action; // BUY / SELL
leg.exchange = exchange;
return leg;
}
BAG (mixed STK / OPT / FUT legs)
Contract BuildBag(const std::string& symbol,
const std::vector<ComboLeg>& legs,
const std::string& currency = "USD") {
Contract c;
c.symbol = symbol;
c.secType = "BAG";
c.exchange = "SMART";
c.currency = currency;
c.comboLegs = legs;
return c;
}
Streaming
void RequestMarketData(EClientSocket* client,
int reqId,
const Contract& contract) {
client->reqMktData(reqId, contract, "", false, false, TagValueListSPtr());
}
Snapshot (preferred for deterministic execution)
void RequestSnapshot(EClientSocket* client,
int reqId,
const Contract& contract) {
client->reqMktData(reqId, contract, "", true, false, TagValueListSPtr());
}
Cancel
void CancelMarketData(EClientSocket* client, int reqId) {
client->cancelMktData(reqId);
}
Contract enrichment
void RequestContractDetails(EClientSocket* client,
int reqId,
const Contract& contract) {
client->reqContractDetails(reqId, contract);
}
Base order
Order BuildBaseOrder(const std::string& action,
int quantity,
const std::string& orderType = "LMT",
double limitPrice = 0.0) {
Order o;
o.action = action;
o.totalQuantity = quantity;
o.orderType = orderType;
if (orderType == "LMT") {
o.lmtPrice = limitPrice;
}
return o;
}
Market
Order BuildMarketOrder(const std::string& action, int qty) {
return BuildBaseOrder(action, qty, "MKT");
}
Limit
Order BuildLimitOrder(const std::string& action,
int qty,
double price) {
return BuildBaseOrder(action, qty, "LMT", price);
}
Create
void CreateOrder(EClientSocket* client,
int orderId,
const Contract& contract,
const Order& order) {
client->placeOrder(orderId, contract, order);
}
Read — open orders
void ReadOpenOrders(EClientSocket* client) {
client->reqOpenOrders();
}
Read — all open orders
void ReadAllOrders(EClientSocket* client) {
client->reqAllOpenOrders();
}
Update (same API as create)
void UpdateOrder(EClientSocket* client,
int orderId,
const Contract& contract,
const Order& updatedOrder) {
client->placeOrder(orderId, contract, updatedOrder);
}
Delete
void CancelOrder(EClientSocket* client, int orderId) {
client->cancelOrder(orderId);
}
Combo limit order
Order BuildComboLimitOrder(const std::string& action,
int qty,
double limitPrice) {
Order o;
o.action = action;
o.orderType = "LMT";
o.totalQuantity = qty;
o.lmtPrice = limitPrice;
return o;
}
Place combo
void CreateComboOrder(EClientSocket* client,
int orderId,
const Contract& bagContract,
const Order& order) {
client->placeOrder(orderId, bagContract, order);
}
Smart routing (optional)
void AddSmartComboRouting(Order& order) {
order.smartComboRoutingParams.reset(new TagValueList());
order.smartComboRoutingParams->push_back(
TagValueSPtr(new TagValue("NonGuaranteed", "1")));
}
Override on your EWrapper (e.g. extend ibkr::Client or a dedicated wrapper):
void execDetails(int reqId,
const Contract& contract,
const Execution& execution) override {
// Persist execution.execId; map to internal trade + order ids
}
conId: treat as lookup hint, not a substitute for a full contract definition for new instruments.Contract fields; use reqContractDetails to backfill missing metadata.reqMktData (snapshot=true) when you need a deterministic price point before sending an order.conId, full contract fields, execution ids (execId, etc.) for audit and replay.conId, watchlist/trade entities, broker_orders / executions — keep API ids and internal ids explicitly mapped.DefaultEWrapper; implement error, connectionClosed, and trading callbacks you subscribe to (tickPrice, orderStatus, execDetails, etc.).eConnect, EReader::start(), then waitForSignal → processMsgs on the reader thread.std::mutex for shared fields consistent with ibkr_client.cc.isConnected(), connection metadata, lastError*, client() != nullptr. Always invoke Drogon callback.ibkr::probeConnection for one-off checks and client-id stepping.processMsgs runs on the reader thread only.req* / placeOrder from HTTP threads: IB APIs are not thread-safe by default — centralize calls inside ibkr::Client (mutex + same thread as socket, or command queue + reader thread drain) before scaling trading features. The next production hardening step is typically a thread-safe wrapper + async event bus between IB callbacks and the rest of the service.tws/main.cc): ibkr, 8888, client id 30 — override with env.TWS_SDK_ZIP / TWS_SDK_URL in tws/Dockerfile; overlay tws/cmake/tws-sdk/ before protoc / install.EReader, joins reader thread, eDisconnect when connected; issueSignal on shutdown.trantor/utils/Logger.h..cc files: add to tws/CMakeLists.txt add_executable.IBKR_HOST matches the gateway container/service name.HttpController paths.callback on error/success branches.tws/cmake/tws-sdk/ overlay compatibility.conId alone for unfamiliar instruments.EClientSocket (except isolated probes like ibkr_probe).req* / placeOrder from multiple threads.-Wl,--allow-shlib-undefined without verifying the twsapi binary you link.tools
Develops, debugs, and scales high-performance Drogon C++ web apps—PostgreSQL via DbClient/ORM, RabbitMQ (AMQP-CPP) with Trantor event-loop integration, HttpController and WebSocketController, drogon_ctl scaffolding, config.json, HttpFilter middleware. Use when editing Drogon code, Postgres, messaging, drogon::app(), CMake, or C++ web APIs in this repository.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------