Honor the catalog's min-legs setting and carve $500 pieces
Big days (> the $1,700 split threshold) must carve at least the account type's minLegs from the catalog — pickLegAmount reserves the minimum piece for each leg still owed, forcing a carve even once the residual dips below the threshold, clamped to what the pieces allow. Days at or below the threshold still trade as one leg. The smallest split piece drops from $800 to $500. An older master without the field means no minimum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
620ab54d42
commit
ae0f93a44f
@@ -37,6 +37,7 @@ export async function GET() {
|
|||||||
maxPositionSize: a.maxPositionSize,
|
maxPositionSize: a.maxPositionSize,
|
||||||
targetSameEquity: a.targetSameEquity,
|
targetSameEquity: a.targetSameEquity,
|
||||||
splitEnabled: a.splitEnabled,
|
splitEnabled: a.splitEnabled,
|
||||||
|
minLegs: a.minLegs,
|
||||||
combineCmeHolidays: a.combineCmeHolidays,
|
combineCmeHolidays: a.combineCmeHolidays,
|
||||||
withdrawalStages: a.withdrawalStages,
|
withdrawalStages: a.withdrawalStages,
|
||||||
})),
|
})),
|
||||||
|
|||||||
+17
-4
@@ -13,7 +13,7 @@ import { getTradableFirms } from './firm-catalog';
|
|||||||
import { getAccountConfig } from './account-type-resolver';
|
import { getAccountConfig } from './account-type-resolver';
|
||||||
import {
|
import {
|
||||||
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, currentTradeDateCT,
|
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, currentTradeDateCT,
|
||||||
LEG_COMPLETION_EPSILON, MAX_LEGS_PER_DAY, POINT_VALUES,
|
BIG_DAY_THRESHOLD, LEG_COMPLETION_EPSILON, MAX_LEGS_PER_DAY, POINT_VALUES,
|
||||||
type DayPlan,
|
type DayPlan,
|
||||||
} from './trading-logic';
|
} from './trading-logic';
|
||||||
import { getSetting, setSetting } from './db';
|
import { getSetting, setSetting } from './db';
|
||||||
@@ -354,11 +354,19 @@ export async function runTrade(action: 'Buy' | 'Sell' | 'Auto', symbol: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trade one leg toward today's remaining target. Split-allowed big days carve a
|
// Trade one leg toward today's remaining target. Split-allowed big days carve a
|
||||||
// ≥$800 piece; everything else trades the full remaining amount in one go. The
|
// ≥$500 piece; everything else trades the full remaining amount in one go. The
|
||||||
// carve fraction is seeded per (prefix, date, leg number) so like accounts in a
|
// carve fraction is seeded per (prefix, date, leg number) so like accounts in a
|
||||||
// batch split their targets the same way, or close to it when remainders differ.
|
// batch split their targets the same way, or close to it when remainders differ.
|
||||||
|
// An account type's min-legs setting forces at least that many carves
|
||||||
|
// on BIG days only — a day at or below the threshold still trades as
|
||||||
|
// one leg. Set per prefix, so like accounts still split alike.
|
||||||
|
const minLegs = item.plan.dayTarget > BIG_DAY_THRESHOLD ? (cfg.minLegs ?? 0) : 0;
|
||||||
const legAmount = item.plan.splitAllowed
|
const legAmount = item.plan.splitAllowed
|
||||||
? pickLegAmount(item.plan.remainingToday, () => groupLegFraction(item.prefix, tradeDate, legsToday(acc.id, tradeDate)))
|
? pickLegAmount(
|
||||||
|
item.plan.remainingToday,
|
||||||
|
() => groupLegFraction(item.prefix, tradeDate, legsToday(acc.id, tradeDate)),
|
||||||
|
minLegs - legsToday(acc.id, tradeDate),
|
||||||
|
)
|
||||||
: item.plan.remainingToday;
|
: item.plan.remainingToday;
|
||||||
recordLeg(acc.id, tradeDate);
|
recordLeg(acc.id, tradeDate);
|
||||||
|
|
||||||
@@ -567,8 +575,13 @@ export async function copyTrade() {
|
|||||||
const { client, acc, contract } = item;
|
const { client, acc, contract } = item;
|
||||||
const cfg = getAccountConfig(acc.id).accountType!;
|
const cfg = getAccountConfig(acc.id).accountType!;
|
||||||
|
|
||||||
|
const minLegs = item.plan.dayTarget > BIG_DAY_THRESHOLD ? (cfg.minLegs ?? 0) : 0;
|
||||||
const legAmount = item.plan.splitAllowed
|
const legAmount = item.plan.splitAllowed
|
||||||
? pickLegAmount(item.plan.remainingToday, () => groupLegFraction(item.prefix, tradeDate, legsToday(acc.id, tradeDate)))
|
? pickLegAmount(
|
||||||
|
item.plan.remainingToday,
|
||||||
|
() => groupLegFraction(item.prefix, tradeDate, legsToday(acc.id, tradeDate)),
|
||||||
|
minLegs - legsToday(acc.id, tradeDate),
|
||||||
|
)
|
||||||
: item.plan.remainingToday;
|
: item.plan.remainingToday;
|
||||||
recordLeg(acc.id, tradeDate);
|
recordLeg(acc.id, tradeDate);
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ function validatePayload(raw: unknown): FirmCatalogPayload {
|
|||||||
...type,
|
...type,
|
||||||
splitEnabled: type.splitEnabled !== false,
|
splitEnabled: type.splitEnabled !== false,
|
||||||
combineCmeHolidays: type.combineCmeHolidays === true,
|
combineCmeHolidays: type.combineCmeHolidays === true,
|
||||||
|
// Lenient: a master from before the min-legs field means "no minimum".
|
||||||
|
minLegs: typeof type.minLegs === 'number' ? type.minLegs : 0,
|
||||||
} as unknown as CatalogAccountType;
|
} as unknown as CatalogAccountType;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -108,8 +108,12 @@ export interface CatalogAccountType {
|
|||||||
maxLoss: number;
|
maxLoss: number;
|
||||||
maxPositionSize: number;
|
maxPositionSize: number;
|
||||||
targetSameEquity: boolean;
|
targetSameEquity: boolean;
|
||||||
/** False = big days trade as one unsplit leg instead of random ≥$800 pieces. */
|
/** False = big days trade as one unsplit leg instead of random ≥$500 pieces. */
|
||||||
splitEnabled: boolean;
|
splitEnabled: boolean;
|
||||||
|
/** Big days (> the $1,700 split threshold) must carve at least this many
|
||||||
|
* legs (0 = however the carve falls). Smaller days always trade as one
|
||||||
|
* leg; clamped to what ≥$500 pieces make possible. */
|
||||||
|
minLegs: number;
|
||||||
/** True = CME holiday sessions count with the next full trading day, the way
|
/** True = CME holiday sessions count with the next full trading day, the way
|
||||||
* firms that follow CME clearing report them (e.g. Apex: Sun/Mon/Tue around
|
* firms that follow CME clearing report them (e.g. Apex: Sun/Mon/Tue around
|
||||||
* a Monday holiday is ONE trading day, recorded on the Tuesday). */
|
* a Monday holiday is ONE trading day, recorded on the Tuesday). */
|
||||||
|
|||||||
+16
-2
@@ -53,7 +53,7 @@ export function resolveEffectiveProfitTarget(
|
|||||||
/** Split a day's target into multiple legs when it exceeds this. */
|
/** Split a day's target into multiple legs when it exceeds this. */
|
||||||
export const BIG_DAY_THRESHOLD = 1700;
|
export const BIG_DAY_THRESHOLD = 1700;
|
||||||
/** Every split leg must be at least this. */
|
/** Every split leg must be at least this. */
|
||||||
export const MIN_LEG = 800;
|
export const MIN_LEG = 500;
|
||||||
/** After at least one trade, a residual at or below this counts as "day done". */
|
/** After at least one trade, a residual at or below this counts as "day done". */
|
||||||
export const LEG_COMPLETION_EPSILON = 25;
|
export const LEG_COMPLETION_EPSILON = 25;
|
||||||
/** Safety valve: never fire more than this many legs per account per trade date. */
|
/** Safety valve: never fire more than this many legs per account per trade date. */
|
||||||
@@ -112,8 +112,22 @@ export function groupLegFraction(prefix: string, tradeDate: string, legIndex: nu
|
|||||||
* Pick the next leg to trade toward today's remaining target.
|
* Pick the next leg to trade toward today's remaining target.
|
||||||
* remaining ≤ BIG_DAY_THRESHOLD → trade it all in one final leg; otherwise carve a random
|
* remaining ≤ BIG_DAY_THRESHOLD → trade it all in one final leg; otherwise carve a random
|
||||||
* $5-rounded piece from [MIN_LEG, remaining − MIN_LEG] so the remainder also stays ≥ MIN_LEG.
|
* $5-rounded piece from [MIN_LEG, remaining − MIN_LEG] so the remainder also stays ≥ MIN_LEG.
|
||||||
|
*
|
||||||
|
* minLegsLeft (an account's min-legs setting minus the legs already fired
|
||||||
|
* today) forces a carve even below the big-day threshold and reserves MIN_LEG
|
||||||
|
* for each leg still owed after this one. It is clamped to what MIN_LEG-sized
|
||||||
|
* pieces make possible, so a small coast day is never forced into impossible
|
||||||
|
* splits.
|
||||||
*/
|
*/
|
||||||
export function pickLegAmount(remainingToday: number, rand: () => number = Math.random): number {
|
export function pickLegAmount(remainingToday: number, rand: () => number = Math.random, minLegsLeft = 0): number {
|
||||||
|
const owedAfter = Math.min(minLegsLeft, Math.floor(remainingToday / MIN_LEG)) - 1;
|
||||||
|
if (owedAfter > 0) {
|
||||||
|
const lo = MIN_LEG;
|
||||||
|
const hi = remainingToday - owedAfter * MIN_LEG;
|
||||||
|
if (hi <= lo) return lo;
|
||||||
|
const leg = Math.round((lo + rand() * (hi - lo)) / 5) * 5;
|
||||||
|
return Math.min(Math.max(leg, lo), hi);
|
||||||
|
}
|
||||||
if (remainingToday <= BIG_DAY_THRESHOLD) return round2(remainingToday);
|
if (remainingToday <= BIG_DAY_THRESHOLD) return round2(remainingToday);
|
||||||
const lo = MIN_LEG;
|
const lo = MIN_LEG;
|
||||||
const hi = remainingToday - MIN_LEG;
|
const hi = remainingToday - MIN_LEG;
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export interface AccountConfig {
|
|||||||
maxLoss: number; // 0 = no limit; positive = account is "Dead" when loss exceeds this
|
maxLoss: number; // 0 = no limit; positive = account is "Dead" when loss exceeds this
|
||||||
maxPositionSize: number; // 0 = no limit; positive = max contracts per trade
|
maxPositionSize: number; // 0 = no limit; positive = max contracts per trade
|
||||||
targetSameEquity: boolean; // if true, reduce profitTarget by priorProfit after withdrawal
|
targetSameEquity: boolean; // if true, reduce profitTarget by priorProfit after withdrawal
|
||||||
splitEnabled: boolean; // false = big days trade as one unsplit leg instead of random ≥$800 pieces
|
splitEnabled: boolean; // false = big days trade as one unsplit leg instead of random ≥$500 pieces
|
||||||
|
minLegs: number; // big days (> $1,700) must carve at least this many legs (0 = however the carve falls)
|
||||||
combineCmeHolidays: boolean; // true = CME holiday sessions count with the next full trading day (Apex-style)
|
combineCmeHolidays: boolean; // true = CME holiday sessions count with the next full trading day (Apex-style)
|
||||||
withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[]; // per-stage fresh profit + consistency (empty = use base values)
|
withdrawalStages: { profit: number; consistency: number; minTradingDays: number }[]; // per-stage fresh profit + consistency (empty = use base values)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user