← back to Ken
fix: migrate Kalshi exit orders + manual create_order to v2 /portfolio/events/orders API
9969827b63a7da56ed5f5d5509813a00edd5f82b · 2026-08-04 04:03:21 -0700 · steve@designerwallcoverings.com
Take-profit and stop-loss sell paths were still calling retired POST /portfolio/orders (v1, 410).
Manual create_order was also on v1. All three now use POST /portfolio/events/orders with
bid/ask semantics, IOC time_in_force for market-like behavior, and yes-price dollar values.
Refs: TK-10174
Files touched
Diff
commit 9969827b63a7da56ed5f5d5509813a00edd5f82b
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Tue Aug 4 04:03:21 2026 -0700
fix: migrate Kalshi exit orders + manual create_order to v2 /portfolio/events/orders API
Take-profit and stop-loss sell paths were still calling retired POST /portfolio/orders (v1, 410).
Manual create_order was also on v1. All three now use POST /portfolio/events/orders with
bid/ask semantics, IOC time_in_force for market-like behavior, and yes-price dollar values.
Refs: TK-10174
---
kalshi-dash/server.js | 55 ++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 43 insertions(+), 12 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 40cfefe..c6f833b 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -977,20 +977,29 @@ const routes = {
if (!body.ticker || !body.side || !body.action || !body.type || !body.count) {
return json(res, { error: 'ticker, side, action, type, and count are required' }, 400);
}
+ // v2 create-order: convert v1 yes/no+action semantics to v2 bid/ask single-price model.
+ // buy YES → bid; sell YES → ask; buy NO → ask (sell YES at low price); sell NO → bid.
+ let v2Side, v2Price;
+ if (body.side === 'yes') {
+ v2Side = body.action === 'buy' ? 'bid' : 'ask';
+ v2Price = (body.yes_price / 100).toFixed(2);
+ } else {
+ v2Side = body.action === 'buy' ? 'ask' : 'bid';
+ v2Price = ((100 - (body.no_price || 0)) / 100).toFixed(2);
+ }
+ const v2Tif = body.time_in_force || (body.type === 'market' ? 'immediate_or_cancel' : 'good_till_canceled');
const orderPayload = {
ticker: body.ticker,
- side: body.side, // 'yes' or 'no'
- action: body.action, // 'buy' or 'sell'
- type: body.type, // 'limit' or 'market'
- count: body.count,
+ side: v2Side,
+ count: String(body.count),
+ price: v2Price,
+ time_in_force: v2Tif,
+ self_trade_prevention_type: 'taker_at_cross',
};
- if (body.yes_price !== undefined) orderPayload.yes_price = body.yes_price;
- if (body.no_price !== undefined) orderPayload.no_price = body.no_price;
if (body.client_order_id) orderPayload.client_order_id = body.client_order_id;
- if (body.time_in_force) orderPayload.time_in_force = body.time_in_force;
if (body.expiration_ts) orderPayload.expiration_ts = body.expiration_ts;
- const data = await kalshiFetch('POST', '/portfolio/orders', orderPayload);
+ const data = await kalshiFetch('POST', '/portfolio/events/orders', orderPayload);
json(res, { success: true, order: data });
break;
}
@@ -7712,8 +7721,19 @@ async function autoTrader(signals) {
// Take profit check
if (currentPrice >= TRADE_CONFIG.take_profit_pct) {
try {
- const sellOrder = await kalshiFetch('POST', '/portfolio/orders', {
- ticker: trade.ticker, side: trade.side, action: 'sell', type: 'market', count: trade.count,
+ // v2 exit: YES position → ask (sell YES); NO position → bid (buy YES back to close).
+ const exitV2Side = trade.side === 'yes' ? 'ask' : 'bid';
+ const exitYesCents = trade.side === 'yes'
+ ? (mkt.yes_bid || mkt.last_price || currentPrice)
+ : (mkt.yes_ask || mkt.last_price || (100 - currentPrice));
+ const sellOrder = await kalshiFetch('POST', '/portfolio/events/orders', {
+ ticker: trade.ticker,
+ side: exitV2Side,
+ count: String(trade.count),
+ price: (exitYesCents / 100).toFixed(2),
+ time_in_force: 'immediate_or_cancel',
+ self_trade_prevention_type: 'taker_at_cross',
+ client_order_id: crypto.randomUUID(),
});
const sellPrice = currentPrice;
const pnl = Math.round((sellPrice - trade.price_cents) * trade.count * (1 - TRADE_CONFIG.fee_rate));
@@ -7730,8 +7750,19 @@ async function autoTrader(signals) {
const unrealizedLoss = (trade.price_cents - currentPrice) * trade.count;
if (unrealizedLoss > trade.cost_cents * (TRADE_CONFIG.stop_loss_pct / 100)) {
try {
- const sellOrder = await kalshiFetch('POST', '/portfolio/orders', {
- ticker: trade.ticker, side: trade.side, action: 'sell', type: 'market', count: trade.count,
+ // v2 exit: same bid/ask conversion as take-profit.
+ const slExitV2Side = trade.side === 'yes' ? 'ask' : 'bid';
+ const slExitYesCents = trade.side === 'yes'
+ ? (mkt.yes_bid || mkt.last_price || currentPrice)
+ : (mkt.yes_ask || mkt.last_price || (100 - currentPrice));
+ const sellOrder = await kalshiFetch('POST', '/portfolio/events/orders', {
+ ticker: trade.ticker,
+ side: slExitV2Side,
+ count: String(trade.count),
+ price: (slExitYesCents / 100).toFixed(2),
+ time_in_force: 'immediate_or_cancel',
+ self_trade_prevention_type: 'taker_at_cross',
+ client_order_id: crypto.randomUUID(),
});
const sellPrice = currentPrice;
const pnl = (sellPrice - trade.price_cents) * trade.count;
← 982c1f4 auto-save: 2026-08-03T13:24:27 (1 files) — kalshi-dash/ken-r
·
back to Ken
·
auto-save: 2026-08-04T09:32:01 (1 files) — kalshi-dash/place 2bec9fc →