@huskly/ibkr-client 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/dist/cli/account.d.ts +7 -0
- package/dist/cli/account.d.ts.map +1 -0
- package/dist/cli/account.js +27 -0
- package/dist/cli/account.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/orders.d.ts +4 -0
- package/dist/cli/orders.d.ts.map +1 -0
- package/dist/cli/orders.js +5 -0
- package/dist/cli/orders.js.map +1 -0
- package/dist/cli/positions.d.ts +12 -0
- package/dist/cli/positions.d.ts.map +1 -0
- package/dist/cli/positions.js +106 -0
- package/dist/cli/positions.js.map +1 -0
- package/dist/cli/quote.d.ts +4 -0
- package/dist/cli/quote.d.ts.map +1 -0
- package/dist/cli/quote.js +5 -0
- package/dist/cli/quote.js.map +1 -0
- package/dist/cli/shared.d.ts +12 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +31 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +32 -0
- package/dist/format.js.map +1 -0
- package/dist/helpers.d.ts +11 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +34 -0
- package/dist/helpers.js.map +1 -0
- package/dist/ibkr/dhPrime.d.ts +13 -0
- package/dist/ibkr/dhPrime.d.ts.map +1 -0
- package/dist/ibkr/dhPrime.js +44 -0
- package/dist/ibkr/dhPrime.js.map +1 -0
- package/dist/ibkr/ibkrApiTypes.d.ts +70 -0
- package/dist/ibkr/ibkrApiTypes.d.ts.map +1 -0
- package/dist/ibkr/ibkrApiTypes.js +9 -0
- package/dist/ibkr/ibkrApiTypes.js.map +1 -0
- package/dist/ibkr/ibkrClient.d.ts +44 -0
- package/dist/ibkr/ibkrClient.d.ts.map +1 -0
- package/dist/ibkr/ibkrClient.js +351 -0
- package/dist/ibkr/ibkrClient.js.map +1 -0
- package/dist/ibkr/oauthConfig.d.ts +15 -0
- package/dist/ibkr/oauthConfig.d.ts.map +1 -0
- package/dist/ibkr/oauthConfig.js +44 -0
- package/dist/ibkr/oauthConfig.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# IBKR CLI (`@huskly/ibkr-client`)
|
|
2
|
+
|
|
3
|
+
A terminal trading CLI for the **Interactive Brokers Web API**, authenticating
|
|
4
|
+
over **OAuth 1.0a** (no Client Portal Gateway required). Built to mirror the
|
|
5
|
+
architecture of [huskly-cli](https://github.com/felipecsl/huskly-cli) so the two
|
|
6
|
+
can merge into a single multi-broker CLI (IBKR + Schwab) over time.
|
|
7
|
+
|
|
8
|
+
The OAuth 1.0a live-session-token handshake is performed by the
|
|
9
|
+
[`ibkr-client`](https://github.com/art1c0/ibkr-client) package. See the
|
|
10
|
+
[`ibind` OAuth 1.0a wiki](https://github.com/Voyz/ibind/wiki/OAuth-1.0a) for how
|
|
11
|
+
the keys below are generated and registered in the IBKR self-service portal.
|
|
12
|
+
|
|
13
|
+
## Layout
|
|
14
|
+
|
|
15
|
+
The repo is split into a reusable **library** and a thin **CLI**, mirroring
|
|
16
|
+
huskly-cli's `@huskly/schwab-client` + CLI split:
|
|
17
|
+
|
|
18
|
+
| Path | Purpose |
|
|
19
|
+
| ------------------------ | ------------------------------------------------------------------------ |
|
|
20
|
+
| `src/types.ts` | Broker-neutral `BrokerClient` interface + domain types (the merge contract) |
|
|
21
|
+
| `src/ibkr/ibkrClient.ts` | `IbkrClient` — typed wrapper over `ibkr-client` implementing `BrokerClient` |
|
|
22
|
+
| `src/ibkr/oauthConfig.ts`| Builds the OAuth config from `.pem` files + env vars |
|
|
23
|
+
| `src/ibkr/dhPrime.ts` | Extracts the DH prime (hex) from `dhparam.pem` |
|
|
24
|
+
| `src/cli/` | `commander` program, `--broker` flag, and command handlers |
|
|
25
|
+
|
|
26
|
+
The `*.pem` files (`private_signature.pem`, `private_encryption.pem`,
|
|
27
|
+
`dhparam.pem`, plus the public keys) are the cryptographic material from the
|
|
28
|
+
wiki setup step and are git-ignored.
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Provide the account-specific secrets, either by copying the template:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cp .env.example .env
|
|
40
|
+
# then edit .env
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
or by exporting them in your shell:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export IBIND_OAUTH1A_CONSUMER_KEY=... # 9-char consumer key from IBKR
|
|
47
|
+
export IBIND_OAUTH1A_ACCESS_TOKEN=... # access token from the portal
|
|
48
|
+
export IBIND_OAUTH1A_ACCESS_TOKEN_SECRET=... # access token secret from the portal
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Optional environment variables:
|
|
52
|
+
|
|
53
|
+
- `IBIND_OAUTH1A_REALM` — OAuth realm (defaults to `limited_poa` for the
|
|
54
|
+
individual self-service flow).
|
|
55
|
+
- `IBKR_KEYS_DIR` — directory holding the `.pem` files (defaults to the current
|
|
56
|
+
working directory).
|
|
57
|
+
- `IBKR_ACCOUNT_ID` — target a specific account (otherwise the first is used).
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
Run in development (via `tsx`, no build step):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run dev -- account
|
|
65
|
+
npm run dev -- positions
|
|
66
|
+
npm run dev -- positions AAPL # filter by symbol
|
|
67
|
+
npm run dev -- positions --type EQUITY # filter by asset type
|
|
68
|
+
npm run dev -- positions --csv # CSV instead of a table
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or build and use the `ibkr-cli` binary:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run build
|
|
75
|
+
node dist/cli/index.js account
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A global `--broker <ibkr|schwab>` flag (default `ibkr`) selects the broker.
|
|
79
|
+
Only IBKR is implemented today; `schwab` is reserved for the huskly-cli merge.
|
|
80
|
+
|
|
81
|
+
Example output:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
💰 Account Summary
|
|
85
|
+
|
|
86
|
+
Authenticated: yes Competing: no
|
|
87
|
+
Account: U********
|
|
88
|
+
──────────────────────────────────────────────────
|
|
89
|
+
Net Liquidation: $123,456.78
|
|
90
|
+
Available Funds: $...
|
|
91
|
+
...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm run lint # eslint
|
|
98
|
+
npm run format # prettier --write
|
|
99
|
+
npm run typecheck # tsc --noEmit
|
|
100
|
+
npm run build # tsc -> dist/
|
|
101
|
+
npm run check # lint + format:check + typecheck
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
CI (`.github/workflows/ci.yml`) runs lint, format check, typecheck, and build on
|
|
105
|
+
every push and pull request, plus [gitleaks](https://github.com/gitleaks/gitleaks)
|
|
106
|
+
to guard against committed secrets.
|
|
107
|
+
|
|
108
|
+
## Security notes
|
|
109
|
+
|
|
110
|
+
- The private keys and `.env` are git-ignored — do not commit them.
|
|
111
|
+
- Credentials are read from the environment, never hardcoded.
|
|
112
|
+
- CI scans every change with gitleaks to catch accidentally committed secrets.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BrokerName } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Show auth status and account balances.
|
|
4
|
+
* Ports the account-summary portion of main.py's main().
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleAccount(broker: BrokerName): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/cli/account.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBrE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { apiClient } from "./shared.js";
|
|
3
|
+
import { fmtMoney } from "../format.js";
|
|
4
|
+
/**
|
|
5
|
+
* Show auth status and account balances.
|
|
6
|
+
* Ports the account-summary portion of main.py's main().
|
|
7
|
+
*/
|
|
8
|
+
export async function handleAccount(broker) {
|
|
9
|
+
const api = await apiClient(broker);
|
|
10
|
+
const auth = await api.getAuthStatus();
|
|
11
|
+
const accountId = await api.getAccountId();
|
|
12
|
+
const balances = await api.getAccountBalances();
|
|
13
|
+
console.log(chalk.bold("\n💰 Account Summary\n"));
|
|
14
|
+
console.log(chalk.gray("Authenticated: ") +
|
|
15
|
+
(auth.authenticated ? chalk.green("yes") : chalk.red("no")) +
|
|
16
|
+
chalk.gray(" Competing: ") +
|
|
17
|
+
(auth.competing ? chalk.red("yes") : chalk.green("no")));
|
|
18
|
+
console.log(chalk.gray("Account: ") + chalk.cyan(accountId));
|
|
19
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
20
|
+
console.log(`Net Liquidation: ${chalk.green(fmtMoney(balances.netLiquidation))}`);
|
|
21
|
+
console.log(`Available Funds: ${chalk.blue(fmtMoney(balances.availableFunds))}`);
|
|
22
|
+
console.log(`Buying Power: ${chalk.magenta(fmtMoney(balances.buyingPower))}`);
|
|
23
|
+
console.log(`Cash Balance: ${chalk.yellow(fmtMoney(balances.cashBalance))}`);
|
|
24
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
25
|
+
console.log();
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/cli/account.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB;IACpD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,kBAAkB,EAAE,CAAC;IAEhD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAC3B,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC;QAC5B,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAC1D,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { handleAccount } from "./account.js";
|
|
5
|
+
import { handlePositions } from "./positions.js";
|
|
6
|
+
import { handleQuote } from "./quote.js";
|
|
7
|
+
import { handleOrders } from "./orders.js";
|
|
8
|
+
import { resolveBroker } from "./shared.js";
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name("ibkr-cli")
|
|
12
|
+
.description("Terminal-based trading tools for Interactive Brokers (and, later, Schwab)")
|
|
13
|
+
.version("0.1.0")
|
|
14
|
+
.option("--broker <name>", "Broker to use: ibkr or schwab", "ibkr");
|
|
15
|
+
/** The resolved broker from the global --broker option. */
|
|
16
|
+
const broker = () => resolveBroker(program.opts().broker);
|
|
17
|
+
program
|
|
18
|
+
.command("account")
|
|
19
|
+
.description("Show account balances and authentication status")
|
|
20
|
+
.action(async () => {
|
|
21
|
+
await handleAccount(broker());
|
|
22
|
+
});
|
|
23
|
+
program
|
|
24
|
+
.command("positions")
|
|
25
|
+
.description("Show account positions, optionally filtered by symbol or type")
|
|
26
|
+
.argument("[symbol]", "Optional symbol to filter positions")
|
|
27
|
+
.option("-t, --type <type>", "Filter by asset type (e.g., OPTION, EQUITY)")
|
|
28
|
+
.option("--csv", "Output in CSV format instead of table")
|
|
29
|
+
.action(async (symbol, options) => {
|
|
30
|
+
await handlePositions(broker(), symbol, options);
|
|
31
|
+
});
|
|
32
|
+
program
|
|
33
|
+
.command("quote")
|
|
34
|
+
.description("Get current price quotes for one or more symbols (not implemented yet)")
|
|
35
|
+
.argument("<symbols...>", "Symbols to quote")
|
|
36
|
+
.action((symbols) => {
|
|
37
|
+
handleQuote(broker(), symbols);
|
|
38
|
+
});
|
|
39
|
+
program
|
|
40
|
+
.command("orders")
|
|
41
|
+
.description("List account orders (not implemented yet)")
|
|
42
|
+
.action(() => {
|
|
43
|
+
handleOrders(broker());
|
|
44
|
+
});
|
|
45
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
console.error(chalk.red("Error:"), message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,2EAA2E,CAAC;KACxF,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,iBAAiB,EAAE,+BAA+B,EAAE,MAAM,CAAC,CAAC;AAEtE,2DAA2D;AAC3D,MAAM,MAAM,GAAG,GAAqC,EAAE,CACpD,aAAa,CAAC,OAAO,CAAC,IAAI,EAAuB,CAAC,MAAM,CAAC,CAAC;AAE5D,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,iDAAiD,CAAC;KAC9D,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,+DAA+D,CAAC;KAC5E,QAAQ,CAAC,UAAU,EAAE,qCAAqC,CAAC;KAC3D,MAAM,CAAC,mBAAmB,EAAE,6CAA6C,CAAC;KAC1E,MAAM,CAAC,OAAO,EAAE,uCAAuC,CAAC;KACxD,MAAM,CAAC,KAAK,EAAE,MAA0B,EAAE,OAAyC,EAAE,EAAE;IACtF,MAAM,eAAe,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,wEAAwE,CAAC;KACrF,QAAQ,CAAC,cAAc,EAAE,kBAAkB,CAAC;KAC5C,MAAM,CAAC,CAAC,OAAiB,EAAE,EAAE;IAC5B,WAAW,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,GAAG,EAAE;IACX,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;AACzB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orders.d.ts","sourceRoot":"","sources":["../../src/cli/orders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAEtD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orders.js","sourceRoot":"","sources":["../../src/cli/orders.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,MAAM,UAAU,YAAY,CAAC,OAAmB;IAC9C,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BrokerName } from "../types.js";
|
|
2
|
+
interface PositionsOptions {
|
|
3
|
+
type?: string;
|
|
4
|
+
csv?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Show account positions as a table (or CSV).
|
|
8
|
+
* Ports render_positions() from main.py.
|
|
9
|
+
*/
|
|
10
|
+
export declare function handlePositions(broker: BrokerName, symbol: string | undefined, options: PositionsOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=positions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"positions.d.ts","sourceRoot":"","sources":["../../src/cli/positions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAC;AAE9D,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AA6BD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAef"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { apiClient } from "./shared.js";
|
|
3
|
+
import { fmtMoney, fmtPct, pad, pnlColor } from "../format.js";
|
|
4
|
+
const HEADERS = [
|
|
5
|
+
"Symbol",
|
|
6
|
+
"Type",
|
|
7
|
+
"Long",
|
|
8
|
+
"Short",
|
|
9
|
+
"Avg Price",
|
|
10
|
+
"Cur Price",
|
|
11
|
+
"Mkt Value",
|
|
12
|
+
"Day P/L",
|
|
13
|
+
"P/L Open",
|
|
14
|
+
"P/L %",
|
|
15
|
+
];
|
|
16
|
+
/** Columns 0 (Symbol) and 1 (Type) are left-aligned; the rest right-aligned. */
|
|
17
|
+
const LEFT_ALIGNED = new Set([0, 1]);
|
|
18
|
+
/** Open P/L as a fraction of cost basis (marketValue net of open P/L). */
|
|
19
|
+
function plPercent(pos) {
|
|
20
|
+
const costBasis = pos.marketValue - pos.openProfitLoss;
|
|
21
|
+
return costBasis ? (pos.openProfitLoss / costBasis) * 100 : null;
|
|
22
|
+
}
|
|
23
|
+
/** Escape a value for CSV output. */
|
|
24
|
+
function escapeCsv(value) {
|
|
25
|
+
return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Show account positions as a table (or CSV).
|
|
29
|
+
* Ports render_positions() from main.py.
|
|
30
|
+
*/
|
|
31
|
+
export async function handlePositions(broker, symbol, options) {
|
|
32
|
+
const api = await apiClient(broker);
|
|
33
|
+
let positions = await api.getPositions(symbol);
|
|
34
|
+
if (options.type) {
|
|
35
|
+
const upper = options.type.toUpperCase();
|
|
36
|
+
positions = positions.filter((p) => p.assetType.toUpperCase() === upper);
|
|
37
|
+
}
|
|
38
|
+
positions = positions.sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
39
|
+
if (options.csv) {
|
|
40
|
+
renderCsv(positions);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
renderTable(symbol, options.type, positions);
|
|
44
|
+
}
|
|
45
|
+
function renderCsv(positions) {
|
|
46
|
+
if (positions.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
console.log(HEADERS.join(","));
|
|
49
|
+
for (const p of positions) {
|
|
50
|
+
const pct = plPercent(p);
|
|
51
|
+
console.log([
|
|
52
|
+
escapeCsv(p.symbol),
|
|
53
|
+
escapeCsv(p.assetType),
|
|
54
|
+
p.longQuantity || "",
|
|
55
|
+
p.shortQuantity || "",
|
|
56
|
+
p.averagePrice.toFixed(2),
|
|
57
|
+
p.marketPrice.toFixed(2),
|
|
58
|
+
p.marketValue.toFixed(2),
|
|
59
|
+
p.currentDayProfitLoss.toFixed(2),
|
|
60
|
+
p.openProfitLoss.toFixed(2),
|
|
61
|
+
pct === null ? "" : pct.toFixed(2),
|
|
62
|
+
].join(","));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function renderTable(symbol, type, positions) {
|
|
66
|
+
const filters = [symbol?.toUpperCase(), type?.toUpperCase()].filter(Boolean);
|
|
67
|
+
const filterText = filters.length ? `: ${filters.join(", ")}` : "";
|
|
68
|
+
console.log(chalk.bold(`\nAccount Positions${filterText}\n`));
|
|
69
|
+
if (positions.length === 0) {
|
|
70
|
+
console.log(chalk.yellow(" (no open positions)"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Each cell is [text, colorFn]; colorize after width is measured on raw text.
|
|
74
|
+
const rows = positions.map((p) => {
|
|
75
|
+
const pct = plPercent(p);
|
|
76
|
+
return [
|
|
77
|
+
[p.symbol, chalk.cyan],
|
|
78
|
+
[p.assetType, chalk.white],
|
|
79
|
+
[p.longQuantity > 0 ? String(p.longQuantity) : "-", chalk.green],
|
|
80
|
+
[p.shortQuantity > 0 ? String(p.shortQuantity) : "-", chalk.red],
|
|
81
|
+
[fmtMoney(p.averagePrice), chalk.white],
|
|
82
|
+
[fmtMoney(p.marketPrice), chalk.white],
|
|
83
|
+
[fmtMoney(p.marketValue), pnlColor(p.marketValue)],
|
|
84
|
+
[fmtMoney(p.currentDayProfitLoss, true), pnlColor(p.currentDayProfitLoss)],
|
|
85
|
+
[fmtMoney(p.openProfitLoss, true), pnlColor(p.openProfitLoss)],
|
|
86
|
+
[fmtPct(pct), pnlColor(pct)],
|
|
87
|
+
];
|
|
88
|
+
});
|
|
89
|
+
const widths = HEADERS.map((h) => h.length);
|
|
90
|
+
for (const row of rows) {
|
|
91
|
+
row.forEach(([text], i) => {
|
|
92
|
+
widths[i] = Math.max(widths[i] ?? 0, text.length);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const align = (i) => (LEFT_ALIGNED.has(i) ? "left" : "right");
|
|
96
|
+
const gap = " ";
|
|
97
|
+
const headerLine = HEADERS.map((h, i) => chalk.dim(pad(h, widths[i] ?? 0, align(i)))).join(gap);
|
|
98
|
+
const totalWidth = widths.reduce((sum, w) => sum + w, 0) + gap.length * (widths.length - 1);
|
|
99
|
+
console.log(headerLine);
|
|
100
|
+
console.log(chalk.dim("-".repeat(totalWidth)));
|
|
101
|
+
for (const row of rows) {
|
|
102
|
+
console.log(row.map(([text, color], i) => color(pad(text, widths[i] ?? 0, align(i)))).join(gap));
|
|
103
|
+
}
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=positions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"positions.js","sourceRoot":"","sources":["../../src/cli/positions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote.d.ts","sourceRoot":"","sources":["../../src/cli/quote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAEzE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../src/cli/quote.ts"],"names":[],"mappings":"AAEA,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,OAAmB,EAAE,QAAkB;IACjE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BrokerClient, BrokerName } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve an authenticated {@link BrokerClient} for the requested broker.
|
|
4
|
+
*
|
|
5
|
+
* Today only IBKR is implemented. The `schwab` branch is intentionally a
|
|
6
|
+
* placeholder: when this is merged into huskly-cli it will return a thin
|
|
7
|
+
* adapter over huskly's existing `SchwabClient`, satisfying the same interface.
|
|
8
|
+
*/
|
|
9
|
+
export declare function apiClient(broker?: BrokerName): Promise<BrokerClient>;
|
|
10
|
+
/** Resolve the broker from a Commander option, validating the value. */
|
|
11
|
+
export declare function resolveBroker(value: string | undefined): BrokerName;
|
|
12
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cli/shared.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,MAAM,GAAE,UAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAclF;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAMnE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IbkrClient } from "../ibkr/ibkrClient.js";
|
|
2
|
+
import { buildOauthConfig } from "../ibkr/oauthConfig.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an authenticated {@link BrokerClient} for the requested broker.
|
|
5
|
+
*
|
|
6
|
+
* Today only IBKR is implemented. The `schwab` branch is intentionally a
|
|
7
|
+
* placeholder: when this is merged into huskly-cli it will return a thin
|
|
8
|
+
* adapter over huskly's existing `SchwabClient`, satisfying the same interface.
|
|
9
|
+
*/
|
|
10
|
+
export async function apiClient(broker = "ibkr") {
|
|
11
|
+
switch (broker) {
|
|
12
|
+
case "ibkr": {
|
|
13
|
+
const client = new IbkrClient(buildOauthConfig());
|
|
14
|
+
await client.init();
|
|
15
|
+
return client;
|
|
16
|
+
}
|
|
17
|
+
case "schwab":
|
|
18
|
+
throw new Error("Broker 'schwab' is not supported yet. It will be wired up when this CLI is merged into huskly-cli.");
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unknown broker: ${String(broker)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Resolve the broker from a Commander option, validating the value. */
|
|
24
|
+
export function resolveBroker(value) {
|
|
25
|
+
const broker = (value ?? "ibkr").toLowerCase();
|
|
26
|
+
if (broker !== "ibkr" && broker !== "schwab") {
|
|
27
|
+
throw new Error(`Invalid --broker '${broker}'. Expected 'ibkr' or 'schwab'.`);
|
|
28
|
+
}
|
|
29
|
+
return broker;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/cli/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAqB,MAAM;IACzD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAClD,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,KAAK,QAAQ;YACX,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,iCAAiC,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal formatting helpers, ported from main.py (fmt_money / fmt_pct /
|
|
3
|
+
* P&L coloring), using chalk instead of raw ANSI codes.
|
|
4
|
+
*/
|
|
5
|
+
/** Format a dollar amount. `signed` forces a leading +/- (for P&L). */
|
|
6
|
+
export declare function fmtMoney(value: number | null | undefined, signed?: boolean): string;
|
|
7
|
+
/** Format a percentage with an explicit +/- sign. */
|
|
8
|
+
export declare function fmtPct(value: number | null | undefined): string;
|
|
9
|
+
/** Green for non-negative P&L, red for losses. */
|
|
10
|
+
export declare function pnlColor(value: number | null | undefined): (text: string) => string;
|
|
11
|
+
/** Pad `text` into `width`, left- or right-aligned. */
|
|
12
|
+
export declare function pad(text: string, width: number, align?: "left" | "right"): string;
|
|
13
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,UAAQ,GAAG,MAAM,CAQjF;AAED,qDAAqD;AACrD,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAI/D;AAED,kDAAkD;AAClD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAEnF;AAED,uDAAuD;AACvD,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,OAAgB,GAAG,MAAM,CAEzF"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/**
|
|
3
|
+
* Terminal formatting helpers, ported from main.py (fmt_money / fmt_pct /
|
|
4
|
+
* P&L coloring), using chalk instead of raw ANSI codes.
|
|
5
|
+
*/
|
|
6
|
+
/** Format a dollar amount. `signed` forces a leading +/- (for P&L). */
|
|
7
|
+
export function fmtMoney(value, signed = false) {
|
|
8
|
+
if (value === null || value === undefined)
|
|
9
|
+
return "-";
|
|
10
|
+
const sign = value < 0 ? "-" : signed ? "+" : "";
|
|
11
|
+
const abs = Math.abs(value).toLocaleString("en-US", {
|
|
12
|
+
minimumFractionDigits: 2,
|
|
13
|
+
maximumFractionDigits: 2,
|
|
14
|
+
});
|
|
15
|
+
return `${sign}$${abs}`;
|
|
16
|
+
}
|
|
17
|
+
/** Format a percentage with an explicit +/- sign. */
|
|
18
|
+
export function fmtPct(value) {
|
|
19
|
+
if (value === null || value === undefined)
|
|
20
|
+
return "-";
|
|
21
|
+
const sign = value < 0 ? "-" : "+";
|
|
22
|
+
return `${sign}${Math.abs(value).toFixed(2)}%`;
|
|
23
|
+
}
|
|
24
|
+
/** Green for non-negative P&L, red for losses. */
|
|
25
|
+
export function pnlColor(value) {
|
|
26
|
+
return (value ?? 0) < 0 ? chalk.red : chalk.green;
|
|
27
|
+
}
|
|
28
|
+
/** Pad `text` into `width`, left- or right-aligned. */
|
|
29
|
+
export function pad(text, width, align = "left") {
|
|
30
|
+
return align === "right" ? text.padStart(width) : text.padEnd(width);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;GAGG;AAEH,uEAAuE;AACvE,MAAM,UAAU,QAAQ,CAAC,KAAgC,EAAE,MAAM,GAAG,KAAK;IACvE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACtD,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE;QAClD,qBAAqB,EAAE,CAAC;QACxB,qBAAqB,EAAE,CAAC;KACzB,CAAC,CAAC;IACH,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;AAC1B,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,MAAM,CAAC,KAAgC;IACrD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACtD,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACnC,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACjD,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,QAAQ,CAAC,KAAgC;IACvD,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,QAA0B,MAAM;IAC/E,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Small shared utilities. */
|
|
2
|
+
/** Throw if a value is null/undefined, otherwise narrow it to non-nullable. */
|
|
3
|
+
export declare function ensure<T>(value: T | null | undefined, message?: string): T;
|
|
4
|
+
/**
|
|
5
|
+
* IBKR asset-class codes -> human-readable type labels.
|
|
6
|
+
* Ported from main.py's ASSET_CLASS_LABELS.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ASSET_CLASS_LABELS: Record<string, string>;
|
|
9
|
+
/** Coerce an unknown (string | number | null) into a number, defaulting to 0. */
|
|
10
|
+
export declare function toNumber(value: unknown): number;
|
|
11
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAK1E;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAUrD,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAO/C"}
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Small shared utilities. */
|
|
2
|
+
/** Throw if a value is null/undefined, otherwise narrow it to non-nullable. */
|
|
3
|
+
export function ensure(value, message) {
|
|
4
|
+
if (value === null || value === undefined) {
|
|
5
|
+
throw new Error(message ?? "Expected value to be non-null/non-undefined");
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* IBKR asset-class codes -> human-readable type labels.
|
|
11
|
+
* Ported from main.py's ASSET_CLASS_LABELS.
|
|
12
|
+
*/
|
|
13
|
+
export const ASSET_CLASS_LABELS = {
|
|
14
|
+
STK: "EQUITY",
|
|
15
|
+
OPT: "OPTION",
|
|
16
|
+
FOP: "FUTURES OPTION",
|
|
17
|
+
FUT: "FUTURE",
|
|
18
|
+
FUND: "COLLECTIVE_INVESTMENT",
|
|
19
|
+
BOND: "BOND",
|
|
20
|
+
WAR: "WARRANT",
|
|
21
|
+
CASH: "FOREX",
|
|
22
|
+
CFD: "CFD",
|
|
23
|
+
};
|
|
24
|
+
/** Coerce an unknown (string | number | null) into a number, defaulting to 0. */
|
|
25
|
+
export function toNumber(value) {
|
|
26
|
+
if (typeof value === "number")
|
|
27
|
+
return value;
|
|
28
|
+
if (typeof value === "string") {
|
|
29
|
+
const n = parseFloat(value);
|
|
30
|
+
return Number.isNaN(n) ? 0 : n;
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,+EAA+E;AAC/E,MAAM,UAAU,MAAM,CAAI,KAA2B,EAAE,OAAgB;IACrE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,6CAA6C,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,OAAO;IACb,GAAG,EAAE,KAAK;CACX,CAAC;AAEF,iFAAiF;AACjF,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the Diffie-Hellman prime (hex) from a dhparam.pem.
|
|
3
|
+
*
|
|
4
|
+
* IBKR's OAuth 1.0a live-session-token exchange (via `ibkr-client`) needs the DH
|
|
5
|
+
* prime as a continuous hex string — it does `BigInt('0x' + dhPrime)`. Ports
|
|
6
|
+
* dh_prime.py from the Python PoC, but parses the PEM's ASN.1 DER directly
|
|
7
|
+
* instead of shelling out to `openssl`.
|
|
8
|
+
*
|
|
9
|
+
* A DH parameters structure is: SEQUENCE { INTEGER prime, INTEGER generator }.
|
|
10
|
+
* We read the first INTEGER and return it as lowercase hex.
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractDhPrime(pem: string): string;
|
|
13
|
+
//# sourceMappingURL=dhPrime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dhPrime.d.ts","sourceRoot":"","sources":["../../src/ibkr/dhPrime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAcH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsBlD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the Diffie-Hellman prime (hex) from a dhparam.pem.
|
|
3
|
+
*
|
|
4
|
+
* IBKR's OAuth 1.0a live-session-token exchange (via `ibkr-client`) needs the DH
|
|
5
|
+
* prime as a continuous hex string — it does `BigInt('0x' + dhPrime)`. Ports
|
|
6
|
+
* dh_prime.py from the Python PoC, but parses the PEM's ASN.1 DER directly
|
|
7
|
+
* instead of shelling out to `openssl`.
|
|
8
|
+
*
|
|
9
|
+
* A DH parameters structure is: SEQUENCE { INTEGER prime, INTEGER generator }.
|
|
10
|
+
* We read the first INTEGER and return it as lowercase hex.
|
|
11
|
+
*/
|
|
12
|
+
/** Read an ASN.1 DER length field at `offset`. Returns the value length and header size. */
|
|
13
|
+
function readLength(der, offset) {
|
|
14
|
+
const first = der[offset] ?? 0;
|
|
15
|
+
if (first < 0x80)
|
|
16
|
+
return { length: first, headerLen: 1 };
|
|
17
|
+
const numBytes = first & 0x7f;
|
|
18
|
+
let length = 0;
|
|
19
|
+
for (let i = 1; i <= numBytes; i++) {
|
|
20
|
+
length = (length << 8) | (der[offset + i] ?? 0);
|
|
21
|
+
}
|
|
22
|
+
return { length, headerLen: 1 + numBytes };
|
|
23
|
+
}
|
|
24
|
+
export function extractDhPrime(pem) {
|
|
25
|
+
const base64 = pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
|
|
26
|
+
const der = Buffer.from(base64, "base64");
|
|
27
|
+
let offset = 0;
|
|
28
|
+
if (der[offset] !== 0x30) {
|
|
29
|
+
throw new Error("Invalid dhparam.pem: expected an ASN.1 SEQUENCE");
|
|
30
|
+
}
|
|
31
|
+
offset += 1 + readLength(der, offset + 1).headerLen; // skip SEQUENCE tag + length
|
|
32
|
+
if (der[offset] !== 0x02) {
|
|
33
|
+
throw new Error("Invalid dhparam.pem: expected an INTEGER (the prime)");
|
|
34
|
+
}
|
|
35
|
+
offset += 1;
|
|
36
|
+
const { length, headerLen } = readLength(der, offset);
|
|
37
|
+
offset += headerLen;
|
|
38
|
+
let prime = der.subarray(offset, offset + length);
|
|
39
|
+
// Strip the leading sign byte DER adds to keep the integer positive.
|
|
40
|
+
if (prime[0] === 0x00)
|
|
41
|
+
prime = prime.subarray(1);
|
|
42
|
+
return prime.toString("hex");
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=dhPrime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dhPrime.js","sourceRoot":"","sources":["../../src/ibkr/dhPrime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,4FAA4F;AAC5F,SAAS,UAAU,CAAC,GAAW,EAAE,MAAc;IAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACzD,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;IAC9B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE1C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,IAAI,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,6BAA6B;IAElF,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,IAAI,CAAC,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,IAAI,SAAS,CAAC;IAEpB,IAAI,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAClD,qEAAqE;IACrE,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEjD,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|