Skip to content

Commit da69a99

Browse files
committed
Merge branch 'pr/49'
2 parents 46e311c + 6bbb84e commit da69a99

File tree

17 files changed

+187
-154
lines changed

17 files changed

+187
-154
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { clampBaoziPrice, normalizeBaoziOutcomes } from "./price";
2+
import { MarketOutcome } from "../../types";
3+
4+
describe("clampBaoziPrice", () => {
5+
test("clamps values below 0 to 0", () => {
6+
expect(clampBaoziPrice(-0.1)).toBe(0);
7+
});
8+
9+
test("clamps values above 1 to 1", () => {
10+
expect(clampBaoziPrice(1.2)).toBe(1);
11+
});
12+
13+
test("leaves values in range unchanged", () => {
14+
expect(clampBaoziPrice(0.3)).toBe(0.3);
15+
expect(clampBaoziPrice(0)).toBe(0);
16+
expect(clampBaoziPrice(1)).toBe(1);
17+
});
18+
});
19+
20+
describe("normalizeBaoziOutcomes", () => {
21+
function makeOutcome(price: number): MarketOutcome {
22+
return { outcomeId: "x", marketId: "m", label: "X", price };
23+
}
24+
25+
test("normalizes prices to sum to 1", () => {
26+
const outcomes = [makeOutcome(2), makeOutcome(3)];
27+
normalizeBaoziOutcomes(outcomes);
28+
expect(outcomes[0].price).toBeCloseTo(0.4);
29+
expect(outcomes[1].price).toBeCloseTo(0.6);
30+
});
31+
32+
test("does nothing when sum is zero", () => {
33+
const outcomes = [makeOutcome(0), makeOutcome(0)];
34+
normalizeBaoziOutcomes(outcomes);
35+
expect(outcomes[0].price).toBe(0);
36+
expect(outcomes[1].price).toBe(0);
37+
});
38+
});

core/src/exchanges/baozi/price.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { MarketOutcome } from "../../types";
2+
3+
export function clampBaoziPrice(value: number): number {
4+
return Math.min(Math.max(value, 0), 1);
5+
}
6+
7+
export function normalizeBaoziOutcomes(outcomes: MarketOutcome[]): void {
8+
const sum = outcomes.reduce((acc, item) => acc + item.price, 0);
9+
if (sum <= 0) {
10+
return;
11+
}
12+
13+
for (const outcome of outcomes) {
14+
outcome.price = outcome.price / sum;
15+
}
16+
}

core/src/exchanges/baozi/utils.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import bs58 from 'bs58';
33
import { createHash } from 'crypto';
44
import { UnifiedMarket, MarketOutcome } from '../../types';
55
import { addBinaryOutcomes } from '../../utils/market-utils';
6+
import { clampBaoziPrice, normalizeBaoziOutcomes } from './price';
67

78
// ---------------------------------------------------------------------------
89
// Constants
@@ -387,13 +388,13 @@ export function mapBooleanToUnified(market: BaoziMarket, pubkey: string): Unifie
387388
outcomeId: `${pubkey}-YES`,
388389
marketId: pubkey,
389390
label: 'Yes',
390-
price: yesPrice,
391+
price: clampBaoziPrice(yesPrice),
391392
},
392393
{
393394
outcomeId: `${pubkey}-NO`,
394395
marketId: pubkey,
395396
label: 'No',
396-
price: noPrice,
397+
price: clampBaoziPrice(noPrice),
397398
},
398399
];
399400

@@ -431,17 +432,12 @@ export function mapRaceToUnified(market: BaoziRaceMarket, pubkey: string): Unifi
431432
outcomeId: `${pubkey}-${i}`,
432433
marketId: pubkey,
433434
label: market.outcomeLabels[i] || `Outcome ${i + 1}`,
434-
price: Math.min(Math.max(price, 0), 1),
435+
price: clampBaoziPrice(price),
435436
});
436437
}
437438

438439
// Normalize prices to sum to 1
439-
const priceSum = outcomes.reduce((s, o) => s + o.price, 0);
440-
if (priceSum > 0) {
441-
for (const o of outcomes) {
442-
o.price = o.price / priceSum;
443-
}
444-
}
440+
normalizeBaoziOutcomes(outcomes);
445441

446442
const um: UnifiedMarket = {
447443
marketId: pubkey,

core/src/exchanges/kalshi/fetchOHLCV.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PriceCandle } from "../../types";
33
import { mapIntervalToKalshi } from "./utils";
44
import { validateIdFormat } from "../../utils/validation";
55
import { kalshiErrorMapper } from "./errors";
6+
import { fromKalshiCents } from "./price";
67

78
export async function fetchOHLCV(
89
id: string,
@@ -98,10 +99,10 @@ export async function fetchOHLCV(
9899

99100
return {
100101
timestamp: c.end_period_ts * 1000,
101-
open: getVal("open") / 100,
102-
high: getVal("high") / 100,
103-
low: getVal("low") / 100,
104-
close: getVal("close") / 100,
102+
open: fromKalshiCents(getVal("open")),
103+
high: fromKalshiCents(getVal("high")),
104+
low: fromKalshiCents(getVal("low")),
105+
close: fromKalshiCents(getVal("close")),
105106
volume: c.volume || 0,
106107
};
107108
});

core/src/exchanges/kalshi/fetchOrderBook.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { OrderBook } from "../../types";
33
import { validateIdFormat } from "../../utils/validation";
44
import { kalshiErrorMapper } from "./errors";
55
import { getMarketsUrl } from "./config";
6+
import { fromKalshiCents, invertKalshiCents } from "./price";
67

78
export async function fetchOrderBook(
89
baseUrl: string,
@@ -31,25 +32,25 @@ export async function fetchOrderBook(
3132
// - Bids: people buying NO (use data.no directly)
3233
// - Asks: people selling NO = people buying YES (invert data.yes)
3334
bids = (data.no || []).map((level: number[]) => ({
34-
price: level[0] / 100,
35+
price: fromKalshiCents(level[0]),
3536
size: level[1],
3637
}));
3738

3839
asks = (data.yes || []).map((level: number[]) => ({
39-
price: 1 - level[0] / 100, // Invert YES price to get NO ask price
40+
price: invertKalshiCents(level[0]), // Invert YES price to get NO ask price
4041
size: level[1],
4142
}));
4243
} else {
4344
// YES outcome order book:
4445
// - Bids: people buying YES (use data.yes directly)
4546
// - Asks: people selling YES = people buying NO (invert data.no)
4647
bids = (data.yes || []).map((level: number[]) => ({
47-
price: level[0] / 100,
48+
price: fromKalshiCents(level[0]),
4849
size: level[1],
4950
}));
5051

5152
asks = (data.no || []).map((level: number[]) => ({
52-
price: 1 - level[0] / 100, // Invert NO price to get YES ask price
53+
price: invertKalshiCents(level[0]), // Invert NO price to get YES ask price
5354
size: level[1],
5455
}));
5556
}

core/src/exchanges/kalshi/fetchTrades.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { HistoryFilterParams, TradesParams } from "../../BaseExchange";
33
import { Trade } from "../../types";
44
import { kalshiErrorMapper } from "./errors";
55
import { getMarketsUrl } from "./config";
6+
import { fromKalshiCents } from "./price";
67

78
export async function fetchTrades(
89
baseUrl: string,
@@ -23,7 +24,7 @@ export async function fetchTrades(
2324
return trades.map((t: any) => ({
2425
id: t.trade_id,
2526
timestamp: new Date(t.created_time).getTime(),
26-
price: t.yes_price / 100,
27+
price: fromKalshiCents(t.yes_price),
2728
amount: t.count,
2829
side: t.taker_side === "yes" ? "buy" : "sell",
2930
}));

core/src/exchanges/kalshi/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { AuthenticationError } from "../../errors";
3333
import { parseOpenApiSpec } from "../../utils/openapi";
3434
import { kalshiApiSpec } from "./api";
3535
import { getKalshiConfig, KalshiApiConfig, KALSHI_PATHS } from "./config";
36+
import { fromKalshiCents, invertKalshiCents } from "./price";
3637

3738
// Re-export for external use
3839
export type { KalshiWebSocketConfig };
@@ -181,20 +182,20 @@ export class KalshiExchange extends PredictionMarketExchange {
181182

182183
if (isNoOutcome) {
183184
bids = (data.no || []).map((level: number[]) => ({
184-
price: level[0] / 100,
185+
price: fromKalshiCents(level[0]),
185186
size: level[1],
186187
}));
187188
asks = (data.yes || []).map((level: number[]) => ({
188-
price: 1 - level[0] / 100,
189+
price: invertKalshiCents(level[0]),
189190
size: level[1],
190191
}));
191192
} else {
192193
bids = (data.yes || []).map((level: number[]) => ({
193-
price: level[0] / 100,
194+
price: fromKalshiCents(level[0]),
194195
size: level[1],
195196
}));
196197
asks = (data.no || []).map((level: number[]) => ({
197-
price: 1 - level[0] / 100,
198+
price: invertKalshiCents(level[0]),
198199
size: level[1],
199200
}));
200201
}
@@ -224,7 +225,7 @@ export class KalshiExchange extends PredictionMarketExchange {
224225
return trades.map((t: any) => ({
225226
id: t.trade_id,
226227
timestamp: new Date(t.created_time).getTime(),
227-
price: t.yes_price / 100,
228+
price: fromKalshiCents(t.yes_price),
228229
amount: t.count,
229230
side: t.taker_side === "yes" ? "buy" : "sell",
230231
}));
@@ -342,7 +343,7 @@ export class KalshiExchange extends PredictionMarketExchange {
342343
return (data.fills || []).map((f: any) => ({
343344
id: f.fill_id,
344345
timestamp: new Date(f.created_time).getTime(),
345-
price: f.yes_price / 100,
346+
price: fromKalshiCents(f.yes_price),
346347
amount: f.count,
347348
side: f.side === "yes" ? ("buy" as const) : ("sell" as const),
348349
orderId: f.order_id,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { fromKalshiCents, invertKalshiCents, invertKalshiUnified } from "./price";
2+
3+
describe("fromKalshiCents", () => {
4+
test("converts cents to a decimal probability", () => {
5+
expect(fromKalshiCents(55)).toBe(0.55);
6+
expect(fromKalshiCents(0)).toBe(0);
7+
expect(fromKalshiCents(100)).toBe(1);
8+
});
9+
});
10+
11+
describe("invertKalshiCents", () => {
12+
test("returns the complement of a cent value", () => {
13+
expect(invertKalshiCents(45)).toBeCloseTo(0.55);
14+
expect(invertKalshiCents(0)).toBe(1);
15+
expect(invertKalshiCents(100)).toBe(0);
16+
});
17+
});
18+
19+
describe("invertKalshiUnified", () => {
20+
test("returns the complement of a normalized price", () => {
21+
expect(invertKalshiUnified(0.45)).toBeCloseTo(0.55);
22+
expect(invertKalshiUnified(0)).toBe(1);
23+
expect(invertKalshiUnified(1)).toBe(0);
24+
});
25+
});

core/src/exchanges/kalshi/price.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function fromKalshiCents(priceInCents: number): number {
2+
return priceInCents / 100;
3+
}
4+
5+
export function invertKalshiCents(priceInCents: number): number {
6+
return 1 - priceInCents / 100;
7+
}
8+
9+
export function invertKalshiUnified(price: number): number {
10+
return 1 - price;
11+
}

core/src/exchanges/kalshi/utils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { UnifiedMarket, MarketOutcome, CandleInterval } from "../../types";
22
import { addBinaryOutcomes } from "../../utils/market-utils";
3+
import { fromKalshiCents, invertKalshiUnified } from "./price";
34

45
export function mapMarketToUnified(
56
event: any,
@@ -10,11 +11,11 @@ export function mapMarketToUnified(
1011
// Calculate price
1112
let price = 0.5;
1213
if (market.last_price) {
13-
price = market.last_price / 100;
14+
price = fromKalshiCents(market.last_price);
1415
} else if (market.yes_ask && market.yes_bid) {
15-
price = (market.yes_ask + market.yes_bid) / 200;
16+
price = (fromKalshiCents(market.yes_ask) + fromKalshiCents(market.yes_bid)) / 2;
1617
} else if (market.yes_ask) {
17-
price = market.yes_ask / 100;
18+
price = fromKalshiCents(market.yes_ask);
1819
}
1920

2021
// Extract candidate name
@@ -44,7 +45,7 @@ export function mapMarketToUnified(
4445
outcomeId: `${market.ticker}-NO`,
4546
marketId: market.ticker,
4647
label: candidateName ? `Not ${candidateName}` : "No",
47-
price: 1 - price,
48+
price: invertKalshiUnified(price),
4849
priceChange24h: -priceChange, // Inverse change for No? simplified assumption
4950
},
5051
];

0 commit comments

Comments
 (0)