Trade mix: name positions by a contract index that never expires
A Tradovate position opened before this build (so its entry wasn't recorded) was named from the contract cache, whose entries expire after 30 minutes: Majd's CL longs showed as #3273057. The resolver now also keeps a lasting contract id → root index, which the mix reads after its recorded entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ed62788480
commit
b125257d4e
@@ -37,8 +37,13 @@ const PROBE_WINDOW = 6;
|
|||||||
// route handlers (settings page, manual trades) never see.
|
// route handlers (settings page, manual trades) never see.
|
||||||
const _g = globalThis as typeof globalThis & {
|
const _g = globalThis as typeof globalThis & {
|
||||||
__contractCache?: Map<string, { contract: ResolvedContract; resolvedAt: number }>;
|
__contractCache?: Map<string, { contract: ResolvedContract; resolvedAt: number }>;
|
||||||
|
__contractRoots?: Map<string, string>;
|
||||||
};
|
};
|
||||||
const cache = (_g.__contractCache ??= new Map());
|
const cache = (_g.__contractCache ??= new Map());
|
||||||
|
/** Contract id → the root it was resolved for. Unlike the cache it never
|
||||||
|
* expires: a position opened hours ago still needs its instrument's name
|
||||||
|
* (lib/trade-mix.ts). Front months are few, so it stays small. */
|
||||||
|
const roots = (_g.__contractRoots ??= new Map());
|
||||||
const CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
const CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
export function getCachedContract(symbol: string): ResolvedContract | null {
|
export function getCachedContract(symbol: string): ResolvedContract | null {
|
||||||
@@ -51,6 +56,12 @@ export function getCachedContract(symbol: string): ResolvedContract | null {
|
|||||||
return entry.contract;
|
return entry.contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The root a resolved contract id belongs to, however long ago it was
|
||||||
|
* resolved; null for one this process never resolved. */
|
||||||
|
export function rootOfContract(contractId: number | string): string | null {
|
||||||
|
return roots.get(String(contractId)) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getAllCachedContracts(): Record<string, ResolvedContract | null> {
|
export function getAllCachedContracts(): Record<string, ResolvedContract | null> {
|
||||||
const result: Record<string, ResolvedContract | null> = {};
|
const result: Record<string, ResolvedContract | null> = {};
|
||||||
for (const [symbol, entry] of cache) {
|
for (const [symbol, entry] of cache) {
|
||||||
@@ -205,6 +216,7 @@ export async function resolveContracts(
|
|||||||
};
|
};
|
||||||
results[sym] = contract;
|
results[sym] = contract;
|
||||||
cache.set(sym, { contract, resolvedAt: Date.now() });
|
cache.set(sym, { contract, resolvedAt: Date.now() });
|
||||||
|
roots.set(String(contract.id), sym);
|
||||||
|
|
||||||
if (rolledFrom) {
|
if (rolledFrom) {
|
||||||
console.log(`[contract-resolver] ${sym}: ${rolledFrom} → ${match.name} ROLLED (by volume)`);
|
console.log(`[contract-resolver] ${sym}: ${rolledFrom} → ${match.name} ROLLED (by volume)`);
|
||||||
|
|||||||
+6
-6
@@ -5,14 +5,15 @@
|
|||||||
* Open positions come off the client's position map, each contract named by
|
* Open positions come off the client's position map, each contract named by
|
||||||
* the instrument it is: a DXtrade position carries its symbol; a futures one
|
* the instrument it is: a DXtrade position carries its symbol; a futures one
|
||||||
* is looked up among the contracts this machine has entered (recorded at the
|
* is looked up among the contracts this machine has entered (recorded at the
|
||||||
* fill, kept across restarts), then the resolver's cache and TopstepX's own.
|
* fill, kept across restarts), TopstepX's own, and every contract the
|
||||||
|
* resolver has resolved in this process (never expiring).
|
||||||
* Entries are counted as they fill and kept in a setting per trade date, so
|
* Entries are counted as they fill and kept in a setting per trade date, so
|
||||||
* a restart doesn't lose the day.
|
* a restart doesn't lose the day.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getSetting, setSetting } from './db';
|
import { getSetting, setSetting } from './db';
|
||||||
import { currentTradeDateCT } from './trading-logic';
|
import { currentTradeDateCT } from './trading-logic';
|
||||||
import { getAllCachedContracts } from './contract-resolver';
|
import { rootOfContract } from './contract-resolver';
|
||||||
import { errorMessage } from './platform-client';
|
import { errorMessage } from './platform-client';
|
||||||
import type { InstrumentCount, TradeMix } from './master-protocol';
|
import type { InstrumentCount, TradeMix } from './master-protocol';
|
||||||
|
|
||||||
@@ -71,16 +72,15 @@ export function tradeMixFor(
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
client: any,
|
client: any,
|
||||||
): TradeMix {
|
): TradeMix {
|
||||||
|
// Recorded at the fill, then TopstepX's own contracts, then every
|
||||||
|
// contract this process resolved (Tradovate's, however long ago).
|
||||||
const names = new Map<string, string>();
|
const names = new Map<string, string>();
|
||||||
for (const [root, contract] of Object.entries(getAllCachedContracts())) {
|
|
||||||
if (contract) names.set(String(contract.id), root);
|
|
||||||
}
|
|
||||||
const own = typeof client.contractRoots === 'function' ? (client.contractRoots() as Map<string, string>) : null;
|
const own = typeof client.contractRoots === 'function' ? (client.contractRoots() as Map<string, string>) : null;
|
||||||
for (const [id, root] of own ?? []) names.set(id, root);
|
for (const [id, root] of own ?? []) names.set(id, root);
|
||||||
for (const [id, instrument] of Object.entries(readJson<Record<string, string>>(NAMES_KEY) ?? {})) names.set(id, instrument);
|
for (const [id, instrument] of Object.entries(readJson<Record<string, string>>(NAMES_KEY) ?? {})) names.set(id, instrument);
|
||||||
const instrumentOf = (contractId: unknown): string => (firm.platform === 'dxtrade'
|
const instrumentOf = (contractId: unknown): string => (firm.platform === 'dxtrade'
|
||||||
? String(contractId).split('|')[0]
|
? String(contractId).split('|')[0]
|
||||||
: names.get(String(contractId)) ?? `#${String(contractId)}`);
|
: names.get(String(contractId)) ?? rootOfContract(contractId as number | string) ?? `#${String(contractId)}`);
|
||||||
|
|
||||||
const open = new Map<string, InstrumentCount>();
|
const open = new Map<string, InstrumentCount>();
|
||||||
for (const acc of (client.accountList ?? []) as { id: number }[]) {
|
for (const acc of (client.accountList ?? []) as { id: number }[]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user