| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- const { executeQuery, executeNonQuery } = require('../config/database');
- const { logger } = require('./logger');
- const fs = require('fs');
- const path = require('path');
- class DatabaseInitializer {
- constructor() {
- this.initialized = false;
- }
- async initialize() {
- if (this.initialized) {
- logger.info('Database already initialized');
- return;
- }
- try {
- logger.info('Starting database initialization...');
- // Create tables based on database type
- const dbType = process.env.DB_TYPE || 'sqlite';
- switch (dbType) {
- case 'mysql':
- case 'postgres':
- await this.initializeRelationalDatabase();
- break;
- case 'sqlite':
- default:
- await this.initializeSQLite();
- break;
- }
- // Insert sample data
- await this.insertSampleData();
- this.initialized = true;
- logger.info('Database initialization completed successfully');
- } catch (error) {
- logger.error('Database initialization failed:', error);
- throw error;
- }
- }
- async initializeRelationalDatabase() {
- try {
- const dbType = process.env.DB_TYPE || 'mysql';
- if (dbType === 'mysql') {
- await this.initializeMySQL();
- } else if (dbType === 'postgres') {
- await this.initializePostgreSQL();
- }
- } catch (error) {
- logger.error('Error creating relational database tables:', error);
- throw error;
- }
- }
- async initializeMySQL() {
- try {
- // Create price_data table for MySQL
- const createPriceTableQuery = `
- CREATE TABLE IF NOT EXISTS price_data (
- id INT PRIMARY KEY AUTO_INCREMENT,
- symbol VARCHAR(10) NOT NULL,
- timeframe VARCHAR(5) NOT NULL,
- timestamp DATETIME NOT NULL,
- open DECIMAL(15,8) NOT NULL,
- high DECIMAL(15,8) NOT NULL,
- low DECIMAL(15,8) NOT NULL,
- close DECIMAL(15,8) NOT NULL,
- volume BIGINT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- INDEX idx_symbol_timeframe (symbol, timeframe),
- INDEX idx_timestamp (timestamp),
- INDEX idx_symbol_timestamp (symbol, timestamp)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
- `;
- await executeNonQuery(createPriceTableQuery);
- logger.info('MySQL price data table created successfully');
- // Create symbols table for MySQL
- const createSymbolsTableQuery = `
- CREATE TABLE IF NOT EXISTS symbols (
- id INT PRIMARY KEY AUTO_INCREMENT,
- symbol VARCHAR(10) UNIQUE NOT NULL,
- name VARCHAR(100) NOT NULL,
- category VARCHAR(20) NOT NULL,
- exchange VARCHAR(20) NOT NULL,
- is_active BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- INDEX idx_symbol (symbol),
- INDEX idx_category (category),
- INDEX idx_exchange (exchange)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
- `;
- await executeNonQuery(createSymbolsTableQuery);
- logger.info('MySQL symbols table created successfully');
- } catch (error) {
- logger.error('Error creating MySQL tables:', error);
- throw error;
- }
- }
- async initializePostgreSQL() {
- try {
- // Create price_data table for PostgreSQL
- const createPriceTableQuery = `
- CREATE TABLE IF NOT EXISTS price_data (
- id SERIAL PRIMARY KEY,
- symbol VARCHAR(10) NOT NULL,
- timeframe VARCHAR(5) NOT NULL,
- timestamp TIMESTAMP NOT NULL,
- open DECIMAL(15,8) NOT NULL,
- high DECIMAL(15,8) NOT NULL,
- low DECIMAL(15,8) NOT NULL,
- close DECIMAL(15,8) NOT NULL,
- volume BIGINT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- );
- `;
- await executeNonQuery(createPriceTableQuery);
- logger.info('PostgreSQL price data table created successfully');
- // Create indexes for PostgreSQL
- const createIndexes = [
- 'CREATE INDEX IF NOT EXISTS idx_symbol_timeframe ON price_data(symbol, timeframe);',
- 'CREATE INDEX IF NOT EXISTS idx_timestamp ON price_data(timestamp);',
- 'CREATE INDEX IF NOT EXISTS idx_symbol_timestamp ON price_data(symbol, timestamp);'
- ];
- for (const indexQuery of createIndexes) {
- await executeNonQuery(indexQuery);
- }
- logger.info('PostgreSQL indexes created successfully');
- // Create symbols table for PostgreSQL
- const createSymbolsTableQuery = `
- CREATE TABLE IF NOT EXISTS symbols (
- id SERIAL PRIMARY KEY,
- symbol VARCHAR(10) UNIQUE NOT NULL,
- name VARCHAR(100) NOT NULL,
- category VARCHAR(20) NOT NULL,
- exchange VARCHAR(20) NOT NULL,
- is_active BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- );
- `;
- await executeNonQuery(createSymbolsTableQuery);
- logger.info('PostgreSQL symbols table created successfully');
- } catch (error) {
- logger.error('Error creating PostgreSQL tables:', error);
- throw error;
- }
- }
- async initializeSQLite() {
- try {
- // Create price_data table
- const createPriceTableQuery = `
- CREATE TABLE IF NOT EXISTS price_data (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- symbol TEXT NOT NULL,
- timeframe TEXT NOT NULL,
- timestamp DATETIME NOT NULL,
- open REAL NOT NULL,
- high REAL NOT NULL,
- low REAL NOT NULL,
- close REAL NOT NULL,
- volume INTEGER NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- );
- `;
- await executeNonQuery(createPriceTableQuery);
- logger.info('SQLite price data table created successfully');
- // Create indexes for better performance
- const createIndexes = [
- 'CREATE INDEX IF NOT EXISTS idx_symbol_timeframe ON price_data(symbol, timeframe);',
- 'CREATE INDEX IF NOT EXISTS idx_timestamp ON price_data(timestamp);',
- 'CREATE INDEX IF NOT EXISTS idx_symbol_timestamp ON price_data(symbol, timestamp);'
- ];
- for (const indexQuery of createIndexes) {
- await executeNonQuery(indexQuery);
- }
- logger.info('SQLite indexes created successfully');
- // Create symbols table
- const createSymbolsTableQuery = `
- CREATE TABLE IF NOT EXISTS symbols (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- symbol TEXT UNIQUE NOT NULL,
- name TEXT NOT NULL,
- category TEXT NOT NULL,
- exchange TEXT NOT NULL,
- is_active INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
- );
- `;
- await executeNonQuery(createSymbolsTableQuery);
- logger.info('SQLite symbols table created successfully');
- } catch (error) {
- logger.error('Error creating SQLite tables:', error);
- throw error;
- }
- }
- async insertSampleData() {
- try {
- const dbType = process.env.DB_TYPE || 'sqlite';
- // Check if symbols already exist
- let existingSymbols;
- if (dbType === 'sqlite') {
- existingSymbols = await executeQuery('SELECT COUNT(*) as count FROM symbols');
- } else {
- existingSymbols = await executeQuery('SELECT COUNT(*) as count FROM symbols');
- }
- // Handle different database result formats
- let symbolCount = 0;
- if (existingSymbols && existingSymbols.length > 0) {
- symbolCount = existingSymbols[0].count || 0;
- }
- if (symbolCount > 0) {
- logger.info('Sample symbols already exist, skipping insertion');
- return;
- }
- // Insert sample symbols
- const sampleSymbols = [
- { symbol: 'EURUSD', name: 'Euro vs US Dollar', category: 'forex', exchange: 'FX' },
- { symbol: 'GBPUSD', name: 'British Pound vs US Dollar', category: 'forex', exchange: 'FX' },
- { symbol: 'USDJPY', name: 'US Dollar vs Japanese Yen', category: 'forex', exchange: 'FX' },
- { symbol: 'USDCHF', name: 'US Dollar vs Swiss Franc', category: 'forex', exchange: 'FX' },
- { symbol: 'USDCAD', name: 'US Dollar vs Canadian Dollar', category: 'forex', exchange: 'FX' },
- { symbol: 'AUDUSD', name: 'Australian Dollar vs US Dollar', category: 'forex', exchange: 'FX' },
- { symbol: 'NZDUSD', name: 'New Zealand Dollar vs US Dollar', category: 'forex', exchange: 'FX' },
- { symbol: 'BTCUSD', name: 'Bitcoin vs US Dollar', category: 'crypto', exchange: 'Crypto' },
- { symbol: 'ETHUSD', name: 'Ethereum vs US Dollar', category: 'crypto', exchange: 'Crypto' },
- { symbol: 'AAPL', name: 'Apple Inc.', category: 'stocks', exchange: 'NASDAQ' },
- { symbol: 'GOOGL', name: 'Alphabet Inc.', category: 'stocks', exchange: 'NASDAQ' },
- { symbol: 'MSFT', name: 'Microsoft Corporation', category: 'stocks', exchange: 'NASDAQ' },
- { symbol: 'TSLA', name: 'Tesla Inc.', category: 'stocks', exchange: 'NASDAQ' },
- { symbol: 'AMZN', name: 'Amazon.com Inc.', category: 'stocks', exchange: 'NASDAQ' },
- { symbol: 'META', name: 'Meta Platforms Inc.', category: 'stocks', exchange: 'NASDAQ' }
- ];
- for (const symbolData of sampleSymbols) {
- let insertQuery;
- if (dbType === 'sqlite') {
- insertQuery = `
- INSERT OR IGNORE INTO symbols (symbol, name, category, exchange, is_active)
- VALUES (?, ?, ?, ?, 1)
- `;
- } else if (dbType === 'postgres') {
- insertQuery = `
- INSERT INTO symbols (symbol, name, category, exchange, is_active)
- VALUES (?, ?, ?, ?, ?)
- ON CONFLICT (symbol) DO NOTHING
- `;
- } else {
- insertQuery = `
- INSERT IGNORE INTO symbols (symbol, name, category, exchange, is_active)
- VALUES (?, ?, ?, ?, 1)
- `;
- }
- await executeNonQuery(insertQuery, [
- symbolData.symbol,
- symbolData.name,
- symbolData.category,
- symbolData.exchange,
- true
- ]);
- }
- logger.info(`Inserted ${sampleSymbols.length} sample symbols`);
- // Generate sample price data for the last 1000 H1 candles
- await this.generateSamplePriceData(sampleSymbols.slice(0, 7)); // Generate for first 7 symbols
- } catch (error) {
- logger.error('Error inserting sample data:', error);
- throw error;
- }
- }
- async generateSamplePriceData(symbols) {
- try {
- logger.info('Generating sample price data...');
- const now = new Date();
- const dbType = process.env.DB_TYPE || 'sqlite';
- for (const symbolData of symbols) {
- logger.info(`Generating price data for ${symbolData.symbol}...`);
- // Generate 1000 H1 candles (about 41 days of data)
- const priceData = [];
- let currentPrice = this.getInitialPrice(symbolData.symbol);
- for (let i = 999; i >= 0; i--) {
- const timestamp = new Date(now.getTime() - (i * 60 * 60 * 1000)); // H1 intervals
- const open = currentPrice;
- // Generate realistic price movement (±1%)
- const change = (Math.random() - 0.5) * 0.02;
- const close = open * (1 + change);
- const high = Math.max(open, close) * (1 + Math.random() * 0.001);
- const low = Math.min(open, close) * (1 - Math.random() * 0.001);
- const volume = Math.floor(Math.random() * 1000000) + 100000;
- priceData.push({
- symbol: symbolData.symbol,
- timeframe: 'H1',
- timestamp,
- open: open.toFixed(8),
- high: high.toFixed(8),
- low: low.toFixed(8),
- close: close.toFixed(8),
- volume
- });
- currentPrice = close;
- }
- // Insert price data in batches
- const batchSize = 100;
- for (let i = 0; i < priceData.length; i += batchSize) {
- const batch = priceData.slice(i, i + batchSize);
- for (const price of batch) {
- let insertQuery;
- if (dbType === 'sqlite') {
- insertQuery = `
- INSERT OR IGNORE INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
- `;
- } else if (dbType === 'postgres') {
- insertQuery = `
- INSERT INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
- ON CONFLICT DO NOTHING
- `;
- } else {
- insertQuery = `
- INSERT IGNORE INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
- `;
- }
- await executeNonQuery(insertQuery, [
- price.symbol,
- price.timeframe,
- price.timestamp,
- price.open,
- price.high,
- price.low,
- price.close,
- price.volume
- ]);
- }
- }
- logger.info(`Generated ${priceData.length} price records for ${symbolData.symbol}`);
- }
- logger.info('Sample price data generation completed');
- } catch (error) {
- logger.error('Error generating sample price data:', error);
- throw error;
- }
- }
- getInitialPrice(symbol) {
- const initialPrices = {
- 'EURUSD': 1.0850,
- 'GBPUSD': 1.2650,
- 'USDJPY': 150.50,
- 'USDCHF': 0.9150,
- 'USDCAD': 1.3450,
- 'AUDUSD': 0.6550,
- 'NZDUSD': 0.6050
- };
- return initialPrices[symbol] || 1.0000;
- }
- async reset() {
- try {
- logger.info('Resetting database...');
- const dbType = process.env.DB_TYPE || 'sqlite';
- // Drop tables
- if (dbType === 'sqlite') {
- await executeNonQuery('DROP TABLE IF EXISTS price_data');
- await executeNonQuery('DROP TABLE IF EXISTS symbols');
- } else {
- await executeNonQuery('DROP TABLE IF EXISTS price_data');
- await executeNonQuery('DROP TABLE IF EXISTS symbols');
- }
- this.initialized = false;
- logger.info('Database reset completed');
- // Reinitialize
- await this.initialize();
- } catch (error) {
- logger.error('Error resetting database:', error);
- throw error;
- }
- }
- async getStats() {
- try {
- const dbType = process.env.DB_TYPE || 'sqlite';
- let symbolCount, priceCount;
- if (dbType === 'sqlite') {
- symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
- priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
- } else {
- symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
- priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
- }
- // Handle different database result formats
- let symbols = 0;
- let priceRecords = 0;
- if (symbolCount && symbolCount.length > 0) {
- symbols = symbolCount[0].count || 0;
- }
- if (priceCount && priceCount.length > 0) {
- priceRecords = priceCount[0].count || 0;
- }
- return {
- symbols,
- price_records: priceRecords,
- initialized: this.initialized
- };
- } catch (error) {
- logger.error('Error getting database stats:', error);
- return {
- symbols: 0,
- price_records: 0,
- initialized: false
- };
- }
- }
- }
- const databaseInitializer = new DatabaseInitializer();
- // Export functions for external use
- const initializeDatabase = () => databaseInitializer.initialize();
- const resetDatabase = () => databaseInitializer.reset();
- const getDatabaseStats = () => databaseInitializer.getStats();
- module.exports = {
- DatabaseInitializer,
- initializeDatabase,
- resetDatabase,
- getDatabaseStats,
- databaseInitializer
- };
|