Files
AI-Trader/docs/assets/js/data-loader.js
2025-10-24 00:35:21 +08:00

391 lines
14 KiB
JavaScript

// Data Loader Utility
// Handles loading and processing all trading data
class DataLoader {
constructor() {
this.agentData = {};
this.priceCache = {};
// Use 'data' for GitHub Pages deployment, '../data' for local development
this.baseDataPath = './data';
}
// Load all agent names from directory structure
async loadAgentList() {
try {
// Since we can't directly list directories in the browser,
// we'll try to load known agents based on common patterns
const potentialAgents = [
'gemini-2.5-flash',
'qwen3-max',
'deepseek-chat-v3.1',
'gpt-5',
'claude-3.7-sonnet',
];
const agents = [];
for (const agent of potentialAgents) {
try {
console.log(`Checking agent: ${agent}`);
const response = await fetch(`${this.baseDataPath}/agent_data/${agent}/position/position.jsonl`);
if (response.ok) {
agents.push(agent);
console.log(`Added agent: ${agent}`);
} else {
console.log(`Agent ${agent} not found (status: ${response.status})`);
}
} catch (e) {
console.log(`Agent ${agent} error:`, e.message);
}
}
return agents;
} catch (error) {
console.error('Error loading agent list:', error);
return [];
}
}
// Load position data for a specific agent
async loadAgentPositions(agentName) {
try {
const response = await fetch(`${this.baseDataPath}/agent_data/${agentName}/position/position.jsonl`);
if (!response.ok) throw new Error(`Failed to load positions for ${agentName}`);
const text = await response.text();
const lines = text.trim().split('\n').filter(line => line.trim() !== '');
const positions = lines.map(line => {
try {
return JSON.parse(line);
} catch (parseError) {
console.error(`Error parsing line for ${agentName}:`, line, parseError);
return null;
}
}).filter(pos => pos !== null);
console.log(`Loaded ${positions.length} positions for ${agentName}`);
return positions;
} catch (error) {
console.error(`Error loading positions for ${agentName}:`, error);
return [];
}
}
// Load price data for a specific stock symbol
async loadStockPrice(symbol) {
if (this.priceCache[symbol]) {
return this.priceCache[symbol];
}
try {
const response = await fetch(`${this.baseDataPath}/daily_prices_${symbol}.json`);
if (!response.ok) throw new Error(`Failed to load price for ${symbol}`);
const data = await response.json();
this.priceCache[symbol] = data['Time Series (Daily)'];
return this.priceCache[symbol];
} catch (error) {
console.error(`Error loading price for ${symbol}:`, error);
return null;
}
}
// Get closing price for a symbol on a specific date
async getClosingPrice(symbol, date) {
const prices = await this.loadStockPrice(symbol);
if (!prices || !prices[date]) {
return null;
}
return parseFloat(prices[date]['4. close']);
}
// Calculate total asset value for a position on a given date
async calculateAssetValue(position, date) {
let totalValue = position.positions.CASH || 0;
// Get all stock symbols (exclude CASH)
const symbols = Object.keys(position.positions).filter(s => s !== 'CASH');
for (const symbol of symbols) {
const shares = position.positions[symbol];
if (shares > 0) {
const price = await this.getClosingPrice(symbol, date);
if (price) {
totalValue += shares * price;
}
}
}
return totalValue;
}
// Load complete data for an agent including asset values over time
async loadAgentData(agentName) {
console.log(`Starting to load data for ${agentName}...`);
const positions = await this.loadAgentPositions(agentName);
if (positions.length === 0) {
console.log(`No positions found for ${agentName}`);
return null;
}
console.log(`Processing ${positions.length} positions for ${agentName}...`);
// Group positions by date and take only the last position for each date
const positionsByDate = {};
positions.forEach(position => {
const date = position.date;
if (!positionsByDate[date] || position.id > positionsByDate[date].id) {
positionsByDate[date] = position;
}
});
// Convert to array and sort by date
const uniquePositions = Object.values(positionsByDate).sort((a, b) => {
if (a.date !== b.date) {
return a.date.localeCompare(b.date);
}
return a.id - b.id;
});
console.log(`Reduced from ${positions.length} to ${uniquePositions.length} unique daily positions for ${agentName}`);
const assetHistory = [];
for (const position of uniquePositions) {
const date = position.date;
const assetValue = await this.calculateAssetValue(position, date);
assetHistory.push({
date: date,
value: assetValue,
id: position.id,
action: position.this_action || null
});
}
const result = {
name: agentName,
positions: positions,
assetHistory: assetHistory,
initialValue: assetHistory[0]?.value || 10000,
currentValue: assetHistory[assetHistory.length - 1]?.value || 0,
return: assetHistory.length > 0 ?
((assetHistory[assetHistory.length - 1].value - assetHistory[0].value) / assetHistory[0].value * 100) : 0
};
console.log(`Successfully loaded data for ${agentName}:`, {
positions: positions.length,
assetHistory: assetHistory.length,
initialValue: result.initialValue,
currentValue: result.currentValue,
return: result.return
});
return result;
}
// Load QQQ invesco data
async loadQQQData() {
try {
console.log('Loading QQQ invesco data...');
const response = await fetch(`${this.baseDataPath}/Adaily_prices_QQQ.json`);
if (!response.ok) throw new Error('Failed to load QQQ data');
const data = await response.json();
const timeSeries = data['Time Series (Daily)'];
// Convert to asset history format
const assetHistory = [];
const dates = Object.keys(timeSeries).sort();
// Calculate QQQ performance starting from first agent's initial value
const agentNames = Object.keys(this.agentData);
let initialValue = 10000; // Default initial value
if (agentNames.length > 0) {
const firstAgent = this.agentData[agentNames[0]];
if (firstAgent && firstAgent.assetHistory.length > 0) {
initialValue = firstAgent.assetHistory[0].value;
}
}
// Find the earliest start date and latest end date across all agents
let startDate = null;
let endDate = null;
if (agentNames.length > 0) {
agentNames.forEach(agentName => {
const agent = this.agentData[agentName];
if (agent && agent.assetHistory.length > 0) {
const agentStartDate = agent.assetHistory[0].date;
const agentEndDate = agent.assetHistory[agent.assetHistory.length - 1].date;
if (!startDate || agentStartDate < startDate) {
startDate = agentStartDate;
}
if (!endDate || agentEndDate > endDate) {
endDate = agentEndDate;
}
}
});
}
let qqqStartPrice = null;
let currentValue = initialValue;
for (const date of dates) {
if (startDate && date < startDate) continue;
if (endDate && date > endDate) continue;
const price = parseFloat(timeSeries[date]['4. close']);
if (!qqqStartPrice) {
qqqStartPrice = price;
}
// Calculate QQQ performance relative to start
const qqqReturn = (price - qqqStartPrice) / qqqStartPrice;
currentValue = initialValue * (1 + qqqReturn);
assetHistory.push({
date: date,
value: currentValue,
id: `qqq-${date}`,
action: null
});
}
const result = {
name: 'QQQ',
positions: [],
assetHistory: assetHistory,
initialValue: initialValue,
currentValue: assetHistory.length > 0 ? assetHistory[assetHistory.length - 1].value : initialValue,
return: assetHistory.length > 0 ?
((assetHistory[assetHistory.length - 1].value - assetHistory[0].value) / assetHistory[0].value * 100) : 0
};
console.log('Successfully loaded QQQ data:', {
assetHistory: assetHistory.length,
initialValue: result.initialValue,
currentValue: result.currentValue,
return: result.return
});
return result;
} catch (error) {
console.error('Error loading QQQ data:', error);
return null;
}
}
// Load all agents data
async loadAllAgentsData() {
console.log('Starting to load all agents data...');
const agents = await this.loadAgentList();
console.log('Found agents:', agents);
const allData = {};
for (const agent of agents) {
console.log(`Loading data for ${agent}...`);
const data = await this.loadAgentData(agent);
if (data) {
allData[agent] = data;
console.log(`Successfully added ${agent} to allData`);
} else {
console.log(`Failed to load data for ${agent}`);
}
}
console.log('Final allData:', Object.keys(allData));
this.agentData = allData;
// Load QQQ invesco data
const qqqData = await this.loadQQQData();
if (qqqData) {
allData['QQQ'] = qqqData;
console.log('Successfully added QQQ invesco to allData');
}
return allData;
}
// Get current holdings for an agent (latest position)
getCurrentHoldings(agentName) {
const data = this.agentData[agentName];
if (!data || !data.positions || data.positions.length === 0) return null;
const latestPosition = data.positions[data.positions.length - 1];
return latestPosition && latestPosition.positions ? latestPosition.positions : null;
}
// Get trade history for an agent
getTradeHistory(agentName) {
const data = this.agentData[agentName];
if (!data) return [];
return data.positions
.filter(p => p.this_action)
.map(p => ({
date: p.date,
action: p.this_action.action,
symbol: p.this_action.symbol,
amount: p.this_action.amount
}))
.reverse(); // Most recent first
}
// Format number as currency
formatCurrency(value) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2
}).format(value);
}
// Format percentage
formatPercent(value) {
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}%`;
}
// Get nice display name for agent
getAgentDisplayName(agentName) {
const names = {
'gemini-2.5-flash': 'Gemini-2.5-flash',
'qwen3-max': 'Qwen3-max',
'gpt-5': 'GPT-5',
'deepseek-chat-v3.1': 'DeepSeek-v3.1',
'claude-3.7-sonnet': 'Claude 3.7 Sonnet',
'QQQ': 'QQQ invesco'
};
return names[agentName] || agentName;
}
// Get icon for agent (emoji/symbol representation)
getAgentIcon(agentName) {
const icons = {
'gemini-2.5-flash': '🤖',
'qwen3-max': '🧠',
'gpt-5': '🔮',
'claude-3.7-sonnet': '🎭',
'deepseek-chat-v3.1': '🔬',
'QQQ': '📈'
};
return icons[agentName] || '🤖';
}
// Get brand color for agent
getAgentBrandColor(agentName) {
const colors = {
'gemini-2.5-flash': '#8A2BE2', // Google purple
'qwen3-max': '#0066ff', // Qwen Blue
'gpt-5': '#10a37f', // OpenAI Green
'deepseek-chat-v3.1': '#4a90e2', // DeepSeek Blue
'claude-3.7-sonnet': '#cc785c', // Anthropic Orange
'QQQ': '#ff6b00' // QQQ Orange
};
return colors[agentName] || null;
}
}
// Export for use in other modules
window.DataLoader = DataLoader;