DXtrade sizing: volatility only (12h default hold); fees and every-day only for DXtrade
The symbol map's fixed notional per contract is gone: every DXtrade leg is sized from the instrument's recent volatility for the firm's target hold (blank = 12 h), and a candidate without a volatility isn't picked. Percent commission and every-day trading are ignored on non-DXtrade firms, which keep measured per-contract fees and the CME calendar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
28f465508d
commit
d01f03b293
+10
-18
@@ -212,16 +212,13 @@ export function realizedVolPerHour(closes: number[]): number | null {
|
||||
/**
|
||||
* A candidate's cost score — lower is better.
|
||||
*
|
||||
* Volatility sizing (targetHoldHours > 0): a leg sized for a hold of T hours
|
||||
* needs notional ∝ 1/σ, so its cost — spread plus both commissions plus
|
||||
* financing over the hold, all proportional to notional — is proportional to
|
||||
* (spread + 2c + f·T/24) / σ. A volatile instrument reaches the take-profit
|
||||
* with less notional, which is what makes a tight spread alone not enough.
|
||||
* A leg sized for a hold of T hours needs notional ∝ 1/σ, so its cost —
|
||||
* spread plus both commissions plus financing over the hold, all
|
||||
* proportional to notional — is proportional to (spread + 2c + f·T/24) / σ.
|
||||
* A volatile instrument reaches the take-profit with less notional, which is
|
||||
* what makes a tight spread alone not enough.
|
||||
*
|
||||
* Fixed sizing: the notional is set by the symbol map, so only the costs per
|
||||
* dollar of notional matter.
|
||||
*
|
||||
* null = can't be scored (volatility sizing without a σ).
|
||||
* null = can't be scored (no σ).
|
||||
*/
|
||||
export function scoreCandidate(o: {
|
||||
spreadFraction: number;
|
||||
@@ -230,13 +227,9 @@ export function scoreCandidate(o: {
|
||||
targetHoldHours: number;
|
||||
volatilityPerHour: number | null;
|
||||
}): number | null {
|
||||
const perNotional = o.spreadFraction + 2 * (o.commissionPct / 100);
|
||||
if (o.targetHoldHours > 0) {
|
||||
if (!o.volatilityPerHour || !(o.volatilityPerHour > 0)) return null;
|
||||
const financing = (o.financingPctPerDay / 100) * (o.targetHoldHours / 24);
|
||||
return (perNotional + financing) / o.volatilityPerHour;
|
||||
}
|
||||
return perNotional;
|
||||
if (!o.volatilityPerHour || !(o.volatilityPerHour > 0)) return null;
|
||||
const financing = (o.financingPctPerDay / 100) * (Math.max(0, o.targetHoldHours) / 24);
|
||||
return (o.spreadFraction + 2 * (o.commissionPct / 100) + financing) / o.volatilityPerHour;
|
||||
}
|
||||
|
||||
/** Most a spread may be, as a share of the mid price, for a candidate to trade. */
|
||||
@@ -252,7 +245,6 @@ export function candidateRejection(o: {
|
||||
quoteAgeMs: number | null;
|
||||
inHours: boolean;
|
||||
candles: number;
|
||||
needVolatility: boolean;
|
||||
minContractsOk: boolean;
|
||||
currency: string | undefined;
|
||||
accountCurrency: string;
|
||||
@@ -263,7 +255,7 @@ export function candidateRejection(o: {
|
||||
if (o.quoteAgeMs !== null && o.quoteAgeMs > MAX_QUOTE_AGE_MS) return `quote ${Math.round(o.quoteAgeMs / 60_000)}m old`;
|
||||
const mid = (o.bid + o.ask) / 2;
|
||||
if ((o.ask - o.bid) / mid > MAX_SPREAD_FRACTION) return `spread ${(((o.ask - o.bid) / mid) * 100).toFixed(2)}%`;
|
||||
if (o.needVolatility && o.candles < MIN_VOL_CANDLES + 1) return `only ${o.candles} hourly candles`;
|
||||
if (o.candles < MIN_VOL_CANDLES + 1) return `only ${o.candles} hourly candles`;
|
||||
if (!o.minContractsOk) return 'minimum order size out of reach';
|
||||
if (o.currency && o.currency !== o.accountCurrency) return `priced in ${o.currency}`;
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user