Initial commit: Gold Trading Simulator with AI-powered analysis

This commit is contained in:
Krikorios
2025-11-16 00:50:04 +02:00
commit 72c1d3adb7
128 changed files with 16232 additions and 0 deletions
+348
View File
@@ -0,0 +1,348 @@
import type { DashboardConfig, TabConfig, LayoutPreset } from '@/types';
const STORAGE_KEY = 'gold-trading-dashboard-config';
export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
{
id: 'chart',
label: 'Price Chart',
visible: true,
order: 0,
size: 'large',
position: 'left',
pinned: true,
customization: {
autoRefresh: true,
refreshRate: 60,
},
},
{
id: 'trade-controls',
label: 'Trade Controls',
visible: true,
order: 1,
size: 'medium',
position: 'center',
pinned: true,
},
{
id: 'portfolio',
label: 'Portfolio',
visible: true,
order: 2,
size: 'medium',
position: 'center',
},
{
id: 'risk-management',
label: 'Risk Management',
visible: true,
order: 3,
size: 'medium',
position: 'center',
},
{
id: 'ai-analysis',
label: 'AI Analysis',
visible: true,
order: 4,
size: 'large',
position: 'left',
},
{
id: 'news',
label: 'News Feed',
visible: true,
order: 5,
size: 'medium',
position: 'right',
customization: {
autoRefresh: true,
refreshRate: 300,
filters: { sentiment: 'ALL' },
},
},
{
id: 'alerts',
label: 'Alerts',
visible: true,
order: 6,
size: 'medium',
position: 'right',
},
{
id: 'analytics',
label: 'Advanced Analytics',
visible: true,
order: 7,
size: 'full',
position: 'bottom',
},
// New Daily Trading Tools
{
id: 'daily-checklist',
label: 'Daily Checklist',
visible: false,
order: 8,
size: 'medium',
position: 'left',
},
{
id: 'daily-plan',
label: 'Trading Plan',
visible: false,
order: 9,
size: 'medium',
position: 'center',
},
{
id: 'trading-journal',
label: 'Trading Journal',
visible: false,
order: 10,
size: 'large',
position: 'right',
},
{
id: 'market-summary',
label: 'Market Brief',
visible: false,
order: 11,
size: 'medium',
position: 'left',
},
];
export const DEFAULT_PRESETS: LayoutPreset[] = [
{
id: 'trading-focus',
name: 'Trading Focus',
description: 'Optimized for active trading with chart and controls prominent',
mode: 'grid',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[0], size: 'full', order: 0 }, // chart
{ ...DEFAULT_TAB_CONFIGS[1], size: 'medium', order: 1, visible: true }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[2], size: 'medium', order: 2, visible: true }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[3], size: 'medium', order: 3, visible: true }, // risk
{ ...DEFAULT_TAB_CONFIGS[4], size: 'medium', order: 4, visible: false }, // ai
{ ...DEFAULT_TAB_CONFIGS[5], size: 'small', order: 5, visible: true }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'small', order: 6, visible: true }, // alerts
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 7, visible: false }, // analytics
],
},
{
id: 'analysis-focus',
name: 'Analysis Focus',
description: 'Full-screen analytics and AI insights',
mode: 'tabs',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[0], size: 'full', order: 0, visible: true }, // chart
{ ...DEFAULT_TAB_CONFIGS[4], size: 'full', order: 1, visible: true }, // ai
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 2, visible: true }, // analytics
{ ...DEFAULT_TAB_CONFIGS[3], size: 'full', order: 3, visible: true }, // risk
{ ...DEFAULT_TAB_CONFIGS[2], size: 'full', order: 4, visible: true }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[5], size: 'full', order: 5, visible: true }, // news
{ ...DEFAULT_TAB_CONFIGS[1], size: 'full', order: 6, visible: false }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[6], size: 'full', order: 7, visible: false }, // alerts
],
},
{
id: 'news-focus',
name: 'News Focus',
description: 'News and market updates at the forefront',
mode: 'split',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[0], size: 'large', order: 0, position: 'left' }, // chart
{ ...DEFAULT_TAB_CONFIGS[5], size: 'large', order: 1, position: 'right' }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'medium', order: 2, position: 'right' }, // alerts
{ ...DEFAULT_TAB_CONFIGS[4], size: 'medium', order: 3, position: 'left' }, // ai
{ ...DEFAULT_TAB_CONFIGS[1], size: 'small', order: 4, position: 'left', visible: false },
{ ...DEFAULT_TAB_CONFIGS[2], size: 'small', order: 5, position: 'left', visible: false },
{ ...DEFAULT_TAB_CONFIGS[3], size: 'small', order: 6, position: 'left', visible: false },
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 7, visible: false }, // analytics
],
},
{
id: 'balanced',
name: 'Balanced View',
description: 'Equal emphasis on all components',
mode: 'grid',
tabs: DEFAULT_TAB_CONFIGS.map((tab, index) => ({ ...tab, order: index, visible: true })),
},
// Daily Trading Workflow Presets
{
id: 'morning-setup',
name: '🌅 Morning Setup',
description: 'Pre-market routine: checklist, plan, and market brief',
mode: 'grid',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[11], size: 'large', order: 0, visible: true }, // market summary
{ ...DEFAULT_TAB_CONFIGS[8], size: 'medium', order: 1, visible: true }, // checklist
{ ...DEFAULT_TAB_CONFIGS[9], size: 'medium', order: 2, visible: true }, // trading plan
{ ...DEFAULT_TAB_CONFIGS[0], size: 'large', order: 3, visible: true }, // chart
{ ...DEFAULT_TAB_CONFIGS[5], size: 'medium', order: 4, visible: true }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'medium', order: 5, visible: true }, // alerts
{ ...DEFAULT_TAB_CONFIGS[4], size: 'medium', order: 6, visible: true }, // ai
{ ...DEFAULT_TAB_CONFIGS[1], size: 'small', order: 7, visible: false }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[2], size: 'small', order: 8, visible: false }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[3], size: 'small', order: 9, visible: false }, // risk
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 10, visible: false }, // analytics
{ ...DEFAULT_TAB_CONFIGS[10], size: 'large', order: 11, visible: false }, // journal
],
},
{
id: 'active-trading',
name: '📈 Active Trading',
description: 'During market hours: chart, controls, and execution',
mode: 'grid',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[0], size: 'full', order: 0, visible: true, pinned: true }, // chart
{ ...DEFAULT_TAB_CONFIGS[1], size: 'medium', order: 1, visible: true, pinned: true }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[2], size: 'medium', order: 2, visible: true }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[3], size: 'medium', order: 3, visible: true }, // risk
{ ...DEFAULT_TAB_CONFIGS[9], size: 'medium', order: 4, visible: true }, // trading plan
{ ...DEFAULT_TAB_CONFIGS[8], size: 'small', order: 5, visible: true }, // checklist
{ ...DEFAULT_TAB_CONFIGS[5], size: 'medium', order: 6, visible: true }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'medium', order: 7, visible: true }, // alerts
{ ...DEFAULT_TAB_CONFIGS[10], size: 'medium', order: 8, visible: false }, // journal (quick access)
{ ...DEFAULT_TAB_CONFIGS[4], size: 'medium', order: 9, visible: false }, // ai
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 10, visible: false }, // analytics
{ ...DEFAULT_TAB_CONFIGS[11], size: 'medium', order: 11, visible: false }, // market summary
],
},
{
id: 'end-of-day-review',
name: '🌙 End-of-Day Review',
description: 'Post-market analysis: journal, analytics, and planning for tomorrow',
mode: 'tabs',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[10], size: 'full', order: 0, visible: true }, // journal
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 1, visible: true }, // analytics
{ ...DEFAULT_TAB_CONFIGS[8], size: 'full', order: 2, visible: true }, // checklist
{ ...DEFAULT_TAB_CONFIGS[9], size: 'full', order: 3, visible: true }, // trading plan
{ ...DEFAULT_TAB_CONFIGS[2], size: 'full', order: 4, visible: true }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[0], size: 'full', order: 5, visible: true }, // chart
{ ...DEFAULT_TAB_CONFIGS[11], size: 'full', order: 6, visible: false }, // market summary
{ ...DEFAULT_TAB_CONFIGS[1], size: 'full', order: 7, visible: false }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[3], size: 'full', order: 8, visible: false }, // risk
{ ...DEFAULT_TAB_CONFIGS[4], size: 'full', order: 9, visible: false }, // ai
{ ...DEFAULT_TAB_CONFIGS[5], size: 'full', order: 10, visible: false }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'full', order: 11, visible: false }, // alerts
],
},
{
id: 'daily-trader',
name: '⚡ Complete Daily Trader',
description: 'All daily trading tools visible for comprehensive workflow',
mode: 'grid',
tabs: [
{ ...DEFAULT_TAB_CONFIGS[0], size: 'large', order: 0, visible: true, position: 'left' }, // chart
{ ...DEFAULT_TAB_CONFIGS[11], size: 'medium', order: 1, visible: true, position: 'left' }, // market summary
{ ...DEFAULT_TAB_CONFIGS[1], size: 'medium', order: 2, visible: true, position: 'center' }, // trade controls
{ ...DEFAULT_TAB_CONFIGS[9], size: 'medium', order: 3, visible: true, position: 'center' }, // trading plan
{ ...DEFAULT_TAB_CONFIGS[8], size: 'medium', order: 4, visible: true, position: 'center' }, // checklist
{ ...DEFAULT_TAB_CONFIGS[2], size: 'medium', order: 5, visible: true, position: 'center' }, // portfolio
{ ...DEFAULT_TAB_CONFIGS[5], size: 'medium', order: 6, visible: true, position: 'right' }, // news
{ ...DEFAULT_TAB_CONFIGS[6], size: 'medium', order: 7, visible: true, position: 'right' }, // alerts
{ ...DEFAULT_TAB_CONFIGS[10], size: 'large', order: 8, visible: true, position: 'right' }, // journal
{ ...DEFAULT_TAB_CONFIGS[3], size: 'medium', order: 9, visible: false }, // risk
{ ...DEFAULT_TAB_CONFIGS[4], size: 'medium', order: 10, visible: false }, // ai
{ ...DEFAULT_TAB_CONFIGS[7], size: 'full', order: 11, visible: false }, // analytics
],
},
];
export const DEFAULT_DASHBOARD_CONFIG: DashboardConfig = {
mode: 'grid',
tabs: DEFAULT_TAB_CONFIGS,
activePreset: 'balanced',
customPresets: [],
};
export function loadDashboardConfig(): DashboardConfig {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const config = JSON.parse(stored);
const storedTabs: TabConfig[] = config.tabs || [];
const defaultTabsById = new Map(DEFAULT_TAB_CONFIGS.map((tab) => [tab.id, tab]));
// Merge stored tab settings with defaults so required tabs always exist
const mergedTabs = DEFAULT_TAB_CONFIGS.map((defaultTab) => {
const storedTab = storedTabs.find((tab) => tab.id === defaultTab.id);
return storedTab ? { ...defaultTab, ...storedTab } : defaultTab;
});
// Preserve any custom tabs user may have added that aren't in defaults
const additionalTabs = storedTabs.filter((tab) => !defaultTabsById.has(tab.id));
return {
...DEFAULT_DASHBOARD_CONFIG,
...config,
tabs: [...mergedTabs, ...additionalTabs],
};
}
} catch (error) {
console.error('Error loading dashboard config:', error);
}
return DEFAULT_DASHBOARD_CONFIG;
}
export function saveDashboardConfig(config: DashboardConfig): void {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
} catch (error) {
console.error('Error saving dashboard config:', error);
}
}
export function getPresetById(presetId: string): LayoutPreset | undefined {
return DEFAULT_PRESETS.find((p) => p.id === presetId);
}
export function applyPreset(config: DashboardConfig, presetId: string): DashboardConfig {
const preset = getPresetById(presetId);
if (!preset) {
// Check custom presets
const customPreset = config.customPresets.find((p) => p.id === presetId);
if (customPreset) {
return {
...config,
mode: customPreset.mode,
tabs: customPreset.tabs,
activePreset: presetId,
};
}
return config;
}
return {
...config,
mode: preset.mode,
tabs: preset.tabs,
activePreset: presetId,
};
}
export function saveCustomPreset(
config: DashboardConfig,
name: string,
description: string
): DashboardConfig {
const newPreset: LayoutPreset = {
id: `custom-${Date.now()}`,
name,
description,
mode: config.mode,
tabs: [...config.tabs],
};
return {
...config,
customPresets: [...config.customPresets, newPreset],
};
}
export function resetToDefault(): DashboardConfig {
return { ...DEFAULT_DASHBOARD_CONFIG };
}
+144
View File
@@ -0,0 +1,144 @@
import type { Trade, Portfolio } from '@/types';
// Export trades to CSV
export const exportTradesToCSV = (trades: Trade[], portfolio: Portfolio): void => {
const headers = [
'ID',
'Timestamp',
'Action',
'Quantity',
'Price',
'Total',
'P&L',
'Date/Time',
];
const rows = trades.map((trade) => [
trade.id,
trade.timestamp,
trade.action,
trade.quantity.toFixed(4),
trade.price.toFixed(2),
trade.total.toFixed(2),
trade.pnl?.toFixed(2) || 'N/A',
new Date(trade.timestamp).toLocaleString(),
]);
// Add summary row
rows.push([]);
rows.push(['Summary', '', '', '', '', '', '']);
rows.push(['Total Trades', trades.length.toString(), '', '', '', '', '']);
rows.push(['Initial Capital', portfolio.initialCapital.toFixed(2), '', '', '', '', '']);
rows.push(['Current Value', portfolio.totalValue.toFixed(2), '', '', '', '', '']);
rows.push(['Total P&L', portfolio.totalPnl.toFixed(2), '', '', '', '', '']);
rows.push([
'Total P&L %',
portfolio.totalPnlPercent.toFixed(2) + '%',
'',
'',
'',
'',
'',
]);
const csvContent = [headers, ...rows].map((row) => row.join(',')).join('\n');
downloadFile(csvContent, 'gold_trades_export.csv', 'text/csv');
};
// Export portfolio summary
export const exportPortfolioSummary = (portfolio: Portfolio): void => {
const summary = {
exportDate: new Date().toISOString(),
initialCapital: portfolio.initialCapital,
currentCash: portfolio.cash,
totalValue: portfolio.totalValue,
totalPnL: portfolio.totalPnl,
totalPnLPercent: portfolio.totalPnlPercent,
totalTrades: portfolio.trades.length,
position: portfolio.position
? {
symbol: portfolio.position.symbol,
quantity: portfolio.position.quantity,
avgPrice: portfolio.position.avgPrice,
currentPrice: portfolio.position.currentPrice,
unrealizedPnL: portfolio.position.unrealizedPnl,
unrealizedPnLPercent: portfolio.position.unrealizedPnlPercent,
}
: null,
};
const jsonContent = JSON.stringify(summary, null, 2);
downloadFile(jsonContent, 'portfolio_summary.json', 'application/json');
};
// Export analytics report
export const exportAnalyticsReport = (
portfolio: Portfolio,
analytics: any
): void => {
const report = `
=================================================
GOLD TRADING SIMULATOR - ANALYTICS REPORT
=================================================
Generated: ${new Date().toLocaleString()}
PORTFOLIO SUMMARY
-------------------------------------------------
Initial Capital: $${portfolio.initialCapital.toFixed(2)}
Current Value: $${portfolio.totalValue.toFixed(2)}
Total P&L: $${portfolio.totalPnl.toFixed(2)} (${portfolio.totalPnlPercent.toFixed(2)}%)
Cash Available: $${portfolio.cash.toFixed(2)}
PERFORMANCE METRICS
-------------------------------------------------
Total Trades: ${analytics.totalTrades}
Win Rate: ${analytics.winRate}%
Winning Trades: ${analytics.winningTrades}
Losing Trades: ${analytics.losingTrades}
Average Win: $${analytics.avgWin.toFixed(2)}
Average Loss: $${analytics.avgLoss.toFixed(2)}
Largest Win: $${analytics.largestWin.toFixed(2)}
Largest Loss: $${Math.abs(analytics.largestLoss).toFixed(2)}
Profit Factor: ${analytics.profitFactor === Infinity ? '∞' : analytics.profitFactor.toFixed(2)}
Risk/Reward Ratio: 1:${analytics.riskRewardRatio.toFixed(2)}
Sharpe Ratio: ${analytics.sharpeRatio.toFixed(2)}
Max Drawdown: ${analytics.maxDrawdown.toFixed(2)}%
${portfolio.position ? `
CURRENT POSITION
-------------------------------------------------
Symbol: ${portfolio.position.symbol}
Quantity: ${portfolio.position.quantity.toFixed(4)} oz
Avg Entry Price: $${portfolio.position.avgPrice.toFixed(2)}
Current Price: $${portfolio.position.currentPrice.toFixed(2)}
Unrealized P&L: $${portfolio.position.unrealizedPnl.toFixed(2)} (${portfolio.position.unrealizedPnlPercent.toFixed(2)}%)
` : ''}
=================================================
END OF REPORT
=================================================
`.trim();
downloadFile(report, 'trading_analytics_report.txt', 'text/plain');
};
// Helper function to download file
const downloadFile = (content: string, filename: string, mimeType: string): void => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
// Copy to clipboard
export const copyToClipboard = (text: string): Promise<void> => {
return navigator.clipboard.writeText(text);
};
+431
View File
@@ -0,0 +1,431 @@
import type { PriceData } from '@/types';
export const calculateSMA = (
data: PriceData[],
period: number
): { time: number; value: number }[] => {
const result: { time: number; value: number }[] = [];
for (let i = period - 1; i < data.length; i++) {
let sum = 0;
for (let j = 0; j < period; j++) {
sum += data[i - j].close;
}
const sma = sum / period;
result.push({
time: data[i].time,
value: Number(sma.toFixed(2)),
});
}
return result;
};
export const calculateEMA = (
data: PriceData[],
period: number
): { time: number; value: number }[] => {
const result: { time: number; value: number }[] = [];
const multiplier = 2 / (period + 1);
if (data.length < period) return result;
// Calculate initial SMA for first EMA value
let sum = 0;
for (let i = 0; i < period; i++) {
sum += data[i].close;
}
let ema = sum / period;
result.push({ time: data[period - 1].time, value: Number(ema.toFixed(2)) });
// Calculate EMA for remaining data
for (let i = period; i < data.length; i++) {
ema = (data[i].close - ema) * multiplier + ema;
result.push({ time: data[i].time, value: Number(ema.toFixed(2)) });
}
return result;
};
export const calculateRSI = (
data: PriceData[],
period: number = 14
): { time: number; value: number }[] => {
const result: { time: number; value: number }[] = [];
if (data.length < period + 1) return result;
let gains = 0;
let losses = 0;
// Calculate initial average gain and loss
for (let i = 1; i <= period; i++) {
const change = data[i].close - data[i - 1].close;
if (change > 0) {
gains += change;
} else {
losses += Math.abs(change);
}
}
let avgGain = gains / period;
let avgLoss = losses / period;
for (let i = period; i < data.length; i++) {
const change = data[i].close - data[i - 1].close;
const gain = change > 0 ? change : 0;
const loss = change < 0 ? Math.abs(change) : 0;
avgGain = (avgGain * (period - 1) + gain) / period;
avgLoss = (avgLoss * (period - 1) + loss) / period;
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
const rsi = 100 - 100 / (1 + rs);
result.push({ time: data[i].time, value: Number(rsi.toFixed(2)) });
}
return result;
};
export const formatPrice = (price: number): string => {
return `$${price.toFixed(2)}`;
};
export const formatPercent = (value: number): string => {
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}%`;
};
export const formatNumber = (value: number): string => {
return new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value);
};
// MACD (Moving Average Convergence Divergence)
export const calculateMACD = (
data: PriceData[],
fastPeriod: number = 12,
slowPeriod: number = 26,
signalPeriod: number = 9
): {
macd: { time: number; value: number }[];
signal: { time: number; value: number }[];
histogram: { time: number; value: number }[];
} => {
const fastEMA = calculateEMA(data, fastPeriod);
const slowEMA = calculateEMA(data, slowPeriod);
const macdLine: { time: number; value: number }[] = [];
// Calculate MACD line (fast EMA - slow EMA)
const startIndex = slowPeriod - 1;
for (let i = 0; i < fastEMA.length; i++) {
if (i + startIndex < slowEMA.length) {
macdLine.push({
time: fastEMA[i + fastPeriod - slowPeriod].time,
value: Number((fastEMA[i + fastPeriod - slowPeriod].value - slowEMA[i].value).toFixed(2)),
});
}
}
// Calculate signal line (EMA of MACD)
const signalLine: { time: number; value: number }[] = [];
if (macdLine.length >= signalPeriod) {
const multiplier = 2 / (signalPeriod + 1);
let ema = macdLine.slice(0, signalPeriod).reduce((sum, d) => sum + d.value, 0) / signalPeriod;
signalLine.push({ time: macdLine[signalPeriod - 1].time, value: Number(ema.toFixed(2)) });
for (let i = signalPeriod; i < macdLine.length; i++) {
ema = (macdLine[i].value - ema) * multiplier + ema;
signalLine.push({ time: macdLine[i].time, value: Number(ema.toFixed(2)) });
}
}
// Calculate histogram (MACD - Signal)
const histogram: { time: number; value: number }[] = [];
for (let i = 0; i < signalLine.length; i++) {
const macdValue = macdLine[i + macdLine.length - signalLine.length].value;
histogram.push({
time: signalLine[i].time,
value: Number((macdValue - signalLine[i].value).toFixed(2)),
});
}
return { macd: macdLine, signal: signalLine, histogram };
};
// Bollinger Bands
export const calculateBollingerBands = (
data: PriceData[],
period: number = 20,
stdDev: number = 2
): {
upper: { time: number; value: number }[];
middle: { time: number; value: number }[];
lower: { time: number; value: number }[];
} => {
const middle = calculateSMA(data, period);
const upper: { time: number; value: number }[] = [];
const lower: { time: number; value: number }[] = [];
for (let i = period - 1; i < data.length; i++) {
const slice = data.slice(i - period + 1, i + 1);
const sma = slice.reduce((sum, d) => sum + d.close, 0) / period;
const variance = slice.reduce((sum, d) => sum + Math.pow(d.close - sma, 2), 0) / period;
const sd = Math.sqrt(variance);
const time = data[i].time;
upper.push({ time, value: Number((sma + stdDev * sd).toFixed(2)) });
lower.push({ time, value: Number((sma - stdDev * sd).toFixed(2)) });
}
return { upper, middle, lower };
};
// ATR (Average True Range)
export const calculateATR = (
data: PriceData[],
period: number = 14
): { time: number; value: number }[] => {
const result: { time: number; value: number }[] = [];
if (data.length < period + 1) return result;
const trueRanges: number[] = [];
// Calculate True Range for each period
for (let i = 1; i < data.length; i++) {
const high = data[i].high;
const low = data[i].low;
const prevClose = data[i - 1].close;
const tr = Math.max(
high - low,
Math.abs(high - prevClose),
Math.abs(low - prevClose)
);
trueRanges.push(tr);
}
// Calculate initial ATR (SMA of TR)
let atr = trueRanges.slice(0, period).reduce((sum, tr) => sum + tr, 0) / period;
result.push({ time: data[period].time, value: Number(atr.toFixed(2)) });
// Calculate remaining ATR using smoothing
for (let i = period; i < trueRanges.length; i++) {
atr = (atr * (period - 1) + trueRanges[i]) / period;
result.push({ time: data[i + 1].time, value: Number(atr.toFixed(2)) });
}
return result;
};
// Fibonacci Retracement Levels
export const calculateFibonacci = (
high: number,
low: number
): { level: string; price: number }[] => {
const diff = high - low;
const levels = [
{ level: '0%', ratio: 0 },
{ level: '23.6%', ratio: 0.236 },
{ level: '38.2%', ratio: 0.382 },
{ level: '50%', ratio: 0.5 },
{ level: '61.8%', ratio: 0.618 },
{ level: '78.6%', ratio: 0.786 },
{ level: '100%', ratio: 1 },
];
return levels.map((l) => ({
level: l.level,
price: Number((high - diff * l.ratio).toFixed(2)),
}));
};
// Stochastic Oscillator
export const calculateStochastic = (
data: PriceData[],
kPeriod: number = 14,
dPeriod: number = 3
): {
k: { time: number; value: number }[];
d: { time: number; value: number }[];
} => {
const kLine: { time: number; value: number }[] = [];
for (let i = kPeriod - 1; i < data.length; i++) {
const slice = data.slice(i - kPeriod + 1, i + 1);
const high = Math.max(...slice.map((d) => d.high));
const low = Math.min(...slice.map((d) => d.low));
const close = data[i].close;
const k = low === high ? 50 : ((close - low) / (high - low)) * 100;
kLine.push({ time: data[i].time, value: Number(k.toFixed(2)) });
}
// Calculate %D (SMA of %K)
const dLine: { time: number; value: number }[] = [];
for (let i = dPeriod - 1; i < kLine.length; i++) {
const sum = kLine.slice(i - dPeriod + 1, i + 1).reduce((s, d) => s + d.value, 0);
dLine.push({ time: kLine[i].time, value: Number((sum / dPeriod).toFixed(2)) });
}
return { k: kLine, d: dLine };
};
// Pivot Points (Standard)
export const calculatePivotPoints = (
high: number,
low: number,
close: number
): {
pivot: number;
r1: number;
r2: number;
r3: number;
s1: number;
s2: number;
s3: number;
} => {
const pivot = (high + low + close) / 3;
const r1 = 2 * pivot - low;
const s1 = 2 * pivot - high;
const r2 = pivot + (high - low);
const s2 = pivot - (high - low);
const r3 = high + 2 * (pivot - low);
const s3 = low - 2 * (high - pivot);
return {
pivot: Number(pivot.toFixed(2)),
r1: Number(r1.toFixed(2)),
r2: Number(r2.toFixed(2)),
r3: Number(r3.toFixed(2)),
s1: Number(s1.toFixed(2)),
s2: Number(s2.toFixed(2)),
s3: Number(s3.toFixed(2)),
};
};
// Volume Weighted Average Price (VWAP)
export const calculateVWAP = (
data: PriceData[]
): { time: number; value: number }[] => {
const result: { time: number; value: number }[] = [];
let cumulativeTPV = 0; // Typical Price * Volume
let cumulativeVolume = 0;
for (let i = 0; i < data.length; i++) {
const typicalPrice = (data[i].high + data[i].low + data[i].close) / 3;
const volume = data[i].volume || 1; // Default to 1 if volume not available
cumulativeTPV += typicalPrice * volume;
cumulativeVolume += volume;
const vwap = cumulativeTPV / cumulativeVolume;
result.push({ time: data[i].time, value: Number(vwap.toFixed(2)) });
}
return result;
};
// Support and Resistance Detection
export const findSupportResistance = (
data: PriceData[],
lookback: number = 20,
threshold: number = 0.02 // 2% threshold
): { support: number[]; resistance: number[] } => {
const support: number[] = [];
const resistance: number[] = [];
for (let i = lookback; i < data.length - lookback; i++) {
const slice = data.slice(i - lookback, i + lookback + 1);
const current = data[i];
// Check if current low is a support (lowest in range)
const isSupport = slice.every((d) => current.low <= d.low * (1 + threshold));
if (isSupport && !support.includes(current.low)) {
support.push(Number(current.low.toFixed(2)));
}
// Check if current high is a resistance (highest in range)
const isResistance = slice.every((d) => current.high >= d.high * (1 - threshold));
if (isResistance && !resistance.includes(current.high)) {
resistance.push(Number(current.high.toFixed(2)));
}
}
// Remove duplicates and sort
return {
support: [...new Set(support)].sort((a, b) => b - a).slice(0, 5),
resistance: [...new Set(resistance)].sort((a, b) => a - b).slice(0, 5),
};
};
// Calculate Win Rate from trades
export const calculateWinRate = (trades: any[]): number => {
if (trades.length === 0) return 0;
const winningTrades = trades.filter((t) => t.pnl && t.pnl > 0).length;
return Number(((winningTrades / trades.length) * 100).toFixed(2));
};
// Calculate Sharpe Ratio (simplified)
export const calculateSharpeRatio = (
returns: number[],
riskFreeRate: number = 0.02
): number => {
if (returns.length === 0) return 0;
const avgReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length;
const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length;
const stdDev = Math.sqrt(variance);
if (stdDev === 0) return 0;
const excessReturn = avgReturn - riskFreeRate / 252; // Daily risk-free rate
return Number((excessReturn / stdDev).toFixed(2));
};
// Calculate Maximum Drawdown
export const calculateMaxDrawdown = (equity: number[]): number => {
if (equity.length === 0) return 0;
let maxDrawdown = 0;
let peak = equity[0];
for (const value of equity) {
if (value > peak) {
peak = value;
}
const drawdown = ((peak - value) / peak) * 100;
if (drawdown > maxDrawdown) {
maxDrawdown = drawdown;
}
}
return Number(maxDrawdown.toFixed(2));
};
// Position Sizing (Kelly Criterion)
export const calculatePositionSize = (
capital: number,
winRate: number,
avgWin: number,
avgLoss: number
): number => {
if (avgLoss === 0) return 0;
const winLossRatio = Math.abs(avgWin / avgLoss);
const kellyPercent = (winRate - (1 - winRate) / winLossRatio) * 100;
// Use half-Kelly for safety
const safeKelly = kellyPercent / 2;
// Cap at 10% of capital
const maxPercent = Math.min(Math.max(safeKelly, 0), 10);
return Number((capital * (maxPercent / 100)).toFixed(2));
};