Tradeify247: each person sizes a little apart from everyone else
Every machine sized a like account in a like state to the same position as every other person's, so two people's positions could match to the unit. Each person now draws a size factor in [0.85, 1.15] per trade date, seeded by the master's uuid for them; all of one person's accounts share it. The factor moves the hold a DXtrade leg is sized for (hold / factor², since size goes as 1/√hold), so the instrument pick, the dashboard's next leg and the Firms page's reach test all see the hold the size was made for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ed56e306d1
commit
6dd2528fe2
+7
-6
@@ -14,7 +14,6 @@ import {
|
||||
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, currentTradeDateCT,
|
||||
obfuscationStopDollars, takeProfitPlan, onTick, percentFee, dllRoom, dllStopDollars, dllViable, stopTicks,
|
||||
expectedSwapToWin, planLegSize, maxLossRoom, breachDistance,
|
||||
DEFAULT_TARGET_HOLD_HOURS,
|
||||
isNoTradeTime, isPastSessionClose,
|
||||
BIG_DAY_THRESHOLD, LEG_COMPLETION_EPSILON, MAX_LEGS_PER_DAY,
|
||||
FALLBACK_COMMISSION_PER_CONTRACT, MAX_COMMISSION_PER_CONTRACT, DLL_MIN_STOP,
|
||||
@@ -27,6 +26,7 @@ import type { LaneTick, TickReason } from './master-protocol';
|
||||
import { POINT_VALUES, housekeepingRoot, HOUSEKEEPING_ROOT, MAX_HOLD_HOURS, futuresContracts } from './instrument-specs';
|
||||
import { firmInstruments, drawFuturesInstrument, spreadBlock, type LegShape } from './instruments';
|
||||
import { recordEntry } from './trade-mix';
|
||||
import { sizingHoldHours } from './person-size';
|
||||
import { errorMessage, isClientReady, isOrderNotPlaced, type PlatformClient, type PlatformContract, type PickContext } from './platform-client';
|
||||
|
||||
// ── process-wide state ───────────────────────────────────────────────────────
|
||||
@@ -1329,7 +1329,7 @@ function sizeLeg(item: SizingItem, contract: LegContract, legAmount: number, tra
|
||||
leg: legAmount,
|
||||
contract,
|
||||
commissionPct: pct,
|
||||
targetHoldHours: item.firm.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS,
|
||||
targetHoldHours: sizingHoldHours(item.firm, tradeDate),
|
||||
financingPctPerDay: item.firm.financingPctPerDay ?? 0,
|
||||
dailyLossLimit: cfg.dailyLossLimit ?? 0,
|
||||
dllRoom: item.dllRoom,
|
||||
@@ -1352,7 +1352,7 @@ function pickContextFor(items: SizingItem[], tradeDate: string): PickContext | u
|
||||
if (!lead) return undefined;
|
||||
const legAmount = legAmountFor(lead, tradeDate);
|
||||
return {
|
||||
targetHoldHours: lead.firm.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS,
|
||||
targetHoldHours: sizingHoldHours(lead.firm, tradeDate),
|
||||
evaluate: (contract) => {
|
||||
const { sizing, cost } = sizeLeg(lead, contract, legAmount, tradeDate);
|
||||
return sizing && cost !== null ? { notional: sizing.notional, holdHours: sizing.predictedHoldHours, cost } : null;
|
||||
@@ -1366,9 +1366,10 @@ function pickContextFor(items: SizingItem[], tradeDate: string): PickContext | u
|
||||
*
|
||||
* Size: futures trade one contract per $1,000 of leg; a platform that prices
|
||||
* positions (stepNotional) sizes by notional instead — whatever makes the
|
||||
* leg's expected hold the firm's target, from the instrument's recent
|
||||
* volatility — under the account type's notional cap and the instrument's
|
||||
* margin rate (the firm's leverage).
|
||||
* leg's expected hold the firm's target as this person sizes it today
|
||||
* (sizingHoldHours), from the instrument's recent volatility — under the
|
||||
* account type's notional cap and the instrument's margin rate (the firm's
|
||||
* leverage).
|
||||
*
|
||||
* Fees: a percent-of-notional firm's fee is computed off the fill; otherwise
|
||||
* it is read off the realized-P&L change a second after it. A firm that
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
*/
|
||||
|
||||
import axios from './http-client';
|
||||
import { currentTradeDateCT, FIRM_RESET_UTC_HOUR, DEFAULT_TARGET_HOLD_HOURS, rankForTarget } from './trading-logic';
|
||||
import { currentTradeDateCT, FIRM_RESET_UTC_HOUR, rankForTarget } from './trading-logic';
|
||||
import { sizingHoldHours } from './person-size';
|
||||
import {
|
||||
saveDailyPnL, loadDailyPnL, loadAccountMeta, saveAccountMeta, loadFundTransactions, saveFundTransactions,
|
||||
saveEndedCycleStart,
|
||||
@@ -1222,7 +1223,7 @@ export class DXTradeClient implements PlatformClient {
|
||||
const ref = this.referenceAccount();
|
||||
if (!ref) return null;
|
||||
const firm = getCachedFirm(this.cfg.firmId);
|
||||
const targetHold = firm?.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS;
|
||||
const targetHold = sizingHoldHours(firm);
|
||||
const symbols = [...new Set(candidates.map((c) => c.symbol))];
|
||||
const [insts, dets, quotes, vols] = await Promise.all([
|
||||
this.instruments(symbols),
|
||||
|
||||
+6
-3
@@ -11,9 +11,10 @@ import type { PlatformContract } from './platform-client';
|
||||
import type { SymbolStats } from './master-protocol';
|
||||
import {
|
||||
combineCmeSessions, computeDayPlan, resolveEffectiveConfig, obfuscationStopDollars, dllRoom as computeDllRoom,
|
||||
planLegSize, rankForTarget, maxLossRoom, lastStand, dllStopDollars, DEFAULT_TARGET_HOLD_HOURS,
|
||||
planLegSize, rankForTarget, maxLossRoom, lastStand, dllStopDollars,
|
||||
FALLBACK_COMMISSION_PER_CONTRACT,
|
||||
} from './trading-logic';
|
||||
import { sizingHoldHours } from './person-size';
|
||||
|
||||
export type CandidateLeg = SymbolStats['symbols'][number] & {
|
||||
swapReserve: number;
|
||||
@@ -39,10 +40,12 @@ export function rankNextLeg(o: {
|
||||
dllRoom: number | null;
|
||||
obfuscationDollars: number;
|
||||
cash: { amount: number; availableFunds?: number };
|
||||
/** The trade date the hold is drawn for (sizingHoldHours); default today. */
|
||||
tradeDate?: string;
|
||||
}): CandidateLeg[] {
|
||||
const candidates: PlatformContract[] = o.client.lastCandidates ?? [];
|
||||
if (!(o.leg > 0) || candidates.length === 0) return [];
|
||||
const target = o.firm.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS;
|
||||
const target = sizingHoldHours(o.firm, o.tradeDate);
|
||||
const floorRoom = maxLossRoom({
|
||||
balance: o.cash.amount, accountSize: o.cfg.accountSize, maxLoss: o.cfg.maxLoss ?? 0,
|
||||
autoLiqThreshold: o.client.autoLiqThresholds?.[o.accountId],
|
||||
@@ -145,7 +148,7 @@ export function accountNextLeg(
|
||||
lastStand: p.room !== null && lastStand({ dllRoom: p.room, dailyLossLimit: p.dll, maxLossRoom: floorRoom }),
|
||||
candidates: rankNextLeg({
|
||||
client, accountId: acc.id, cfg: p.cfg, firm, leg: p.plan.remainingToday, dllRoom: p.room,
|
||||
obfuscationDollars: p.obfuscation, cash: p.cash,
|
||||
obfuscationDollars: p.obfuscation, cash: p.cash, tradeDate,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* The hold this machine sizes DXtrade legs for. Every person's machine would
|
||||
* otherwise size a like account in a like state to the same position as
|
||||
* everyone else's; each person's own draw (personHoldHours) puts their sizes
|
||||
* within ±PERSON_SIZE_SPREAD of the plan instead, the same for all of their
|
||||
* accounts and new each trade date.
|
||||
*/
|
||||
|
||||
import { getSetting } from './db';
|
||||
import { currentTradeDateCT, personHoldHours, DEFAULT_TARGET_HOLD_HOURS } from './trading-logic';
|
||||
|
||||
/** Who this machine trades for: the master's uuid for the person, else the
|
||||
* instance name it reports under. */
|
||||
function personKey(): string {
|
||||
return getSetting('instance_id') || getSetting('instance_name') || '';
|
||||
}
|
||||
|
||||
/** The hold a DXtrade firm's legs are sized for on this machine today. */
|
||||
export function sizingHoldHours(
|
||||
firm: { targetHoldHours?: number } | null | undefined,
|
||||
tradeDate: string = currentTradeDateCT(),
|
||||
): number {
|
||||
return personHoldHours(firm?.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS, personKey(), tradeDate);
|
||||
}
|
||||
+3
-2
@@ -20,9 +20,10 @@ import { getClients } from './clients';
|
||||
import { getAccountTypeById, getKeptRuleIdsForFirm, getTradableFirms } from './firm-catalog';
|
||||
import { getAccountConfig } from './account-type-resolver';
|
||||
import { getSchedulerStatus } from './auto-trade';
|
||||
import { botStopsActive, combineCmeSessions, computeDayPlan, currentTradeDateCT, cycleCompletion, endedCycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig, DEFAULT_TARGET_HOLD_HOURS } from './trading-logic';
|
||||
import { botStopsActive, combineCmeSessions, computeDayPlan, currentTradeDateCT, cycleCompletion, endedCycleCompletion, LEG_COMPLETION_EPSILON, projectPerfectPath, resolveEffectiveConfig } from './trading-logic';
|
||||
import type { AccountTypeStats, FirmStats, InstrumentStats, SymbolStats } from './master-protocol';
|
||||
import { accountNextLeg, accountLegShape } from './leg-estimate';
|
||||
import { sizingHoldHours } from './person-size';
|
||||
import { instrumentStatsFor, isFuturesFirm } from './instruments';
|
||||
import { accountStatusFields, brokerFacts, emptyFirmStats, firmStatusFields, historyFresh, keptRuleIdsField } from './platform-client';
|
||||
|
||||
@@ -494,7 +495,7 @@ function symbolStatsFor(
|
||||
symbolStats: {
|
||||
account: acc.name,
|
||||
leg: next.leg,
|
||||
targetHoldHours: f.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS,
|
||||
targetHoldHours: Math.round(sizingHoldHours(f, tradeDate) * 10) / 10,
|
||||
lastStand: next.lastStand,
|
||||
at: new Date().toISOString(),
|
||||
symbols: next.candidates.map((c) => ({
|
||||
|
||||
@@ -1440,6 +1440,44 @@ export function botStopsActive(cfg: { firstDayStop: boolean; minDayStop: boolean
|
||||
/** The hold a leg is sized for when the firm doesn't set one. */
|
||||
export const DEFAULT_TARGET_HOLD_HOURS = 12;
|
||||
|
||||
/** How far one person's DXtrade sizes move off the plan, each way. */
|
||||
export const PERSON_SIZE_SPREAD = 0.15;
|
||||
|
||||
/**
|
||||
* One person's size factor for a trade date, in [1 − spread, 1 + spread].
|
||||
* Seeded (FNV-1a) by the person, so all of one person's accounts size alike
|
||||
* while another person's land elsewhere, and drawn afresh each trade date.
|
||||
* FNV alone barely moves its top bits when only the key's last character
|
||||
* changes (the next day's date), so the hash is mixed (murmur3's finalizer)
|
||||
* before it is scaled.
|
||||
*/
|
||||
export function personSizeFactor(personKey: string, tradeDate: string): number {
|
||||
const key = `size|${personKey}|${tradeDate}`;
|
||||
let hash = 0x811c9dc5;
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
hash ^= key.charCodeAt(i);
|
||||
hash = Math.imul(hash, 0x01000193) >>> 0;
|
||||
}
|
||||
hash ^= hash >>> 16;
|
||||
hash = Math.imul(hash, 0x85ebca6b);
|
||||
hash ^= hash >>> 13;
|
||||
hash = Math.imul(hash, 0xc2b2ae35);
|
||||
hash = (hash ^ (hash >>> 16)) >>> 0;
|
||||
return 1 - PERSON_SIZE_SPREAD + (hash / 0x100000000) * 2 * PERSON_SIZE_SPREAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hold a person's DXtrade leg is sized for: the firm's target, moved so
|
||||
* the size comes out personSizeFactor times the plan. Size goes as 1/√hold
|
||||
* (sizeNotional), so hold / factor² does it, and every rule that ranks or
|
||||
* checks against the hold sees the same number the size was made for.
|
||||
*/
|
||||
export function personHoldHours(targetHoldHours: number, personKey: string, tradeDate: string): number {
|
||||
const hold = targetHoldHours > 0 ? targetHoldHours : DEFAULT_TARGET_HOLD_HOURS;
|
||||
const factor = personSizeFactor(personKey, tradeDate);
|
||||
return hold / (factor * factor);
|
||||
}
|
||||
|
||||
/** Share of the broker's margin headroom a new position may use: the rest
|
||||
* absorbs the price moving between sizing and the fill, and the entry fee. */
|
||||
export const MARGIN_HEADROOM = 0.97;
|
||||
|
||||
Reference in New Issue
Block a user