Files
autofirmer-expanded/lib/tradovate-helpers.ts
T
Brandon LiandClaude Opus 5.5 7fb9ddbd96 Tradovate: each login's calls go to the hosts its organization is served from
Lucid's users moved to their own Tradovate hosts (demo-api.lucid.ninjatrader.com,
seen 2026-11-01). The login still answers on live.tradovateapi.com, but the
shared demo socket drops a Lucid session right after its authorize (close 1006,
no status), so every Lucid firm sat at "Connecting…" on every machine. The
login answer now names the hosts (apiHosts, orgName "LucidTrading"), and the
web trader follows them.

A Tradovate client now opens its socket and makes its account, contract, fee
and report calls on the hosts its own login names; a login without apiHosts,
or with a value that isn't a plain host name, keeps demo.tradovateapi.com and
rpt-demo.tradovateapi.com. A renewal answer without apiHosts keeps the login's.
The shared contract cache still resolves with a client on the shared hosts
when there is one; a client on its organization's own only stands in. Login,
renewal and the quote feed are unchanged: Lucid's live and md hosts are the
shared ones. The debug routes follow the same hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:12:21 -06:00

98 lines
2.9 KiB
TypeScript

import crypto from 'crypto';
export interface AuthLoginResponse {
accessToken: string;
mdAccessToken: string;
expirationTime: string;
userStatus: string;
userId: number;
name: string;
hasLive: boolean;
hasSimPlus: boolean;
hasFunded: boolean;
hasMarketData: boolean;
requiredNonProCertification: boolean;
outdatedSentimentPolicy: boolean;
orgName: string;
/** Where this user's organization is served — see demoHosts(). */
apiHosts?: TradovateApiHosts | null;
}
/** The hosts a login answer names, one per environment and service. */
export interface TradovateApiHosts {
live?: string;
demo?: string;
mdLive?: string;
mdDemo?: string;
reportingLive?: string;
reportingDemo?: string;
replay?: string;
}
export const SHARED_DEMO_HOST = 'demo.tradovateapi.com';
export const SHARED_DEMO_REPORTING_HOST = 'rpt-demo.tradovateapi.com';
const HOST_NAME = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i;
/**
* The hosts a login's demo-environment calls go to: REST and the trading
* socket (service), and reports. Tradovate serves some organizations from
* their own hosts and says so in the login answer's apiHosts — Lucid moved to
* demo-api.lucid.ninjatrader.com (seen 2026-11-01), and the shared demo
* socket drops a Lucid session right after its authorize. A login without
* apiHosts, or with a value that isn't a plain host name, keeps the shared
* hosts.
*/
export function demoHosts(apiHosts: TradovateApiHosts | null | undefined): { service: string; reporting: string } {
const pick = (value: unknown, fallback: string) =>
typeof value === 'string' && HOST_NAME.test(value) ? value.toLowerCase() : fallback;
return {
service: pick(apiHosts?.demo, SHARED_DEMO_HOST),
reporting: pick(apiHosts?.reportingDemo, SHARED_DEMO_REPORTING_HOST),
};
}
export interface AccountItem {
id: number;
name: string;
userId: number;
accountType: string;
restricted: boolean;
closed: boolean;
clearingHouseId: number;
riskCategoryId: number;
autoLiqProfileId: number;
marginAccountType: string;
legalStatus: string;
archived: boolean;
timestamp: string;
active: boolean;
}
export interface Contract {
id: number;
name: string;
contractMaturityId: number;
timestamp: string;
status: string;
providerTickSize: number;
}
export const randomUUIDV4 = () => {
return crypto.randomUUID();
};
export const computeSec = (user: string, password: string, deviceId: string, chl: string) => {
const key = '035a1259-11e7-485a-aeae-9b6016579351';
const Ct = ['chl', 'deviceId', 'name', 'password', 'appId'];
const body = {
chl: chl,
deviceId: deviceId,
name: user,
password: password,
appId: 'tradovate_trader(web)',
};
const msg = Ct.map((k) => body[k] ?? '').join('');
return crypto.createHmac('sha256', key).update(msg, 'utf8').digest('hex');
};