tradestation-client 1.0.15 → 1.1.2
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/dist/authMiddleware.d.ts.map +1 -1
- package/dist/authMiddleware.js +2 -1
- package/dist/authMiddleware.js.map +1 -1
- package/dist/commands/barsCommand.d.ts.map +1 -1
- package/dist/commands/barsCommand.js +9 -5
- package/dist/commands/barsCommand.js.map +1 -1
- package/dist/{tradestation-client.d.ts → index.d.ts} +1 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/{tradestation-client.js → index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/scripts/downloadOpenAPI.d.ts.map +1 -0
- package/dist/scripts/downloadOpenAPI.js.map +1 -0
- package/dist/spike.js +1 -1
- package/dist/spike.js.map +1 -1
- package/dist/test/cli.test.d.ts +2 -0
- package/dist/test/cli.test.d.ts.map +1 -0
- package/dist/test/cli.test.js +32 -0
- package/dist/test/cli.test.js.map +1 -0
- package/package.json +14 -7
- package/.env.example +0 -2
- package/.prettierrc +0 -5
- package/authMiddleware.ts +0 -243
- package/cli.ts +0 -19
- package/commands/auth.ts +0 -13
- package/commands/barsCommand.ts +0 -83
- package/config.ts +0 -2
- package/dist/config.d.ts +0 -3
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -3
- package/dist/config.js.map +0 -1
- package/dist/downloadOpenAPI.d.ts.map +0 -1
- package/dist/downloadOpenAPI.js.map +0 -1
- package/dist/tradestation-client.d.ts.map +0 -1
- package/dist/tradestation-client.js.map +0 -1
- package/downloadOpenAPI.ts +0 -31
- package/generated/tradestation-api.d.ts +0 -8115
- package/openapi.json +0 -12928
- package/spike.ts +0 -30
- package/tradestation-client.ts +0 -15
- package/tsconfig.json +0 -42
- /package/dist/{downloadOpenAPI.d.ts → scripts/downloadOpenAPI.d.ts} +0 -0
- /package/dist/{downloadOpenAPI.js → scripts/downloadOpenAPI.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authMiddleware.d.ts","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"authMiddleware.d.ts","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAO/C,KAAK,YAAY,GAAG;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,KAAK,QAAQ,GAAG,YAAY,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAQD,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAO5D;AA8HD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkB5F;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,UAAU,CA6BvF"}
|
package/dist/authMiddleware.js
CHANGED
|
@@ -3,7 +3,8 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import fastify from 'fastify';
|
|
5
5
|
import open from 'open';
|
|
6
|
-
|
|
6
|
+
const redirectPort = 31022;
|
|
7
|
+
const redirectUri = `http://localhost:${redirectPort}`;
|
|
7
8
|
const authFilePath = join(process.cwd(), 'tradestation-auth.json');
|
|
8
9
|
function generateRandomState() {
|
|
9
10
|
return randomBytes(32).toString('base64url');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,
|
|
1
|
+
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../authMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"barsCommand.d.ts","sourceRoot":"","sources":["../../commands/barsCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAA;AAoB3C,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"barsCommand.d.ts","sourceRoot":"","sources":["../../commands/barsCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAA;AAoB3C,eAAO,MAAM,WAAW,SAmEpB,CAAA"}
|
|
@@ -7,7 +7,7 @@ const units = ['Minute', 'Hour', 'Daily', 'Weekly', 'Monthly'];
|
|
|
7
7
|
async function authenticateOrFail() {
|
|
8
8
|
const authData = await tryLoadAuth();
|
|
9
9
|
if (!authData) {
|
|
10
|
-
console.error('No authentication data found. Please run the auth command first.');
|
|
10
|
+
console.error('No authentication data found. Please run the auth command first or provide credentials.');
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
return authData;
|
|
@@ -16,16 +16,20 @@ export const barsCommand = new Command('bars')
|
|
|
16
16
|
.description('Download market data bars from TradeStation API')
|
|
17
17
|
.argument('<symbol>', 'Market symbol to download data for')
|
|
18
18
|
.option('--interval <interval>', 'Bar interval (e.g., 1, 5, 1440, ...)', '1')
|
|
19
|
-
.addOption(new Option('--unit <unit>', 'Bar unit').choices(units).default(
|
|
20
|
-
.option('--barsBack <barsBack>', 'Number of bars to retrieve', parseInt
|
|
19
|
+
.addOption(new Option('--unit <unit>', 'Bar unit').choices(units).default('Daily'))
|
|
20
|
+
.option('--barsBack <barsBack>', 'Number of bars to retrieve', parseInt)
|
|
21
21
|
.option('--firstDate <firstDate>', 'First date for bar data (YYYY-MM-DD)')
|
|
22
22
|
.option('--lastDate <lastDate>', 'Last date for bar data (YYYY-MM-DD)')
|
|
23
23
|
.addOption(new Option('--sessionTemplate <sessionTemplate>', 'Session template to use.').choices(sessions).default(sessions[0]))
|
|
24
|
+
// Optional authentication options
|
|
24
25
|
.addOption(new Option('--clientId <clientId>', 'TradeStation Client ID'))
|
|
25
26
|
.addOption(new Option('--clientSecret <clientSecret>', 'TradeStation Client Secret'))
|
|
26
27
|
.addOption(new Option('--refreshToken <refreshToken>', 'TradeStation Refresh Token'))
|
|
28
|
+
// Output options
|
|
29
|
+
.option('-o --output <output>', 'Output file name, defaults to <symbol>.json')
|
|
27
30
|
.action(async function (symbol, options) {
|
|
28
31
|
const environment = this.optsWithGlobals().environment;
|
|
32
|
+
const output = options.output || `${symbol}.json`;
|
|
29
33
|
let client;
|
|
30
34
|
if (options.clientId && options.clientSecret && options.refreshToken) {
|
|
31
35
|
client = new TradeStationClient({
|
|
@@ -53,7 +57,7 @@ export const barsCommand = new Command('bars')
|
|
|
53
57
|
barsback: options.barsBack,
|
|
54
58
|
sessiontemplate: options.sessionTemplate,
|
|
55
59
|
});
|
|
56
|
-
await fsp.writeFile(
|
|
57
|
-
console.log(`Saved ${bars.Bars.length} bars to ${
|
|
60
|
+
await fsp.writeFile(output, JSON.stringify(bars.Bars, null, 2));
|
|
61
|
+
console.log(`Saved ${bars.Bars.length} bars to ${output}`);
|
|
58
62
|
});
|
|
59
63
|
//# sourceMappingURL=barsCommand.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"barsCommand.js","sourceRoot":"","sources":["../../commands/barsCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAElD,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;AAEnF,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAA;AAE9D,KAAK,UAAU,kBAAkB;IAC/B,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAA;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"barsCommand.js","sourceRoot":"","sources":["../../commands/barsCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { paths } from './generated/tradestation-api.js';
|
|
2
2
|
export declare function createTradeStationClient(clientId: string, clientSecret: string): import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
3
|
-
//# sourceMappingURL=
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAA;AAG5D,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,gEAOrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AAGxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE1D,MAAM,UAAU,wBAAwB,CACtC,QAAgB,EAChB,YAAoB;IAEpB,MAAM,MAAM,GAAG,YAAY,CAAQ;QACjC,OAAO,EAAE,8BAA8B;KACxC,CAAC,CAAA;IACF,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAA;IACxD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadOpenAPI.d.ts","sourceRoot":"","sources":["../../scripts/downloadOpenAPI.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadOpenAPI.js","sourceRoot":"","sources":["../../scripts/downloadOpenAPI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE1C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;IACpC,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,QAAQ;IACjB,IAAI,EAAE,CAAC,sBAAsB,CAAC;CAC/B,CAAC,CAAA;AACF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;AAEpC,MAAM,IAAI,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAA;AAEnE,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAA;AAE1C,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;AAErD,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAA;AAE1D,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAA;AACtC,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;AAE5C,IAAI,cAAc,EAAE,CAAC;IACnB,MAAM,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAA;IACnE,MAAM,MAAM,CAAC,cAAc,CAAC,CAAA;IAC5B,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAA;AAC1D,CAAC;KAAM,CAAC;IACN,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
package/dist/spike.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
process.loadEnvFile();
|
|
3
|
-
import { createTradeStationClient } from './
|
|
3
|
+
import { createTradeStationClient } from './index.js';
|
|
4
4
|
const tradestationClient = createTradeStationClient(process.env.CLIENT_ID, process.env.CLIENT_SECRET);
|
|
5
5
|
const { data } = await tradestationClient.GET('/v3/marketdata/stream/barcharts/{symbol}', {
|
|
6
6
|
params: {
|
package/dist/spike.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spike.js","sourceRoot":"","sources":["../spike.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAA;AAElC,OAAO,CAAC,WAAW,EAAE,CAAA;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"spike.js","sourceRoot":"","sources":["../spike.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAA;AAElC,OAAO,CAAC,WAAW,EAAE,CAAA;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAErD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC,SAAU,EAAE,OAAO,CAAC,GAAG,CAAC,aAAc,CAAC,CAAA;AAEvG,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,0CAA0C,EAAE;IACxF,MAAM,EAAE;QACN,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QACvB,KAAK,EAAE;YACL,QAAQ,EAAE,GAAG;YACb,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,GAAG;YACb,eAAe,EAAE,YAAY;SAC9B;KACF;IACD,OAAO,EAAE,QAAQ;CAClB,CAAC,CAAA;AAEF,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAK,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AAC9C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../test/cli.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, test, beforeEach, mock } from 'node:test';
|
|
2
|
+
describe('CLI Tests', () => {
|
|
3
|
+
describe('auth command', () => {
|
|
4
|
+
beforeEach(t => {
|
|
5
|
+
mock.reset();
|
|
6
|
+
mock.module('../authMiddleware.js', {
|
|
7
|
+
namedExports: {
|
|
8
|
+
authenticate: mock.fn(),
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
test('should require clientId and clientSecret', async (t) => {
|
|
13
|
+
const { authCommand } = await import('../commands/auth.js');
|
|
14
|
+
t.assert.throws(() => authCommand.exitOverride().parse([], { from: 'user' }));
|
|
15
|
+
});
|
|
16
|
+
test('should fail with just clientId', async (t) => {
|
|
17
|
+
const { authCommand } = await import('../commands/auth.js');
|
|
18
|
+
t.assert.throws(() => authCommand.exitOverride().parse(['--clientId', 'myClientId'], { from: 'user' }));
|
|
19
|
+
});
|
|
20
|
+
test('should fail with just clientSecret', async (t) => {
|
|
21
|
+
const { authCommand } = await import('../commands/auth.js');
|
|
22
|
+
t.assert.throws(() => authCommand.exitOverride().parse(['--clientSecret', 'myClientSecret'], { from: 'user' }));
|
|
23
|
+
});
|
|
24
|
+
test('should pass with both clientId and clientSecret', async (t) => {
|
|
25
|
+
const { authCommand } = await import('../commands/auth.js');
|
|
26
|
+
authCommand.exitOverride().parse(['--clientId', 'myClientId', '--clientSecret', 'myClientSecret'], {
|
|
27
|
+
from: 'user',
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=cli.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../test/cli.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE5D,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,UAAU,CAAC,CAAC,CAAC,EAAE;YACb,IAAI,CAAC,KAAK,EAAE,CAAA;YACZ,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE;gBAClC,YAAY,EAAE;oBACZ,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE;iBACxB;aACF,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,0CAA0C,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;YACzD,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC3D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;QAC/E,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,gCAAgC,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;YAC/C,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC3D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;QACzG,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,oCAAoC,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;YACnD,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC3D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;QACjH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,iDAAiD,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;YAChE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAE3D,WAAW,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,EAAE;gBACjG,IAAI,EAAE,MAAM;aACb,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tradestation-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A Node.js client for the TradeStation API with OAuth2 authentication and OpenAPI integration.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
6
7
|
"bin": "./dist/cli.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
7
11
|
"scripts": {
|
|
8
|
-
"test
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
+
"test": "tsx --experimental-test-module-mocks --test",
|
|
13
|
+
"cli": "tsx cli.ts",
|
|
14
|
+
"download-openapi": "node scripts/downloadOpenAPI.ts",
|
|
15
|
+
"generate-client": "openapi-typescript openapi.json --output generated/tradestation-api.d.ts",
|
|
16
|
+
"build": "npm run download-openapi && npm run generate-client && rimraf dist && tsc",
|
|
12
17
|
"prepublishOnly": "npm run build"
|
|
13
18
|
},
|
|
14
19
|
"keywords": [],
|
|
15
|
-
"author": "",
|
|
20
|
+
"author": "Simone Busoli <simone.busoli@gmail.com>",
|
|
21
|
+
"repository": "github:simoneb/tradestation-client",
|
|
16
22
|
"dependencies": {
|
|
17
23
|
"commander": "^14.0.2",
|
|
18
24
|
"fastify": "^5.6.2",
|
|
19
25
|
"install": "^0.13.0",
|
|
20
|
-
"npm": "^11.7.0",
|
|
21
26
|
"open": "^11.0.0",
|
|
22
27
|
"openapi-fetch": "^0.15.0",
|
|
23
28
|
"tradestation-api-ts": "^1.2.1"
|
|
@@ -26,6 +31,8 @@
|
|
|
26
31
|
"@types/node": "^25.0.3",
|
|
27
32
|
"openapi-typescript": "^7.10.1",
|
|
28
33
|
"playwright-core": "^1.57.0",
|
|
34
|
+
"rimraf": "^6.1.2",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
29
36
|
"typescript": "^5.9.3"
|
|
30
37
|
}
|
|
31
38
|
}
|
package/.env.example
DELETED
package/.prettierrc
DELETED
package/authMiddleware.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto'
|
|
2
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import type { Middleware } from 'openapi-fetch'
|
|
5
|
-
import fastify from 'fastify'
|
|
6
|
-
import open from 'open'
|
|
7
|
-
|
|
8
|
-
import { redirectPort, redirectUri } from './config.js'
|
|
9
|
-
|
|
10
|
-
type AuthResponse = {
|
|
11
|
-
access_token: string
|
|
12
|
-
refresh_token: string
|
|
13
|
-
id_token: string
|
|
14
|
-
scope: string
|
|
15
|
-
expires_in: number
|
|
16
|
-
token_type: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type AuthData = AuthResponse & {
|
|
20
|
-
timestamp: number // Unix timestamp in milliseconds when token was obtained
|
|
21
|
-
client_id: string
|
|
22
|
-
client_secret: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const authFilePath = join(process.cwd(), 'tradestation-auth.json')
|
|
26
|
-
|
|
27
|
-
function generateRandomState(): string {
|
|
28
|
-
return randomBytes(32).toString('base64url')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function tryLoadAuth(): Promise<AuthData | null> {
|
|
32
|
-
try {
|
|
33
|
-
const data = await readFile(authFilePath, 'utf-8')
|
|
34
|
-
return JSON.parse(data)
|
|
35
|
-
} catch {
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function saveAuth(
|
|
41
|
-
authResponse: AuthResponse,
|
|
42
|
-
clientId: string,
|
|
43
|
-
clientSecret: string
|
|
44
|
-
): Promise<AuthData> {
|
|
45
|
-
const authData: AuthData = {
|
|
46
|
-
...authResponse,
|
|
47
|
-
timestamp: Date.now(),
|
|
48
|
-
client_id: clientId,
|
|
49
|
-
client_secret: clientSecret,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await writeFile(authFilePath, JSON.stringify(authData, null, 2), 'utf-8')
|
|
53
|
-
return authData
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isTokenValid(token: AuthData): boolean {
|
|
57
|
-
const now = Date.now()
|
|
58
|
-
const expiresAt = token.timestamp + token.expires_in * 1000
|
|
59
|
-
// Consider token expired 30 seconds before actual expiration to account for clock skew
|
|
60
|
-
return now < expiresAt - 30000
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function requestAccessToken(
|
|
64
|
-
code: string,
|
|
65
|
-
clientId: string,
|
|
66
|
-
clientSecret: string
|
|
67
|
-
): Promise<AuthData> {
|
|
68
|
-
const authRes = await fetch('https://signin.tradestation.com/oauth/token', {
|
|
69
|
-
method: 'POST',
|
|
70
|
-
headers: {
|
|
71
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
72
|
-
},
|
|
73
|
-
body: new URLSearchParams({
|
|
74
|
-
grant_type: 'authorization_code',
|
|
75
|
-
code,
|
|
76
|
-
redirect_uri: redirectUri,
|
|
77
|
-
client_id: clientId,
|
|
78
|
-
client_secret: clientSecret,
|
|
79
|
-
}),
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
const authResponse = (await authRes.json()) as AuthResponse
|
|
83
|
-
|
|
84
|
-
if (!authRes.ok) {
|
|
85
|
-
throw new Error(`Failed to obtain access token: ${authResponse}`)
|
|
86
|
-
}
|
|
87
|
-
return saveAuth(authResponse, clientId, clientSecret)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function refreshAccessToken(
|
|
91
|
-
refreshToken: string,
|
|
92
|
-
clientId: string,
|
|
93
|
-
clientSecret: string
|
|
94
|
-
): Promise<AuthData> {
|
|
95
|
-
const authRes = await fetch('https://signin.tradestation.com/oauth/token', {
|
|
96
|
-
method: 'POST',
|
|
97
|
-
headers: {
|
|
98
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
99
|
-
},
|
|
100
|
-
body: new URLSearchParams({
|
|
101
|
-
grant_type: 'refresh_token',
|
|
102
|
-
client_id: clientId,
|
|
103
|
-
client_secret: clientSecret,
|
|
104
|
-
refresh_token: refreshToken,
|
|
105
|
-
}),
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
if (!authRes.ok) {
|
|
109
|
-
throw new Error(`Failed to refresh token: ${await authRes.text()}`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const authResponse = (await authRes.json()) as AuthResponse
|
|
113
|
-
|
|
114
|
-
// Preserve the original refresh token if a new one wasn't provided
|
|
115
|
-
// (TradeStation doesn't rotate refresh tokens by default)
|
|
116
|
-
if (!authResponse.refresh_token) {
|
|
117
|
-
authResponse.refresh_token = refreshToken
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return saveAuth(authResponse, clientId, clientSecret)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function authenticateWithOAuth2(
|
|
124
|
-
clientId: string,
|
|
125
|
-
clientSecret: string
|
|
126
|
-
): Promise<AuthData> {
|
|
127
|
-
const app = fastify()
|
|
128
|
-
const state = generateRandomState()
|
|
129
|
-
|
|
130
|
-
const authPromise = new Promise<AuthData>((resolve, reject) => {
|
|
131
|
-
app.get('/', async (request, reply) => {
|
|
132
|
-
const { code, state: callbackState } = request.query as {
|
|
133
|
-
code?: string
|
|
134
|
-
state?: string
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!code) {
|
|
138
|
-
reply.code(400)
|
|
139
|
-
return 'No code provided'
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (callbackState !== state) {
|
|
143
|
-
reply.code(401)
|
|
144
|
-
reject(new Error('State parameter mismatch - possible CSRF attack'))
|
|
145
|
-
return 'State validation failed'
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
resolve(await requestAccessToken(code, clientId, clientSecret))
|
|
149
|
-
|
|
150
|
-
setTimeout(() => {
|
|
151
|
-
app.close()
|
|
152
|
-
}, 100)
|
|
153
|
-
|
|
154
|
-
return 'Authorization successful! You can close this tab.'
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
await app.listen({ port: redirectPort, host: 'localhost' })
|
|
159
|
-
|
|
160
|
-
console.log('Opening browser for authentication...')
|
|
161
|
-
|
|
162
|
-
await openAuthUrl(state, clientId)
|
|
163
|
-
|
|
164
|
-
return authPromise
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function openAuthUrl(state: string, clientId: string) {
|
|
168
|
-
const authUrl = new URL('https://signin.tradestation.com/authorize')
|
|
169
|
-
authUrl.searchParams.append('response_type', 'code')
|
|
170
|
-
authUrl.searchParams.append(
|
|
171
|
-
'scope',
|
|
172
|
-
'openid offline_access profile MarketData ReadAccount Trade Matrix'
|
|
173
|
-
)
|
|
174
|
-
authUrl.searchParams.append('redirect_uri', redirectUri)
|
|
175
|
-
authUrl.searchParams.append('client_id', clientId)
|
|
176
|
-
authUrl.searchParams.append('state', state)
|
|
177
|
-
authUrl.searchParams.append('audience', 'https://api.tradestation.com')
|
|
178
|
-
|
|
179
|
-
await open(authUrl.toString())
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export async function authenticate(
|
|
183
|
-
clientId: string,
|
|
184
|
-
clientSecret: string
|
|
185
|
-
): Promise<AuthData> {
|
|
186
|
-
const auth = await tryLoadAuth()
|
|
187
|
-
|
|
188
|
-
if (!auth) {
|
|
189
|
-
return authenticateWithOAuth2(clientId, clientSecret)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (isTokenValid(auth)) {
|
|
193
|
-
return auth
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
return await refreshAccessToken(auth.refresh_token, clientId, clientSecret)
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error('Token refresh failed, starting OAuth flow:', error)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return authenticateWithOAuth2(clientId, clientSecret)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function createAuthMiddleware(
|
|
206
|
-
clientId: string,
|
|
207
|
-
clientSecret: string
|
|
208
|
-
): Middleware {
|
|
209
|
-
return {
|
|
210
|
-
async onRequest({ request }) {
|
|
211
|
-
const auth = await authenticate(clientId, clientSecret)
|
|
212
|
-
|
|
213
|
-
request.headers.set('Authorization', `Bearer ${auth.access_token}`)
|
|
214
|
-
return request
|
|
215
|
-
},
|
|
216
|
-
async onResponse({ response, request }) {
|
|
217
|
-
if (response.status === 401) {
|
|
218
|
-
console.warn(
|
|
219
|
-
`Received 401 Unauthorized for request to ${request.url}. Access token may be invalid or expired.`
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
let auth = await tryLoadAuth()
|
|
223
|
-
if (!auth) {
|
|
224
|
-
throw new Error('No authentication data available to refresh token.')
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
auth = await refreshAccessToken(
|
|
228
|
-
auth.refresh_token,
|
|
229
|
-
clientId,
|
|
230
|
-
clientSecret
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
response = await fetch(new Request(request), {
|
|
234
|
-
headers: {
|
|
235
|
-
Authorization: `Bearer ${auth.access_token}`,
|
|
236
|
-
},
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
return response
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
}
|
|
243
|
-
}
|
package/cli.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { program, Option, Command } from 'commander'
|
|
3
|
-
import { TradeStationClient } from 'tradestation-api-ts'
|
|
4
|
-
|
|
5
|
-
import { authenticate, tryLoadAuth } from './authMiddleware.js'
|
|
6
|
-
import type { BarUnit, SessionTemplate } from 'tradestation-api-ts/dist/types/marketData.js'
|
|
7
|
-
import { authCommand } from './commands/auth.js'
|
|
8
|
-
import { barsCommand } from './commands/barsCommand.js'
|
|
9
|
-
|
|
10
|
-
program
|
|
11
|
-
.addOption(
|
|
12
|
-
new Option('--environment <environment>', 'TradeStation environment')
|
|
13
|
-
.choices(['Live', 'Simulation'])
|
|
14
|
-
.default('Simulation')
|
|
15
|
-
)
|
|
16
|
-
.addCommand(authCommand, { isDefault: true })
|
|
17
|
-
.addCommand(barsCommand)
|
|
18
|
-
|
|
19
|
-
await program.parseAsync()
|
package/commands/auth.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
2
|
-
|
|
3
|
-
import { authenticate } from '../authMiddleware.js'
|
|
4
|
-
|
|
5
|
-
export const authCommand = new Command('auth')
|
|
6
|
-
.description('Authenticate with TradeStation API')
|
|
7
|
-
.requiredOption('--clientId <clientId>', 'TradeStation Client ID')
|
|
8
|
-
.requiredOption('--clientSecret <clientSecret>', 'TradeStation Client Secret')
|
|
9
|
-
.action(async (options: { clientId: string; clientSecret: string }) => {
|
|
10
|
-
console.log('Authenticating with TradeStation API...', options)
|
|
11
|
-
await authenticate(options.clientId, options.clientSecret)
|
|
12
|
-
console.log('Authentication successful!')
|
|
13
|
-
})
|
package/commands/barsCommand.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from 'commander'
|
|
2
|
-
import { TradeStationClient } from 'tradestation-api-ts'
|
|
3
|
-
import type { BarUnit, SessionTemplate } from 'tradestation-api-ts/dist/types/marketData.js'
|
|
4
|
-
import fsp from 'fs/promises'
|
|
5
|
-
|
|
6
|
-
import { tryLoadAuth } from '../authMiddleware.js'
|
|
7
|
-
|
|
8
|
-
const sessions = ['Default', 'USEQPre', 'USEQPost', 'USEQPreAndPost', 'USEQ24Hour']
|
|
9
|
-
|
|
10
|
-
const units = ['Minute', 'Hour', 'Daily', 'Weekly', 'Monthly']
|
|
11
|
-
|
|
12
|
-
async function authenticateOrFail() {
|
|
13
|
-
const authData = await tryLoadAuth()
|
|
14
|
-
if (!authData) {
|
|
15
|
-
console.error('No authentication data found. Please run the auth command first.')
|
|
16
|
-
process.exit(1)
|
|
17
|
-
}
|
|
18
|
-
return authData
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const barsCommand = new Command('bars')
|
|
22
|
-
.description('Download market data bars from TradeStation API')
|
|
23
|
-
.argument('<symbol>', 'Market symbol to download data for')
|
|
24
|
-
.option('--interval <interval>', 'Bar interval (e.g., 1, 5, 1440, ...)', '1')
|
|
25
|
-
.addOption(new Option('--unit <unit>', 'Bar unit').choices(units).default(units[0]))
|
|
26
|
-
.option('--barsBack <barsBack>', 'Number of bars to retrieve', parseInt, 10)
|
|
27
|
-
.option('--firstDate <firstDate>', 'First date for bar data (YYYY-MM-DD)')
|
|
28
|
-
.option('--lastDate <lastDate>', 'Last date for bar data (YYYY-MM-DD)')
|
|
29
|
-
.addOption(
|
|
30
|
-
new Option('--sessionTemplate <sessionTemplate>', 'Session template to use.').choices(sessions).default(sessions[0])
|
|
31
|
-
)
|
|
32
|
-
.addOption(new Option('--clientId <clientId>', 'TradeStation Client ID'))
|
|
33
|
-
.addOption(new Option('--clientSecret <clientSecret>', 'TradeStation Client Secret'))
|
|
34
|
-
.addOption(new Option('--refreshToken <refreshToken>', 'TradeStation Refresh Token'))
|
|
35
|
-
.action(async function (
|
|
36
|
-
symbol: string,
|
|
37
|
-
options: {
|
|
38
|
-
interval: string
|
|
39
|
-
unit: BarUnit
|
|
40
|
-
barsBack: number
|
|
41
|
-
firstDate: string
|
|
42
|
-
lastDate: string
|
|
43
|
-
sessionTemplate: SessionTemplate
|
|
44
|
-
clientId?: string
|
|
45
|
-
clientSecret?: string
|
|
46
|
-
refreshToken?: string
|
|
47
|
-
}
|
|
48
|
-
) {
|
|
49
|
-
const environment = this.optsWithGlobals().environment as 'Live' | 'Simulation'
|
|
50
|
-
|
|
51
|
-
let client: TradeStationClient
|
|
52
|
-
|
|
53
|
-
if (options.clientId && options.clientSecret && options.refreshToken) {
|
|
54
|
-
client = new TradeStationClient({
|
|
55
|
-
clientId: options.clientId,
|
|
56
|
-
clientSecret: options.clientSecret,
|
|
57
|
-
refresh_token: options.refreshToken,
|
|
58
|
-
environment,
|
|
59
|
-
})
|
|
60
|
-
} else {
|
|
61
|
-
const authData = await authenticateOrFail()
|
|
62
|
-
client = new TradeStationClient({
|
|
63
|
-
clientId: authData.client_id,
|
|
64
|
-
clientSecret: authData.client_secret,
|
|
65
|
-
refresh_token: authData.refresh_token,
|
|
66
|
-
environment,
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(`Downloading market data for symbol: ${symbol}`)
|
|
71
|
-
|
|
72
|
-
const bars = await client.marketData.getBarHistory(symbol, {
|
|
73
|
-
interval: options.interval,
|
|
74
|
-
unit: options.unit,
|
|
75
|
-
firstdate: options.firstDate,
|
|
76
|
-
lastdate: options.lastDate,
|
|
77
|
-
barsback: options.barsBack,
|
|
78
|
-
sessiontemplate: options.sessionTemplate,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
await fsp.writeFile(`${symbol}_bars.json`, JSON.stringify(bars.Bars, null, 2))
|
|
82
|
-
console.log(`Saved ${bars.Bars.length} bars to ${symbol}_bars.json`)
|
|
83
|
-
})
|
package/config.ts
DELETED
package/dist/config.d.ts
DELETED
package/dist/config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,QAAQ,CAAA;AACjC,eAAO,MAAM,WAAW,2BAAqC,CAAA"}
|
package/dist/config.js
DELETED
package/dist/config.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAA;AACjC,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,YAAY,EAAE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"downloadOpenAPI.d.ts","sourceRoot":"","sources":["../downloadOpenAPI.ts"],"names":[],"mappings":""}
|