trading-logic: when baseAmount=0 (consistency=0%), target cappedByFuture directly instead of collapsing to minDayPnL. For a $4000 target with $150 min-day and 5 days, day 1 now correctly targets $3400 ($4000 - 4×$150) then $150 for each remaining mandatory day. tradovate-class: make fetchDaysTraded() public so auto-trade can call it immediately after a trade exits. Fix daysTraded to count only positive-P&L days from the FIFO results, consistent with computeDailyTarget's positiveDays.length — previously counted all raw fill dates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
92 lines
3.9 KiB
TypeScript
92 lines
3.9 KiB
TypeScript
/** Dollar-per-point value for common futures products. */
|
||
export const POINT_VALUES: { [symbol: string]: number } = {
|
||
NQ: 20, MNQ: 2, ES: 50, MES: 5,
|
||
YM: 5, MYM: 0.5, RTY: 50, M2K: 10,
|
||
GC: 100, MGC: 10, SI: 50, CL: 1000,
|
||
MCL: 100, NG: 10000, ZB: 1000, ZN: 1000,
|
||
ZF: 1000, '6E': 125000, '6J': 12500000, '6B': 62500,
|
||
};
|
||
|
||
/**
|
||
* Compute the next trading day's profit target for an account.
|
||
*
|
||
* Path 1 – No positive trading days yet:
|
||
* target = profitTarget × consistency
|
||
*
|
||
* Path 2 – At least one positive day exists:
|
||
* maxDay = highest single-day P&L so far
|
||
* realTarget = maxDay / consistency (the total profit at which maxDay ≤ consistency% of total)
|
||
* needed = realTarget − totalProfit
|
||
*
|
||
* if needed > maxDay → target maxDay (still a long way from the real target; trade a normal day)
|
||
* else → target needed (close to the real target; aim for exactly what's left)
|
||
*
|
||
* Min-day reservation (only when minDayPnL > 0):
|
||
* When there are still mandatory trading days remaining, today's target is capped so that
|
||
* enough profit is reserved for each future mandatory day to meet minDayPnL.
|
||
* Cap = (profitTarget - totalProfit) − (remainingDaysAfterToday × minDayPnL)
|
||
* Floor = minDayPnL (we must make at least this today)
|
||
*/
|
||
export function computeDailyTarget(
|
||
profitTarget: number,
|
||
consistency: number,
|
||
totalProfit: number,
|
||
dailyPnL: { date: string; pnl: number }[],
|
||
minDayPnL: number = 0, // -999 or 0 = no minimum per day
|
||
minTradingDays: number = 0 // 0 = no minimum trading days
|
||
): { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } {
|
||
const positiveDays = dailyPnL.filter((d) => d.pnl > 0);
|
||
const daysTraded = positiveDays.length;
|
||
|
||
// --- Base target via consistency logic ---
|
||
let baseAmount: number;
|
||
let path: 'first_day' | 'normal_day' | 'reduced_day';
|
||
|
||
if (daysTraded === 0) {
|
||
baseAmount = profitTarget * consistency;
|
||
path = 'first_day';
|
||
} else {
|
||
const maxDay = Math.max(...positiveDays.map((d) => d.pnl));
|
||
const realTarget = maxDay / consistency;
|
||
const needed = realTarget - totalProfit;
|
||
|
||
if (needed > maxDay) {
|
||
baseAmount = maxDay;
|
||
path = 'normal_day';
|
||
} else {
|
||
baseAmount = Math.max(0, needed);
|
||
path = 'reduced_day';
|
||
}
|
||
}
|
||
|
||
// --- Min-day reservation (only when minDayPnL is a positive value) ---
|
||
const effectiveMinDay = minDayPnL > 0 ? minDayPnL : 0;
|
||
|
||
if (effectiveMinDay > 0 && minTradingDays > daysTraded) {
|
||
const remaining = profitTarget - totalProfit; // intentionally NOT clamped — can be negative
|
||
|
||
if (remaining <= 0) {
|
||
// Profit target already met but mandatory trading days not yet satisfied.
|
||
// Trade exactly minDayPnL each remaining day.
|
||
return { amount: effectiveMinDay, path };
|
||
}
|
||
|
||
const remainingMandatoryDays = minTradingDays - daysTraded; // includes today
|
||
const futureReserve = (remainingMandatoryDays - 1) * effectiveMinDay;
|
||
|
||
// Cap: don't take more than what's available after reserving future days
|
||
const cappedByFuture = remaining - futureReserve;
|
||
// Floor: must make at least minDayPnL today (or whatever is left if less)
|
||
const floor = Math.min(effectiveMinDay, remaining);
|
||
|
||
// When consistency = 0% (baseAmount = 0), there's no consistency-based upper bound —
|
||
// target cappedByFuture directly (make as much as possible today, reserve future days).
|
||
// When consistency > 0%, treat baseAmount as the consistency cap.
|
||
const consistencyCapped = baseAmount > 0 ? Math.min(baseAmount, cappedByFuture) : cappedByFuture;
|
||
const amount = Math.max(floor, consistencyCapped);
|
||
return { amount: Math.round(amount * 100) / 100, path };
|
||
}
|
||
|
||
return { amount: Math.round(baseAmount * 100) / 100, path };
|
||
}
|