Drain: random first day, then size the rest by consistency
Only the drain cycle's first day draws from the $4-$15 grid. Under a consistency rule, later days follow the standard consistency math and the day that can close the cycle aims at the midpoint of the amounts that close it, so accounts drained together finish together whatever their first draw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b932452e98
commit
61384c9fa6
+36
-26
@@ -3,9 +3,10 @@ export interface WithdrawalStage {
|
||||
consistency: number;
|
||||
minTradingDays: number;
|
||||
/** Spam-withdraw stage: no profit target — book minTradingDays qualifying
|
||||
* days of ~minDayPnL (tiny MNQ trades), then wait for the withdrawal.
|
||||
* The stage's consistency still applies: days keep booking until the
|
||||
* cycle's biggest day fits inside that share of the cycle total. */
|
||||
* days of a few dollars (tiny MNQ trades), then wait for the withdrawal.
|
||||
* The stage's consistency still applies: the first day is a random draw,
|
||||
* later days are sized so the cycle's biggest day fits inside that share
|
||||
* of the cycle total (see computeDayPlan). */
|
||||
drain?: boolean;
|
||||
/** Drain stages only: the per-day booked amount (default $5; randomized
|
||||
* the fixed $4–$15 half-dollar grid by drainDayAmount; this field is
|
||||
@@ -139,15 +140,11 @@ export function currentTradeDateCT(now: Date = new Date()): string {
|
||||
return new Date(date).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drain-day target: the stage's min-day amount plus a deterministic bump of
|
||||
* $0–$10 in $0.50 steps — MNQ's per-contract tick value, so every possible
|
||||
* target lands on a realizable exit ($5 min day → $5.00, $5.50, … $15.00
|
||||
* depending on the (account, trade date) pair). Seeded like dailyBump so
|
||||
* every consumer of the plan computes the same number.
|
||||
*/
|
||||
/** Drain-day target grid: $4–$15 in $0.50 steps (23 values), always traded
|
||||
* on MNQ. Drain days qualify at ≥ DRAIN_DAY_MIN, so every draw counts. */
|
||||
/** Drain-day target grid: $4–$15 in $0.50 steps (23 values, MNQ's
|
||||
* per-contract tick), always traded on MNQ. Drain days qualify at ≥
|
||||
* DRAIN_DAY_MIN, so every draw counts. Seeded per (account, trade date) like
|
||||
* dailyBump so every consumer of the plan computes the same number. Under a
|
||||
* consistency rule only the cycle's first drain day draws from it. */
|
||||
export const DRAIN_DAY_MIN = 4;
|
||||
export const DRAIN_DAY_MAX = 15;
|
||||
const DRAIN_DAY_STEPS = Math.round((DRAIN_DAY_MAX - DRAIN_DAY_MIN) / 0.5) + 1;
|
||||
@@ -987,25 +984,38 @@ export function computeDayPlan(
|
||||
const priorDays = dailyPnL.filter((d) => d.date !== tradeDate);
|
||||
|
||||
// Drain stage: no profit target at all. Book minTradingDays qualifying
|
||||
// days at the randomized min-day amount, then go quiet until the next
|
||||
// withdrawal resets the cycle. One unsplit tiny trade per day.
|
||||
// days, then go quiet until the next withdrawal resets the cycle. One
|
||||
// unsplit tiny trade per day. The cycle's first day books a randomized
|
||||
// grid amount (every day does when there is no consistency rule).
|
||||
//
|
||||
// The stage's consistency rule outlives the profit target: the cycle's
|
||||
// biggest day must fit inside `consistency` of the cycle total. Past the
|
||||
// random first day the drain follows the standard consistency math, so
|
||||
// accounts drained together finish together whatever their first draw:
|
||||
// a day that can close the cycle aims at the MIDPOINT of the amounts
|
||||
// that close it (a fill a little over or under still lands inside), and
|
||||
// earlier days size like computeDailyTarget with the target at 0.
|
||||
if (drain) {
|
||||
const qualifying = minDayPnL > 0
|
||||
? priorDays.filter((d) => d.pnl >= minDayPnL)
|
||||
: priorDays.filter((d) => d.pnl !== 0);
|
||||
// The stage's consistency rule outlives the profit target: the cycle's
|
||||
// biggest day must fit inside `consistency` of the cycle total, so the
|
||||
// drain keeps booking days past the min-day count until the total
|
||||
// covers maxDay / consistency.
|
||||
let consistencyShort = false;
|
||||
if (consistency > 0 && consistency < 1 && priorDays.length > 0) {
|
||||
const maxDay = Math.max(0, ...priorDays.map((d) => d.pnl));
|
||||
const total = priorDays.reduce((s, d) => s + d.pnl, 0);
|
||||
consistencyShort = maxDay > 0 && total < round2(maxDay / consistency) - 0.005;
|
||||
const daysDone = qualifying.length >= minTradingDays;
|
||||
let dayTarget: number;
|
||||
if (consistency <= 0 || consistency >= 1 || qualifying.length === 0) {
|
||||
dayTarget = daysDone ? 0 : drainDayAmount(accountId, tradeDate);
|
||||
} else {
|
||||
const maxDay = Math.max(...qualifying.map((d) => d.pnl));
|
||||
const total = round2(priorDays.reduce((s, d) => s + d.pnl, 0));
|
||||
const needed = round2(maxDay / consistency - total);
|
||||
// A day of x closes the rule when it covers the shortfall without
|
||||
// topping maxDay (x ≥ needed), or tops it but still fits as the
|
||||
// new biggest day (total + x ≥ x / consistency).
|
||||
const lo = Math.max(needed, minDayPnL);
|
||||
const hi = round2((total * consistency) / (1 - consistency));
|
||||
if (daysDone && needed <= 0) dayTarget = 0;
|
||||
else if (qualifying.length + 1 >= minTradingDays && lo <= hi) dayTarget = round2((lo + hi) / 2);
|
||||
else dayTarget = computeDailyTarget(0, consistency, priorDays, minDayPnL, minTradingDays, total)?.amount ?? 0;
|
||||
}
|
||||
const dayTarget = qualifying.length >= minTradingDays && !consistencyShort
|
||||
? 0
|
||||
: drainDayAmount(accountId, tradeDate);
|
||||
return {
|
||||
dayTarget,
|
||||
remainingToday: round2(dayTarget - realizedPnL),
|
||||
|
||||
Reference in New Issue
Block a user