Skip to content

Commit aa317d5

Browse files
authored
Merge pull request #237 from ceddybi/get_tickbytick_alllast_updates
feat(TickByTickAllLastDataUpdates)
2 parents f4868ab + ca6c1bf commit aa317d5

File tree

4 files changed

+225
-1
lines changed

4 files changed

+225
-1
lines changed

src/api-next/api-next.ts

+82
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ import {
2929
ScannerSubscription,
3030
SecType,
3131
TagValue,
32+
TickByTickDataType,
3233
WhatToShow,
3334
} from "../";
3435
import LogLevel from "../api/data/enum/log-level";
36+
import { TickAttribLast } from "../api/historical/historicalTickLast";
37+
import { TickByTickAllLast } from "../api/market/tickByTickAllLast";
3538
import OrderStatus from "../api/order/enum/order-status";
3639
import { isNonFatalError } from "../common/errorCode";
3740
import {
@@ -3133,4 +3136,83 @@ export class IBApiNext {
31333136
},
31343137
);
31353138
}
3139+
3140+
/** TickByTickAllLastDataUpdates event handler */
3141+
private readonly onTickByTickAllLastDataUpdates =
3142+
(contract: Contract) =>
3143+
(
3144+
subscriptions: Map<number, IBApiNextSubscription<TickByTickAllLast>>,
3145+
reqId: number,
3146+
tickType: number,
3147+
time: string,
3148+
price: number,
3149+
size: number,
3150+
tickAttribLast: TickAttribLast,
3151+
exchange: string,
3152+
specialConditions: string,
3153+
): void => {
3154+
// get subscription
3155+
3156+
const subscription = subscriptions.get(reqId);
3157+
if (!subscription) {
3158+
return;
3159+
}
3160+
3161+
// update tick by tick all last
3162+
3163+
const current = subscription.lastAllValue ?? ({} as TickByTickAllLast);
3164+
current.tickType = tickType;
3165+
current.time = !time ? undefined : +time;
3166+
current.price = price !== -1 ? price : undefined;
3167+
current.size = size !== -1 ? size : undefined;
3168+
current.tickAttribLast = tickAttribLast;
3169+
current.exchange = exchange;
3170+
current.specialConditions = specialConditions;
3171+
current.contract = contract;
3172+
subscription.next({
3173+
all: current,
3174+
});
3175+
};
3176+
3177+
/**
3178+
* Create a subscription to receive tick-by-tick last or all last price data updates.
3179+
*
3180+
* Use {@link IBApiNext.getHistoricalTicksLast} to receive historical last tick data and this function if you
3181+
* want to receive real-time tick-by-tick last or all last price data updates.
3182+
*
3183+
* @see https://interactivebrokers.github.io/tws-api/tick_data.html for details
3184+
*
3185+
* @param contract The contract for which we want to retrieve the data.
3186+
* @param numberOfTicks The number of ticks to retrieve.
3187+
* @param ignoreSize If true, the size of the tick will be ignored.
3188+
*/
3189+
getTickByTickAllLastDataUpdates(
3190+
contract: Contract,
3191+
numberOfTicks: number = 0,
3192+
ignoreSize: boolean = false,
3193+
): Observable<TickByTickAllLast> {
3194+
return this.subscriptions
3195+
.register<TickByTickAllLast>(
3196+
(reqId) => {
3197+
this.api.reqTickByTickData(
3198+
reqId,
3199+
contract,
3200+
TickByTickDataType.Last,
3201+
numberOfTicks,
3202+
ignoreSize,
3203+
);
3204+
},
3205+
(reqId) => {
3206+
this.api.cancelTickByTickData(reqId);
3207+
},
3208+
[
3209+
[
3210+
EventName.tickByTickAllLast,
3211+
this.onTickByTickAllLastDataUpdates(contract),
3212+
],
3213+
],
3214+
`${JSON.stringify(contract)}:${numberOfTicks}:${ignoreSize}`, // Use the same instance ID each time to ensure there is only one pending request at a time.
3215+
)
3216+
.pipe(map((v: { all: TickByTickAllLast }) => v.all));
3217+
}
31363218
}

src/api/api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3021,7 +3021,7 @@ export declare interface IBApi {
30213021
listener: (
30223022
reqId: number,
30233023
tickType: number,
3024-
time: number,
3024+
time: string,
30253025
price: number,
30263026
size: number,
30273027
tickAttribLast: TickAttribLast,

src/api/market/tickByTickAllLast.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Contract } from "../contract/contract";
2+
import { TickAttribLast } from "../historical/historicalTickLast";
3+
4+
/**
5+
* Tick-by-tick last or all last price data.
6+
*
7+
* Used when requesting tick-by-tick data with tickType = LAST or ALL_LAST.
8+
*/
9+
export interface TickByTickAllLast {
10+
/** The tick type. */
11+
tickType?: number;
12+
13+
/** The UNIX timestamp of the tick. */
14+
time: number;
15+
16+
/** The price of the tick. */
17+
price: number;
18+
19+
/** The size of the tick. */
20+
size: number;
21+
22+
/** The tick attributes of the tick. */
23+
tickAttribLast: TickAttribLast;
24+
25+
/** The exchange of the tick. */
26+
exchange: string;
27+
28+
/** The special conditions of the tick. */
29+
specialConditions: string;
30+
31+
/** The contract of the tick. */
32+
contract: Contract;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* This file implements tests for the [[IBApiNext.getTickByTickAllLastDataUpdates]] function.
3+
*/
4+
5+
import {
6+
Contract,
7+
EventName,
8+
IBApi,
9+
IBApiNext,
10+
IBApiNextError,
11+
SecType,
12+
} from "../../..";
13+
import { TickByTickAllLast } from "../../../api/market/tickByTickAllLast";
14+
15+
describe("RxJS Wrapper: getTickByTickAllLastDataUpdates()", () => {
16+
test("Observable updates", (done) => {
17+
// create IBApiNext
18+
19+
const apiNext = new IBApiNext();
20+
const api = (apiNext as unknown as Record<string, unknown>).api as IBApi;
21+
22+
// emit EventName.tickByTickAllLast events and verify RxJS result
23+
24+
const contract: Contract = {
25+
symbol: "AMZN",
26+
exchange: "SMART",
27+
currency: "USD",
28+
secType: SecType.STK,
29+
};
30+
31+
const REF_TICKS: TickByTickAllLast[] = [
32+
{
33+
contract,
34+
time: 1675228123,
35+
price: 1,
36+
size: 2,
37+
tickAttribLast: {
38+
pastLimit: false,
39+
unreported: false,
40+
},
41+
exchange: "EXCHANGE",
42+
specialConditions: "SPECIAL_CONDITIONS",
43+
},
44+
{
45+
contract,
46+
time: 1675228124,
47+
price: 11,
48+
size: 12,
49+
tickAttribLast: {
50+
pastLimit: false,
51+
unreported: false,
52+
},
53+
exchange: "EXCHANGE",
54+
specialConditions: "SPECIAL_CONDITIONS",
55+
},
56+
{
57+
contract,
58+
time: 1675228125,
59+
price: 21,
60+
size: 22,
61+
tickAttribLast: {
62+
pastLimit: false,
63+
unreported: false,
64+
},
65+
exchange: "EXCHANGE",
66+
specialConditions: "SPECIAL_CONDITIONS",
67+
},
68+
];
69+
70+
let updateCount = 0;
71+
72+
apiNext
73+
.getTickByTickAllLastDataUpdates(contract, 0, false)
74+
// eslint-disable-next-line rxjs/no-ignored-subscription
75+
.subscribe({
76+
next: (update) => {
77+
expect(update.contract).toEqual(REF_TICKS[updateCount].contract);
78+
expect(update.time).toEqual(REF_TICKS[updateCount].time);
79+
expect(update.price).toEqual(REF_TICKS[updateCount].price);
80+
expect(update.size).toEqual(REF_TICKS[updateCount].size);
81+
expect(update.tickAttribLast).toEqual(REF_TICKS[updateCount].tickAttribLast);
82+
expect(update.exchange).toEqual(REF_TICKS[updateCount].exchange);
83+
expect(update.specialConditions).toEqual(REF_TICKS[updateCount].specialConditions);
84+
85+
updateCount++;
86+
if (updateCount >= REF_TICKS.length) {
87+
done();
88+
}
89+
},
90+
error: (err: IBApiNextError) => {
91+
fail(err.error.message);
92+
},
93+
});
94+
95+
for (let i = 0; i < REF_TICKS.length; i++) {
96+
api.emit(
97+
EventName.tickByTickAllLast,
98+
1,
99+
1,
100+
REF_TICKS[i].time,
101+
REF_TICKS[i].price,
102+
REF_TICKS[i].size,
103+
REF_TICKS[i].tickAttribLast,
104+
REF_TICKS[i].exchange,
105+
REF_TICKS[i].specialConditions,
106+
);
107+
}
108+
});
109+
});

0 commit comments

Comments
 (0)