254 lines
7.5 KiB
TypeScript
254 lines
7.5 KiB
TypeScript
/**
|
|
* Paper Trading Controller
|
|
* Handles paper trading account and position endpoints
|
|
*/
|
|
|
|
import type { Request, Response, NextFunction } from 'express';
|
|
import { paperTradingService, TradeDirection } from '../services/paper-trading.service';
|
|
import type { AuthenticatedRequest } from '../../../core/guards/auth.guard';
|
|
|
|
// ============================================================================
|
|
// Account Endpoints
|
|
// ============================================================================
|
|
|
|
export async function getAccount(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const account = await paperTradingService.getOrCreateAccount(authReq.user.id);
|
|
res.json({ success: true, data: account });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function getAccounts(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const accounts = await paperTradingService.getUserAccounts(authReq.user.id);
|
|
res.json({ success: true, data: accounts });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function createAccount(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { name, initialBalance, currency } = req.body;
|
|
|
|
const account = await paperTradingService.createAccount(authReq.user.id, {
|
|
name,
|
|
initialBalance,
|
|
currency,
|
|
});
|
|
|
|
res.status(201).json({ success: true, data: account });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function resetAccount(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { accountId } = req.params;
|
|
|
|
const account = await paperTradingService.resetAccount(accountId, authReq.user.id);
|
|
|
|
if (!account) {
|
|
res.status(404).json({ success: false, error: 'Account not found or unauthorized' });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: account, message: 'Account reset successfully' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function getAccountSummary(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { accountId } = req.params;
|
|
|
|
const summary = await paperTradingService.getAccountSummary(authReq.user.id, accountId);
|
|
|
|
if (!summary) {
|
|
res.status(404).json({ success: false, error: 'Account not found' });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: summary });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Position Endpoints
|
|
// ============================================================================
|
|
|
|
export async function openPosition(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { symbol, direction, lotSize, entryPrice, stopLoss, takeProfit } = req.body;
|
|
|
|
if (!symbol || !direction || !lotSize) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Symbol, direction, and lotSize are required',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const validDirections: TradeDirection[] = ['long', 'short'];
|
|
if (!validDirections.includes(direction)) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: `Invalid direction. Must be one of: ${validDirections.join(', ')}`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (lotSize <= 0) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Lot size must be greater than 0',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const position = await paperTradingService.openPosition(authReq.user.id, {
|
|
symbol,
|
|
direction,
|
|
lotSize,
|
|
entryPrice,
|
|
stopLoss,
|
|
takeProfit,
|
|
});
|
|
|
|
res.status(201).json({ success: true, data: position });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function closePosition(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { positionId } = req.params;
|
|
const { exitPrice, closeReason } = req.body;
|
|
|
|
const position = await paperTradingService.closePosition(positionId, authReq.user.id, {
|
|
exitPrice,
|
|
closeReason,
|
|
});
|
|
|
|
if (!position) {
|
|
res.status(404).json({ success: false, error: 'Position not found or already closed' });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: position });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function getPosition(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { positionId } = req.params;
|
|
|
|
const position = await paperTradingService.getPosition(positionId, authReq.user.id);
|
|
|
|
if (!position) {
|
|
res.status(404).json({ success: false, error: 'Position not found' });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: position });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function getPositions(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { accountId, status, symbol, limit } = req.query;
|
|
|
|
const positions = await paperTradingService.getPositions(authReq.user.id, {
|
|
accountId: accountId as string,
|
|
status: status as 'open' | 'closed' | 'pending',
|
|
symbol: symbol as string,
|
|
limit: limit ? parseInt(limit as string, 10) : undefined,
|
|
});
|
|
|
|
res.json({ success: true, data: positions });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function updatePosition(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { positionId } = req.params;
|
|
const { stopLoss, takeProfit } = req.body;
|
|
|
|
const position = await paperTradingService.updatePosition(positionId, authReq.user.id, {
|
|
stopLoss,
|
|
takeProfit,
|
|
});
|
|
|
|
if (!position) {
|
|
res.status(404).json({ success: false, error: 'Position not found or not open' });
|
|
return;
|
|
}
|
|
|
|
res.json({ success: true, data: position });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Trade History & Analytics
|
|
// ============================================================================
|
|
|
|
export async function getTradeHistory(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { accountId, symbol, startDate, endDate, limit } = req.query;
|
|
|
|
const trades = await paperTradingService.getTradeHistory(authReq.user.id, {
|
|
accountId: accountId as string,
|
|
symbol: symbol as string,
|
|
startDate: startDate ? new Date(startDate as string) : undefined,
|
|
endDate: endDate ? new Date(endDate as string) : undefined,
|
|
limit: limit ? parseInt(limit as string, 10) : undefined,
|
|
});
|
|
|
|
res.json({ success: true, data: trades });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
export async function getPerformanceStats(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const { accountId } = req.query;
|
|
|
|
const stats = await paperTradingService.getPerformanceStats(
|
|
authReq.user.id,
|
|
accountId as string | undefined
|
|
);
|
|
|
|
res.json({ success: true, data: stats });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|