Add TopstepX platform support, drain withdrawal stages, and perfect-path calendar projection
- TopstepXClient (ProjectX Gateway): API-key auth, SignalR user hub, Trade/search daily P&L, emulated OCO exits, books-invariant withdrawal detection for $0-start accounts - Platform-aware client pool, credentials (api_key column), and credential form; $0-size account types match by prefix alone - Drain stages: min-day ($5-$15 randomized) MNQ legs for the configured days, then wait for the 50% withdrawal; stage pills show 'Stage N – Drain' - Account calendar projects the perfect path: future days book their planned targets, simulated 50% withdrawals, payout marker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1bca441247
commit
50a5eb4713
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import type { CatalogAccountType } from './master-protocol';
|
||||
import type { TradovateClient } from './tradovate-class';
|
||||
import type { PlatformClient } from './platform-client';
|
||||
import {
|
||||
getAssignment,
|
||||
setAssignment,
|
||||
@@ -69,10 +69,14 @@ export function matchAccountType(
|
||||
const byPrefix = types.filter((t) => accountName.startsWith(t.prefix));
|
||||
if (byPrefix.length === 0) return { type: null, ambiguous: false };
|
||||
|
||||
// No size yet — stay Unassigned and try again next sync.
|
||||
if (accountSize === null) return { type: null, ambiguous: false };
|
||||
|
||||
const candidates = byPrefix.filter((t) => Math.abs(t.accountSize - accountSize) < 0.01);
|
||||
// Exact size match first. An account type with size 0 means the account's
|
||||
// balance starts at $0 (TopstepX Express style) — there is no funding
|
||||
// transaction to infer a size from, so it matches on prefix alone, but
|
||||
// only once every sized candidate has had its chance.
|
||||
const sized = accountSize !== null
|
||||
? byPrefix.filter((t) => t.accountSize > 0 && Math.abs(t.accountSize - accountSize) < 0.01)
|
||||
: [];
|
||||
const candidates = sized.length > 0 ? sized : byPrefix.filter((t) => t.accountSize === 0);
|
||||
if (candidates.length === 0) return { type: null, ambiguous: false };
|
||||
if (candidates.length === 1) return { type: candidates[0], ambiguous: false };
|
||||
|
||||
@@ -117,7 +121,7 @@ export function getAccountConfig(accountId: number): ResolvedAssignment {
|
||||
|
||||
/** The size this account resolved to, cached once found so a later reset
|
||||
* deposit can't move it. */
|
||||
export function getInferredSize(accountId: number, client: TradovateClient): number | null {
|
||||
export function getInferredSize(accountId: number, client: PlatformClient): number | null {
|
||||
const cached = loadAccountMeta(accountId, INFERRED_SIZE_KEY);
|
||||
if (cached !== null) {
|
||||
const value = Number(cached);
|
||||
@@ -130,7 +134,7 @@ export function getInferredSize(accountId: number, client: TradovateClient): num
|
||||
* Called after each Tradovate sync and after the catalog changes. */
|
||||
export function resolveFirmAssignments(
|
||||
firmId: string,
|
||||
client: TradovateClient,
|
||||
client: PlatformClient,
|
||||
): { assigned: number; unassigned: number; manual: number } {
|
||||
const firm = getCachedFirm(firmId);
|
||||
const types = firm?.accountTypes ?? [];
|
||||
@@ -184,6 +188,9 @@ export function resolveFirmAssignments(
|
||||
}
|
||||
|
||||
if (matched.type) {
|
||||
// A $0-start type matched on prefix alone — whatever number rode
|
||||
// along (usually the live balance) is not an inferred size.
|
||||
if (matched.type.accountSize === 0) matchedSize = 0;
|
||||
if (matchedSize !== null && cached === null) {
|
||||
saveAccountMeta(account.id, INFERRED_SIZE_KEY, String(matchedSize));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user