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,7 +10,7 @@ import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
percentFee, takeProfitPlan, obfuscationStopDollars, dllRoom, dllStopDollars, dllViable, stopTicks,
|
||||
botStopsActive, sizeNotional, isNoTradeTime, isPastSessionClose, nextTradingDate, effectiveCombine,
|
||||
botStopsActive, sizeNotional, isNoTradeTime, isPastSessionClose, nextTradingDate, effectiveCombine, DEFAULT_TARGET_HOLD_HOURS,
|
||||
projectPerfectPath, cycleCompletion, DLL_RESERVE_FRACTION, DLL_MIN_STOP,
|
||||
} from '../lib/trading-logic.ts';
|
||||
|
||||
@@ -258,18 +258,17 @@ const btcSigma = 0.03 / Math.sqrt(24); // 3%/day
|
||||
|
||||
test('volatility sizing hits the target hold, and the example table', () => {
|
||||
const stop = () => 1350;
|
||||
const at = (hours) => sizeNotional({ leg: 600, stopDollarsAt: stop, volatilityPerHour: btcSigma, targetHoldHours: hours, commissionPct: 0, fixedNotional: 0, maxNotional: 0 });
|
||||
const at = (hours) => sizeNotional({ leg: 600, stopDollarsAt: stop, volatilityPerHour: btcSigma, targetHoldHours: hours, commissionPct: 0, maxNotional: 0 });
|
||||
for (const [hours, notional] of [[54, 20000], [24, 30000], [6, 60000]]) {
|
||||
const s = at(hours);
|
||||
assert.equal(s.mode, 'volatility');
|
||||
assert.ok(Math.abs(s.notional - notional) / notional < 0.001, `${hours}h → ${s.notional}`);
|
||||
assert.ok(Math.abs(s.predictedHoldHours - hours) / hours < 0.001);
|
||||
assert.ok(Math.abs(s.winProbability - 1350 / 1950) < 1e-9);
|
||||
}
|
||||
});
|
||||
|
||||
test('sizing: fees feed back, the cap binds, financing only raises N, fixed fallback', () => {
|
||||
const base = { leg: 600, volatilityPerHour: btcSigma, targetHoldHours: 6, commissionPct: 0.04, spreadFraction: 0.0002, fixedNotional: 20000, maxNotional: 0 };
|
||||
test('sizing: fees feed back, the cap binds, financing only raises N, defaults', () => {
|
||||
const base = { leg: 600, volatilityPerHour: btcSigma, targetHoldHours: 6, commissionPct: 0.04, spreadFraction: 0.0002, maxNotional: 0 };
|
||||
// Daily-loss stop shrinks as fees grow with N.
|
||||
const stopDollarsAt = (n) => dllStopDollars({ room: 1500, dailyLossLimit: 1500, entryFee: 0.0004 * n, commissionPct: 0.04 });
|
||||
const s = sizeNotional({ ...base, stopDollarsAt });
|
||||
@@ -286,11 +285,15 @@ test('sizing: fees feed back, the cap binds, financing only raises N, fixed fall
|
||||
const unfinanced = sizeNotional({ ...base, stopDollarsAt, targetHoldHours: 200 });
|
||||
assert.ok(financed.notional > unfinanced.notional);
|
||||
|
||||
const fixed = sizeNotional({ ...base, stopDollarsAt: () => Infinity, leg: 1700 });
|
||||
assert.equal(fixed.mode, 'fixed');
|
||||
assert.equal(fixed.notional, 40000);
|
||||
assert.equal(sizeNotional({ ...base, stopDollarsAt: () => Infinity, fixedNotional: 0 }), null);
|
||||
assert.equal(sizeNotional({ ...base, targetHoldHours: 0, stopDollarsAt }).mode, 'fixed');
|
||||
// No target hold = 12 h.
|
||||
const dflt = sizeNotional({ ...base, targetHoldHours: 0, stopDollarsAt });
|
||||
assert.ok(Math.abs(dflt.predictedHoldHours - DEFAULT_TARGET_HOLD_HOURS) < 0.1, `hold ${dflt.predictedHoldHours}`);
|
||||
// A stopless leg is sized as if the stop sat as far as the target.
|
||||
const stopless = sizeNotional({ ...base, stopDollarsAt: () => Infinity });
|
||||
assert.ok(Math.abs(stopless.predictedHoldHours - 6) < 0.05);
|
||||
assert.equal(stopless.winProbability, null);
|
||||
// No volatility, no size.
|
||||
assert.equal(sizeNotional({ ...base, volatilityPerHour: undefined, stopDollarsAt }), null);
|
||||
});
|
||||
|
||||
test('random-walk simulation agrees with the predicted hold and win rate', () => {
|
||||
@@ -461,15 +464,16 @@ test('the selector ranks the plan example: ETH, BTC, XYZ100, AAPL', () => {
|
||||
const k = Math.sqrt((600 * 1350) / 6);
|
||||
assert.ok(Math.abs(score(0.0003, 0.04) * k - 49.6) < 1);
|
||||
assert.ok(Math.abs(score(0.0002, 0.03) * k - 60) < 1);
|
||||
// Fixed sizing ignores volatility; volatility sizing needs it.
|
||||
assert.equal(scoreCandidate({ spreadFraction: 0.0002, commissionPct: 0.04, financingPctPerDay: 0, targetHoldHours: 0, volatilityPerHour: null }), 0.001);
|
||||
// No volatility, no score; financing over the hold adds to the cost.
|
||||
assert.equal(scoreCandidate({ spreadFraction: 0.0002, commissionPct: 0.04, financingPctPerDay: 0, targetHoldHours: 6, volatilityPerHour: null }), null);
|
||||
const withFinancing = scoreCandidate({ spreadFraction: 0.0002, commissionPct: 0.04, financingPctPerDay: 0.033, targetHoldHours: 12, volatilityPerHour: perHour(0.03) });
|
||||
assert.ok(withFinancing > score(0.0002, 0.03));
|
||||
});
|
||||
|
||||
test('the selector drops what cannot trade right now', () => {
|
||||
const ok = {
|
||||
tradingStatus: 'FULL', bid: 100, ask: 100.02, quoteAgeMs: 1000, inHours: true, candles: 49,
|
||||
needVolatility: true, minContractsOk: true, currency: 'USD', accountCurrency: 'USD',
|
||||
minContractsOk: true, currency: 'USD', accountCurrency: 'USD',
|
||||
};
|
||||
assert.equal(candidateRejection(ok), null);
|
||||
assert.match(candidateRejection({ ...ok, tradingStatus: 'CLOSE_ONLY' }), /CLOSE_ONLY/);
|
||||
@@ -478,6 +482,5 @@ test('the selector drops what cannot trade right now', () => {
|
||||
assert.match(candidateRejection({ ...ok, quoteAgeMs: 10 * 60_000 }), /old/);
|
||||
assert.match(candidateRejection({ ...ok, ask: 101 }), /spread/);
|
||||
assert.match(candidateRejection({ ...ok, candles: 10 }), /candles/);
|
||||
assert.equal(candidateRejection({ ...ok, candles: 10, needVolatility: false }), null);
|
||||
assert.match(candidateRejection({ ...ok, currency: 'EUR' }), /EUR/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user