Show fund transactions (W/D) on calendar, equity curve, and cash history table
- Persist fund transactions to SQLite (fund_transactions table) so they survive beyond Tradovate's 28-day report window - Calendar: highlight W/D dates in amber with the amount shown below the day - Equity curve: reduce running equity at withdrawal dates and show a vertical dashed amber line labelled W/D - New Cash History table below calendar listing all trades and W/D events sorted newest-first Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6d960ceb7b
commit
4252adfbea
@@ -227,6 +227,30 @@ export function loadDailyPnL(accountId: number): { date: string; pnl: number }[]
|
||||
return (db.prepare('SELECT date, pnl FROM daily_pnl WHERE account_id = ? ORDER BY date').all(accountId) as { date: string; pnl: number }[]);
|
||||
}
|
||||
|
||||
// ── Fund Transactions Cache ───────────────────────────────────────────────────
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS fund_transactions (
|
||||
account_id INTEGER NOT NULL,
|
||||
account_name TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
PRIMARY KEY (account_id, date)
|
||||
);
|
||||
`);
|
||||
|
||||
export function saveFundTransactions(accountId: number, accountName: string, entries: { date: string; amount: number }[]): void {
|
||||
const ins = db.prepare('INSERT OR REPLACE INTO fund_transactions (account_id, account_name, date, amount) VALUES (?, ?, ?, ?)');
|
||||
const txn = db.transaction(() => {
|
||||
for (const e of entries) ins.run(accountId, accountName, e.date, e.amount);
|
||||
});
|
||||
txn();
|
||||
}
|
||||
|
||||
export function loadFundTransactions(accountId: number): { date: string; amount: number }[] {
|
||||
return db.prepare('SELECT date, amount FROM fund_transactions WHERE account_id = ? ORDER BY date').all(accountId) as { date: string; amount: number }[];
|
||||
}
|
||||
|
||||
// ── Account Meta ─────────────────────────────────────────────────────────────
|
||||
|
||||
db.exec(`
|
||||
|
||||
+18
-5
@@ -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, saveAccountMeta, loadAccountMeta } from './db';
|
||||
import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta, saveFundTransactions, loadFundTransactions } from './db';
|
||||
|
||||
export class TradovateClient {
|
||||
private name: string;
|
||||
@@ -33,6 +33,8 @@ export class TradovateClient {
|
||||
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 } = {};
|
||||
/** All fund transactions (deposits/withdrawals) per account */
|
||||
public fundTransactions: { [accountId: number]: { date: string; amount: number }[] } = {};
|
||||
/** Balance floor set by the prop firm — account is blown when amount <= this value (0 = not set) */
|
||||
public autoLiqThresholds: { [accountId: number]: number } = {};
|
||||
|
||||
@@ -408,9 +410,10 @@ 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
|
||||
// Load last known fund date and fund transactions from DB
|
||||
const storedFundDate = loadAccountMeta(account.id, 'last_fund_date');
|
||||
this.lastFundDates[account.id] = storedFundDate;
|
||||
this.fundTransactions[account.id] = loadFundTransactions(account.id);
|
||||
|
||||
// Load cache first — serves as both the startup baseline and the fallback if API fails
|
||||
const cached = loadDailyPnL(account.id);
|
||||
@@ -427,11 +430,12 @@ 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 fundMap: { [date: string]: number } = {};
|
||||
const dailyMap: { [date: string]: number } = {};
|
||||
for (const row of rows) {
|
||||
if ((row['Cash Change Type'] ?? '').trim() === 'Fund Transaction') {
|
||||
fundDates.push(row['Date']);
|
||||
const delta = parseFloat((row['Delta'] ?? '0').replace(/,/g, ''));
|
||||
if (!isNaN(delta)) fundMap[row['Date']] = (fundMap[row['Date']] ?? 0) + delta;
|
||||
continue;
|
||||
}
|
||||
const delta = parseFloat((row['Delta'] ?? '0').replace(/,/g, ''));
|
||||
@@ -439,8 +443,17 @@ export class TradovateClient {
|
||||
dailyMap[row['Date']] = (dailyMap[row['Date']] ?? 0) + delta;
|
||||
}
|
||||
|
||||
// Persist fund transactions so they survive beyond the 28-day window
|
||||
const freshFundTxns = Object.entries(fundMap)
|
||||
.map(([date, amount]) => ({ date, amount: Math.round(amount * 100) / 100 }))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
if (freshFundTxns.length > 0) {
|
||||
saveFundTransactions(account.id, account.name, freshFundTxns);
|
||||
this.fundTransactions[account.id] = loadFundTransactions(account.id); // reload merged full history
|
||||
}
|
||||
|
||||
// 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;
|
||||
const lastFundDate = [...Object.keys(fundMap)].sort().pop() ?? null;
|
||||
if (lastFundDate) {
|
||||
saveAccountMeta(account.id, 'last_fund_date', lastFundDate);
|
||||
this.lastFundDates[account.id] = lastFundDate;
|
||||
|
||||
Reference in New Issue
Block a user