Reset days traded counter from last fund transaction date
Tracks Fund Transaction entries in the Cash History report to find the most recent account funding/reset date. Only trading days on or after that date count toward daysTraded and the daily target calculation. The last fund date is persisted in SQLite so it survives beyond the 28-day Tradovate report window. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6b299f5359
commit
6d960ceb7b
+33
-9
@@ -5,7 +5,7 @@ import type { AccountItem, AuthLoginResponse, Contract } from './tradovate-helpe
|
||||
import { computeSec, randomUUIDV4 } from './tradovate-helpers';
|
||||
import { POINT_VALUES } from './trading-logic';
|
||||
import { getCachedContract, resolveContracts } from './contract-resolver';
|
||||
import { saveDailyPnL, loadDailyPnL } from './db';
|
||||
import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta } from './db';
|
||||
|
||||
export class TradovateClient {
|
||||
private name: string;
|
||||
@@ -31,6 +31,8 @@ export class TradovateClient {
|
||||
|
||||
public daysTraded: { [accountId: number]: number } = {};
|
||||
public dailyPnL: { [accountId: number]: { date: string; pnl: number }[] } = {};
|
||||
/** Date of the last fund transaction per account — days traded are counted from this date onwards */
|
||||
public lastFundDates: { [accountId: number]: string | null } = {};
|
||||
/** Balance floor set by the prop firm — account is blown when amount <= this value (0 = not set) */
|
||||
public autoLiqThresholds: { [accountId: number]: number } = {};
|
||||
|
||||
@@ -406,11 +408,16 @@ export class TradovateClient {
|
||||
};
|
||||
|
||||
for (const account of this.accountList) {
|
||||
// Load last known fund date from DB — used to filter days traded to the current challenge period
|
||||
const storedFundDate = loadAccountMeta(account.id, 'last_fund_date');
|
||||
this.lastFundDates[account.id] = storedFundDate;
|
||||
|
||||
// Load cache first — serves as both the startup baseline and the fallback if API fails
|
||||
const cached = loadDailyPnL(account.id);
|
||||
if (cached.length > 0) {
|
||||
this.dailyPnL[account.id] = cached;
|
||||
this.daysTraded[account.id] = cached.filter((d) => d.pnl !== 0).length;
|
||||
const active = storedFundDate ? cached.filter((d) => d.date >= storedFundDate) : cached;
|
||||
this.dailyPnL[account.id] = active;
|
||||
this.daysTraded[account.id] = active.filter((d) => d.pnl !== 0).length;
|
||||
}
|
||||
|
||||
// --- Try Cash History first (true after-fee daily P&L) ---
|
||||
@@ -420,21 +427,34 @@ export class TradovateClient {
|
||||
const rows: { Date: string; Delta: string; 'Cash Change Type': string; [k: string]: any }[] = JSON.parse(fixed);
|
||||
this.lastFetchRaw[account.name] = rows.length > 0 ? JSON.stringify(rows[0]) : '(empty)';
|
||||
|
||||
const fundDates: string[] = [];
|
||||
const dailyMap: { [date: string]: number } = {};
|
||||
for (const row of rows) {
|
||||
if ((row['Cash Change Type'] ?? '').trim() === 'Fund Transaction') continue;
|
||||
if ((row['Cash Change Type'] ?? '').trim() === 'Fund Transaction') {
|
||||
fundDates.push(row['Date']);
|
||||
continue;
|
||||
}
|
||||
const delta = parseFloat((row['Delta'] ?? '0').replace(/,/g, ''));
|
||||
if (isNaN(delta)) continue;
|
||||
dailyMap[row['Date']] = (dailyMap[row['Date']] ?? 0) + delta;
|
||||
}
|
||||
|
||||
// Use the most recent fund transaction as the reset point — persist it so it survives beyond the 28-day window
|
||||
const lastFundDate = fundDates.sort().pop() ?? null;
|
||||
if (lastFundDate) {
|
||||
saveAccountMeta(account.id, 'last_fund_date', lastFundDate);
|
||||
this.lastFundDates[account.id] = lastFundDate;
|
||||
}
|
||||
const fundDate = this.lastFundDates[account.id];
|
||||
|
||||
const fresh = Object.entries(dailyMap)
|
||||
.map(([date, pnl]) => ({ date, pnl: Math.round(pnl * 100) / 100 }))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
|
||||
const merged = mergePnL(cached, fresh);
|
||||
this.dailyPnL[account.id] = merged;
|
||||
this.daysTraded[account.id] = merged.filter((d) => d.pnl !== 0).length;
|
||||
const active = fundDate ? merged.filter((d) => d.date >= fundDate) : merged;
|
||||
this.dailyPnL[account.id] = active;
|
||||
this.daysTraded[account.id] = active.filter((d) => d.pnl !== 0).length;
|
||||
saveDailyPnL(account.id, account.name, fresh); // upsert only fresh entries — preserves older cached rows
|
||||
delete this.lastFetchErrors[account.name];
|
||||
continue;
|
||||
@@ -484,19 +504,23 @@ export class TradovateClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Fills report has no fund transaction data — use stored fund date
|
||||
const fundDate = this.lastFundDates[account.id];
|
||||
|
||||
const fresh = Object.entries(dailyMap)
|
||||
.map(([date, pnl]) => ({ date, pnl: Math.round(pnl * 100) / 100 }))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
|
||||
const merged = mergePnL(cached, fresh);
|
||||
this.dailyPnL[account.id] = merged;
|
||||
this.daysTraded[account.id] = merged.filter((d) => d.pnl !== 0).length;
|
||||
const active = fundDate ? merged.filter((d) => d.date >= fundDate) : merged;
|
||||
this.dailyPnL[account.id] = active;
|
||||
this.daysTraded[account.id] = active.filter((d) => d.pnl !== 0).length;
|
||||
saveDailyPnL(account.id, account.name, fresh); // upsert only fresh entries — preserves older cached rows
|
||||
this.lastFetchErrors[account.name] = 'cash history unavailable (using fills fallback)';
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error(`[fetchDaysTraded] both reports failed for ${account.name}:`, msg);
|
||||
// Cache already loaded at top of loop — just log the error
|
||||
// Cache already loaded and filtered at top of loop — just log the error
|
||||
if (cached.length > 0) {
|
||||
this.lastFetchErrors[account.name] = `${msg} (using ${cached.length} cached entries from database)`;
|
||||
console.log(`[fetchDaysTraded] using ${cached.length} cached entries from DB for ${account.name}`);
|
||||
|
||||
Reference in New Issue
Block a user