Skip to content

Commit e2d200d

Browse files
feat : Account Snapshot
1 parent 6fcf442 commit e2d200d

File tree

2 files changed

+75
-5
lines changed

2 files changed

+75
-5
lines changed

server/src/bot.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { invokeAgent } from './lib/agent';
2-
import { getOrCreateAccount, getOpenPositions, updatePositionPnL } from './lib/exchange/helper';
2+
import { getOrCreateAccount, getOpenPositions, updatePositionPnL, createAccountSnapshot } from './lib/exchange/helper';
33
import { fetchMarketData } from './lib/exchange';
44
import { closeDatabase } from './config/database';
55
import type { SessionState } from './types';
@@ -67,6 +67,13 @@ export class TradingBot {
6767
}
6868
}
6969

70+
// Create snapshot after updating positions
71+
try {
72+
await createAccountSnapshot(this.accountId);
73+
} catch (error) {
74+
console.error('Error creating account snapshot:', error);
75+
}
76+
7077
console.log(`📊 Updated ${positions.length} positions`);
7178
} catch (error) {
7279
console.error('Error updating positions:', error);
@@ -101,6 +108,15 @@ export class TradingBot {
101108
// Update session state
102109
this.sessionState.invocationCount++;
103110

111+
// Create snapshot after each trading cycle
112+
if (this.accountId) {
113+
try {
114+
await createAccountSnapshot(this.accountId);
115+
} catch (error) {
116+
console.error('Error creating account snapshot:', error);
117+
}
118+
}
119+
104120
} catch (error) {
105121
console.error('Error in trading cycle:', error);
106122
this.sessionState.invocationCount++;

server/src/lib/exchange/helper.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { db, accounts, positions, orders } from '../../config/database';
1+
import { db, accounts, positions, orders, accountSnapshots } from '../../config/database';
22
import { eq, and, desc } from 'drizzle-orm';
33
import { isSupportedSymbol } from '../../config/exchange';
44

@@ -24,6 +24,10 @@ export async function getOrCreateAccount(initialBalance: number = 10000) {
2424
initial_balance: initialBalance.toString(),
2525
current_balance: initialBalance.toString(),
2626
total_pnl: '0',
27+
account_value: initialBalance.toString(),
28+
crypto_value: '0',
29+
total_return_percent: '0',
30+
sharpe_ratio: null,
2731
})
2832
.returning();
2933

@@ -103,6 +107,9 @@ export async function updatePositionPnL(positionId: string, currentPrice: number
103107
.where(eq(positions.id, positionId))
104108
.returning();
105109

110+
// Update account metrics after position PnL update
111+
await getAccountMetrics(updated.account_id);
112+
106113
return updated;
107114
}
108115

@@ -177,10 +184,14 @@ export async function createPosition(
177184
price: price.toString(),
178185
status: 'FILLED',
179186
filled_price: price.toString(),
187+
trade_value: tradeValue.toString(),
180188
position_id: newPosition.id,
181189
})
182190
.returning();
183191

192+
// Update account metrics after position creation
193+
await getAccountMetrics(accountId);
194+
184195
return {
185196
position: newPosition,
186197
order,
@@ -282,18 +293,22 @@ export async function closePosition(
282293
status: 'FILLED',
283294
filled_price: currentPrice.toString(),
284295
realized_pnl: realizedPnL.toString(),
296+
trade_value: tradeValue.toString(),
285297
position_id: existingPosition.id,
286298
})
287299
.returning();
288300

301+
// Update account metrics after position closure
302+
await getAccountMetrics(accountId);
303+
289304
return {
290305
realizedPnL,
291306
closedQuantity: closingQuantity,
292307
order,
293308
};
294309
}
295310

296-
// Get account metrics for agent
311+
// Get account metrics for agent and update database
297312
export async function getAccountMetrics(accountId: string) {
298313
const account = await getAccountBalance(accountId);
299314
const openPositions = await getOpenPositions(accountId);
@@ -314,7 +329,7 @@ export async function getAccountMetrics(accountId: string) {
314329
: 0;
315330

316331
// Calculate Sharpe Ratio
317-
let sharpeRatio = 0;
332+
let sharpeRatio: number | null = null;
318333
if (closedOrders.length > 0 && initialBalance > 0) {
319334
try {
320335
const returns: number[] = [];
@@ -341,13 +356,52 @@ export async function getAccountMetrics(accountId: string) {
341356
}
342357
}
343358

359+
// Update account with calculated metrics
360+
await db
361+
.update(accounts)
362+
.set({
363+
account_value: accountValue.toString(),
364+
crypto_value: unrealizedPnL.toString(),
365+
total_return_percent: totalReturnPercent.toString(),
366+
sharpe_ratio: sharpeRatio !== null ? sharpeRatio.toString() : null,
367+
updated_at: new Date(),
368+
})
369+
.where(eq(accounts.id, accountId));
370+
344371
return {
345372
availableCash: currentBalance,
346373
cryptoValue: unrealizedPnL,
347374
accountValue,
348375
positions: openPositions,
349376
totalReturnPercent,
350-
sharpeRatio,
377+
sharpeRatio: sharpeRatio || 0,
351378
initialBalance,
352379
};
353380
}
381+
382+
// Create an account snapshot for historical tracking
383+
export async function createAccountSnapshot(accountId: string): Promise<void> {
384+
const account = await getAccountBalance(accountId);
385+
if (!account) {
386+
throw new Error('Account not found');
387+
}
388+
389+
// Get updated account metrics (this also updates the database)
390+
await getAccountMetrics(accountId);
391+
392+
// Get updated account with metrics
393+
const updatedAccount = await getAccountBalance(accountId);
394+
395+
await db
396+
.insert(accountSnapshots)
397+
.values({
398+
account_id: accountId,
399+
account_value: updatedAccount.account_value || updatedAccount.current_balance,
400+
current_balance: updatedAccount.current_balance,
401+
crypto_value: updatedAccount.crypto_value || '0',
402+
total_pnl: updatedAccount.total_pnl,
403+
total_return_percent: updatedAccount.total_return_percent,
404+
sharpe_ratio: updatedAccount.sharpe_ratio,
405+
snapshot_at: new Date(),
406+
});
407+
}

0 commit comments

Comments
 (0)