Skip to content

Commit

Permalink
Merge pull request #198 from stoqey/bonds
Browse files Browse the repository at this point in the history
getContractDetails implemented for bonds
  • Loading branch information
rylorin authored Dec 2, 2023
2 parents 423be12 + 8f67cb5 commit 8dd6f76
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 15 deletions.
4 changes: 1 addition & 3 deletions src/api-next/api-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@ export class IBApiNext {
undefined,
[
[EventName.contractDetails, this.onContractDetails],
[EventName.bondContractDetails, this.onContractDetails],
[EventName.contractDetailsEnd, this.onContractDetailsEnd],
],
)
Expand Down Expand Up @@ -2409,8 +2410,6 @@ export class IBApiNext {
legStr,
};

// console.log("onScannerData", item);

const lastAllValue =
subscription.lastAllValue ??
new Map<MarketScannerItemRank, MarketScannerItem>();
Expand All @@ -2429,7 +2428,6 @@ export class IBApiNext {
added: existing ? undefined : updated,
});
} else {
// console.log("saving for future use", lastValue);
subscription.lastAllValue = lastAllValue;
}
};
Expand Down
24 changes: 24 additions & 0 deletions src/api/contract/bond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SecType from "../data/enum/sec-type";
import { Contract } from "./contract";

/**
* A Bond Contract
*/
export class Bond implements Contract {
constructor(
public symbol: string,
public maturity?: string,
public exchange?: string,
public currency?: string,
) {
this.currency = this.currency ?? "USD";
}

public secType = SecType.BOND;

public get lastTradeDateOrContractMonth(): string {
return this.maturity;
}
}

export default Bond;
2 changes: 1 addition & 1 deletion src/api/contract/future.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SecType from "../data/enum/sec-type";
import { Contract } from "./contract";

/**
* A Future Option Contract
* A Future Contract
*/
export class Future implements Contract {
constructor(
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export { ErrorCode } from "./common/errorCode";

// export contract types

export { Bond } from "./api/contract/bond";
export { CFD } from "./api/contract/cfd";
export { Combo } from "./api/contract/combo";
export { ComboLeg } from "./api/contract/comboLeg";
Expand Down
131 changes: 130 additions & 1 deletion src/tests/unit/api-next/get-contract-details.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@
* This file implements tests for the [[IBApiNext.getContractDetails]] function.
*/

import { ContractDetails, EventName, IBApi, IBApiNext, IBApiNextError } from "../../..";
import { Subscription } from "rxjs";
import {
ContractDetails,
EventName,
IBApi,
IBApiNext,
IBApiNextError,
} from "../../..";
import {
sample_bond,
sample_future,
sample_option,
sample_stock,
} from "../contracts";

describe("RxJS Wrapper: getContractDetails()", () => {
test("Error Event", (done) => {
Expand Down Expand Up @@ -62,3 +75,119 @@ describe("RxJS Wrapper: getContractDetails()", () => {
api.emit(EventName.contractDetailsEnd, 1);
});
});

describe("ApiNext: getContractDetails()", () => {
jest.setTimeout(10 * 1000);

const clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client

let subscription$: Subscription;

Check warning on line 84 in src/tests/unit/api-next/get-contract-details.test.ts

View workflow job for this annotation

GitHub Actions / job

'subscription$' is defined but never used. Allowed unused vars must match /^_/u
let api: IBApiNext;
let error$: Subscription;

beforeEach(() => {
api = new IBApiNext();

if (!error$) {
error$ = api.errorSubject.subscribe((error) => {
if (error.reqId === -1) {
console.warn(`${error.error.message} (Error #${error.code})`);
} else {
console.error(
`${error.error.message} (Error #${error.code}) ${
error.advancedOrderReject ? error.advancedOrderReject : ""
}`,
);
}
});
}

try {
api.connect(clientId);
} catch (error) {
console.error(error.message);
}
});

afterEach(() => {
if (api) {
api.disconnect();
api = undefined;
}
});

test("Stock contract details", (done) => {
api
.getContractDetails(sample_stock)
.then((result) => {
// console.log(result);
expect(result.length).toBeGreaterThan(0);
if (result.length) {
expect(result[0].contract.symbol).toEqual(sample_stock.symbol);
expect(result[0].contract.secType).toEqual(sample_stock.secType);
}
done();
})
.catch((err: IBApiNextError) => {
done(
`getContractDetails failed with '${err.error.message}' (Error #${err.code})`,
);
});
});

test("Future contract details", (done) => {
api
.getContractDetails(sample_future)
.then((result) => {
// console.log(result);
expect(result.length).toBeGreaterThan(0);
if (result.length) {
expect(result[0].contract.symbol).toEqual(sample_future.symbol);
expect(result[0].contract.secType).toEqual(sample_future.secType);
}
done();
})
.catch((err: IBApiNextError) => {
done(
`getContractDetails failed with '${err.error.message}' (Error #${err.code})`,
);
});
});

test("Option contract details", (done) => {
api
.getContractDetails(sample_option)
.then((result) => {
// console.log(result);
expect(result.length).toBeGreaterThan(0);
if (result.length) {
expect(result[0].contract.symbol).toEqual(sample_option.symbol);
expect(result[0].contract.secType).toEqual(sample_option.secType);
}
done();
})
.catch((err: IBApiNextError) => {
done(
`getContractDetails failed with '${err.error.message}' (Error #${err.code})`,
);
});
});

test("Bond contract details", (done) => {
api
.getContractDetails(sample_bond)
.then((result) => {
// console.log(result);
expect(result.length).toBeGreaterThan(0);
if (result.length) {
expect(result[0].contract.secType).toEqual(sample_bond.secType);
}
done();
})
.catch((err: IBApiNextError) => {
done(
`getContractDetails failed with '${err.error.message}' (Error #${err.code})`,
);
});
});
});
1 change: 0 additions & 1 deletion src/tests/unit/api-next/place-order.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import IBApi, {
Stock,
} from "../../..";
import configuration from "../../../common/configuration";
// import configuration from "../../../common/configuration";

describe("Place orders to IB", () => {
test("Error Event", (done) => {
Expand Down
21 changes: 21 additions & 0 deletions src/tests/unit/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* This file describe sample contracts to be used in various tests code.
*/
import { Bond, Contract, Future, Option, OptionType, Stock } from "../..";

export const sample_stock: Contract = new Stock("AAPL");
export const sample_etf: Contract = new Stock("SPY");
export const sample_future: Contract = new Future(
"ES",
"ESZ3",
"202312",
"CME",
50,
);
export const sample_option: Contract = new Option(
"AAPL",
"20251219",
200,
OptionType.Put,
);
export const sample_bond: Contract = new Bond("912828C57");
12 changes: 4 additions & 8 deletions src/tools/market-data-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const DESCRIPTION_TEXT =
const USAGE_TEXT = "Usage: market-data-snapshot.js <options>";
const OPTION_ARGUMENTS: [string, string][] = [
...IBApiNextApp.DEFAULT_CONTRACT_OPTIONS,
["ticks=<ticks>", "Comma separated list of generic ticks to fetch."],
// Snapshot market data subscription is not applicable to generic ticks (Error #321)
// ["ticks=<ticks>", "Comma separated list of generic ticks to fetch."],
];
const EXAMPLE_TEXT =
"market-data-snapshot.js -symbol=AAPL -conid=265598 -sectype=STK -exchange=SMART";
Expand All @@ -30,7 +31,7 @@ class PrintMarketDataSingleApp extends IBApiNextApp {
super(DESCRIPTION_TEXT, USAGE_TEXT, OPTION_ARGUMENTS, EXAMPLE_TEXT);
}

/** The [[Subscription]] on the PnLSingle. */
/** The [[Subscription]] */
private subscription$: Subscription;

/**
Expand All @@ -40,13 +41,8 @@ class PrintMarketDataSingleApp extends IBApiNextApp {
super.start();

this.api
.getMarketDataSnapshot(
this.getContractArg(),
this.cmdLineArgs.ticks as string,
false,
)
.getMarketDataSnapshot(this.getContractArg(), "", false)
.then((marketData) => {
// this.printObject(marketData);
const dataWithTickNames = new Map<string, number>();
marketData.forEach((tick, type) => {
if (type > IBApiNextTickType.API_NEXT_FIRST_TICK_ID) {
Expand Down
3 changes: 2 additions & 1 deletion src/tools/market-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IBApiNextError } from "../api-next";
import {
Instrument,
LocationCode,
MarketScannerUpdate,
ScanCode,
} from "../api-next/market-scanner/market-scanner";
import logger from "../common/logger";
Expand Down Expand Up @@ -48,7 +49,7 @@ class PrintMarketScreenerApp extends IBApiNextApp {
numberOfRows: 20,
})
.subscribe({
next: (data) => {
next: (data: MarketScannerUpdate) => {
this.printObject(data.all);
if (!this.cmdLineArgs.watch) this.stop();
},
Expand Down

0 comments on commit 8dd6f76

Please sign in to comment.