|
@@ -1,7 +1,5 @@
|
|
|
const { executeQuery, executeNonQuery } = require('../config/database');
|
|
const { executeQuery, executeNonQuery } = require('../config/database');
|
|
|
const { logger } = require('./logger');
|
|
const { logger } = require('./logger');
|
|
|
-const fs = require('fs');
|
|
|
|
|
-const path = require('path');
|
|
|
|
|
|
|
|
|
|
class DatabaseInitializer {
|
|
class DatabaseInitializer {
|
|
|
constructor() {
|
|
constructor() {
|
|
@@ -18,7 +16,7 @@ class DatabaseInitializer {
|
|
|
logger.info('Starting database initialization...');
|
|
logger.info('Starting database initialization...');
|
|
|
|
|
|
|
|
// Create tables based on database type
|
|
// Create tables based on database type
|
|
|
- const dbType = process.env.DB_TYPE || 'sqlite';
|
|
|
|
|
|
|
+ const dbType = process.env.DB_TYPE || 'postgres';
|
|
|
|
|
|
|
|
switch (dbType) {
|
|
switch (dbType) {
|
|
|
case 'mysql':
|
|
case 'mysql':
|
|
@@ -31,9 +29,6 @@ class DatabaseInitializer {
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Insert sample data
|
|
|
|
|
- await this.insertSampleData();
|
|
|
|
|
-
|
|
|
|
|
this.initialized = true;
|
|
this.initialized = true;
|
|
|
logger.info('Database initialization completed successfully');
|
|
logger.info('Database initialization completed successfully');
|
|
|
|
|
|
|
@@ -45,7 +40,7 @@ class DatabaseInitializer {
|
|
|
|
|
|
|
|
async initializeRelationalDatabase() {
|
|
async initializeRelationalDatabase() {
|
|
|
try {
|
|
try {
|
|
|
- const dbType = process.env.DB_TYPE || 'mysql';
|
|
|
|
|
|
|
+ const dbType = process.env.DB_TYPE || 'postgres';
|
|
|
|
|
|
|
|
if (dbType === 'mysql') {
|
|
if (dbType === 'mysql') {
|
|
|
await this.initializeMySQL();
|
|
await this.initializeMySQL();
|
|
@@ -61,47 +56,45 @@ class DatabaseInitializer {
|
|
|
|
|
|
|
|
async initializeMySQL() {
|
|
async initializeMySQL() {
|
|
|
try {
|
|
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)
|
|
|
|
|
|
|
+ // Create candles_h1 table for storing H1 candlestick data
|
|
|
|
|
+ const createCandlesH1TableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS candles_h1 (
|
|
|
|
|
+ id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
+ symbol TEXT NOT NULL,
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
|
|
+ open FLOAT NOT NULL,
|
|
|
|
|
+ high FLOAT NOT NULL,
|
|
|
|
|
+ low FLOAT NOT NULL,
|
|
|
|
|
+ close FLOAT NOT NULL,
|
|
|
|
|
+ volume FLOAT NOT NULL,
|
|
|
|
|
+ timestamp TIMESTAMP NOT NULL,
|
|
|
|
|
+ UNIQUE KEY unique_symbol_timestamp (symbol, timestamp),
|
|
|
|
|
+ INDEX idx_symbol (symbol),
|
|
|
|
|
+ INDEX idx_market (market),
|
|
|
|
|
+ INDEX idx_timestamp (timestamp)
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
) 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,
|
|
|
|
|
|
|
+ await executeNonQuery(createCandlesH1TableQuery);
|
|
|
|
|
+ logger.info('MySQL candles_h1 table created successfully');
|
|
|
|
|
+
|
|
|
|
|
+ // Create live_prices table for real-time price updates
|
|
|
|
|
+ const createLivePricesTableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS live_prices (
|
|
|
|
|
+ id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
+ symbol TEXT NOT NULL,
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
|
|
+ price FLOAT NOT NULL,
|
|
|
|
|
+ updated_at TIMESTAMP NOT NULL,
|
|
|
|
|
+ UNIQUE KEY unique_symbol_market (symbol, market),
|
|
|
INDEX idx_symbol (symbol),
|
|
INDEX idx_symbol (symbol),
|
|
|
- INDEX idx_category (category),
|
|
|
|
|
- INDEX idx_exchange (exchange)
|
|
|
|
|
|
|
+ INDEX idx_market (market),
|
|
|
|
|
+ INDEX idx_updated_at (updated_at)
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
- await executeNonQuery(createSymbolsTableQuery);
|
|
|
|
|
- logger.info('MySQL symbols table created successfully');
|
|
|
|
|
|
|
+ await executeNonQuery(createLivePricesTableQuery);
|
|
|
|
|
+ logger.info('MySQL live_prices table created successfully');
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
logger.error('Error creating MySQL tables:', error);
|
|
logger.error('Error creating MySQL tables:', error);
|
|
@@ -111,54 +104,65 @@ class DatabaseInitializer {
|
|
|
|
|
|
|
|
async initializePostgreSQL() {
|
|
async initializePostgreSQL() {
|
|
|
try {
|
|
try {
|
|
|
- // Create price_data table for PostgreSQL
|
|
|
|
|
- const createPriceTableQuery = `
|
|
|
|
|
- CREATE TABLE IF NOT EXISTS price_data (
|
|
|
|
|
|
|
+ // Create candles_h1 table for PostgreSQL
|
|
|
|
|
+ const createCandlesH1TableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS candles_h1 (
|
|
|
id SERIAL PRIMARY KEY,
|
|
id SERIAL PRIMARY KEY,
|
|
|
- symbol VARCHAR(10) NOT NULL,
|
|
|
|
|
- timeframe VARCHAR(5) NOT NULL,
|
|
|
|
|
|
|
+ symbol TEXT NOT NULL,
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
|
|
+ open FLOAT NOT NULL,
|
|
|
|
|
+ high FLOAT NOT NULL,
|
|
|
|
|
+ low FLOAT NOT NULL,
|
|
|
|
|
+ close FLOAT NOT NULL,
|
|
|
|
|
+ volume FLOAT NOT NULL,
|
|
|
timestamp TIMESTAMP 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
|
|
|
|
|
|
|
+ UNIQUE(symbol, timestamp)
|
|
|
);
|
|
);
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
- await executeNonQuery(createPriceTableQuery);
|
|
|
|
|
- logger.info('PostgreSQL price data table created successfully');
|
|
|
|
|
|
|
+ await executeNonQuery(createCandlesH1TableQuery);
|
|
|
|
|
+ logger.info('PostgreSQL candles_h1 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);'
|
|
|
|
|
|
|
+ // Create indexes for candles_h1
|
|
|
|
|
+ const createCandlesH1Indexes = [
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_symbol ON candles_h1(symbol);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_market ON candles_h1(market);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_timestamp ON candles_h1(timestamp);'
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- for (const indexQuery of createIndexes) {
|
|
|
|
|
|
|
+ for (const indexQuery of createCandlesH1Indexes) {
|
|
|
await executeNonQuery(indexQuery);
|
|
await executeNonQuery(indexQuery);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- logger.info('PostgreSQL indexes created successfully');
|
|
|
|
|
|
|
+ logger.info('PostgreSQL candles_h1 indexes created successfully');
|
|
|
|
|
|
|
|
- // Create symbols table for PostgreSQL
|
|
|
|
|
- const createSymbolsTableQuery = `
|
|
|
|
|
- CREATE TABLE IF NOT EXISTS symbols (
|
|
|
|
|
|
|
+ // Create live_prices table for PostgreSQL
|
|
|
|
|
+ const createLivePricesTableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS live_prices (
|
|
|
id SERIAL PRIMARY KEY,
|
|
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
|
|
|
|
|
|
|
+ symbol TEXT NOT NULL,
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
|
|
+ price FLOAT NOT NULL,
|
|
|
|
|
+ updated_at TIMESTAMP NOT NULL,
|
|
|
|
|
+ UNIQUE(symbol, market)
|
|
|
);
|
|
);
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
- await executeNonQuery(createSymbolsTableQuery);
|
|
|
|
|
- logger.info('PostgreSQL symbols table created successfully');
|
|
|
|
|
|
|
+ await executeNonQuery(createLivePricesTableQuery);
|
|
|
|
|
+ logger.info('PostgreSQL live_prices table created successfully');
|
|
|
|
|
+
|
|
|
|
|
+ // Create indexes for live_prices
|
|
|
|
|
+ const createLivePricesIndexes = [
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_symbol ON live_prices(symbol);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_market ON live_prices(market);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_updated_at ON live_prices(updated_at);'
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ for (const indexQuery of createLivePricesIndexes) {
|
|
|
|
|
+ await executeNonQuery(indexQuery);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info('PostgreSQL live_prices indexes created successfully');
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
logger.error('Error creating PostgreSQL tables:', error);
|
|
logger.error('Error creating PostgreSQL tables:', error);
|
|
@@ -168,259 +172,87 @@ class DatabaseInitializer {
|
|
|
|
|
|
|
|
async initializeSQLite() {
|
|
async initializeSQLite() {
|
|
|
try {
|
|
try {
|
|
|
- // Create price_data table
|
|
|
|
|
- const createPriceTableQuery = `
|
|
|
|
|
- CREATE TABLE IF NOT EXISTS price_data (
|
|
|
|
|
|
|
+ // Create candles_h1 table for SQLite
|
|
|
|
|
+ const createCandlesH1TableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS candles_h1 (
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
symbol TEXT NOT NULL,
|
|
symbol TEXT NOT NULL,
|
|
|
- timeframe TEXT NOT NULL,
|
|
|
|
|
- timestamp DATETIME NOT NULL,
|
|
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
open REAL NOT NULL,
|
|
open REAL NOT NULL,
|
|
|
high REAL NOT NULL,
|
|
high REAL NOT NULL,
|
|
|
low REAL NOT NULL,
|
|
low REAL NOT NULL,
|
|
|
close REAL NOT NULL,
|
|
close REAL NOT NULL,
|
|
|
- volume INTEGER NOT NULL,
|
|
|
|
|
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
|
+ volume REAL NOT NULL,
|
|
|
|
|
+ timestamp DATETIME NOT NULL,
|
|
|
|
|
+ UNIQUE(symbol, timestamp)
|
|
|
);
|
|
);
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
- await executeNonQuery(createPriceTableQuery);
|
|
|
|
|
- logger.info('SQLite price data table created successfully');
|
|
|
|
|
|
|
+ await executeNonQuery(createCandlesH1TableQuery);
|
|
|
|
|
+ logger.info('SQLite candles_h1 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);'
|
|
|
|
|
|
|
+ // Create indexes for candles_h1
|
|
|
|
|
+ const createCandlesH1Indexes = [
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_symbol ON candles_h1(symbol);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_market ON candles_h1(market);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_candles_timestamp ON candles_h1(timestamp);'
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- for (const indexQuery of createIndexes) {
|
|
|
|
|
|
|
+ for (const indexQuery of createCandlesH1Indexes) {
|
|
|
await executeNonQuery(indexQuery);
|
|
await executeNonQuery(indexQuery);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- logger.info('SQLite indexes created successfully');
|
|
|
|
|
|
|
+ logger.info('SQLite candles_h1 indexes created successfully');
|
|
|
|
|
|
|
|
- // Create symbols table
|
|
|
|
|
- const createSymbolsTableQuery = `
|
|
|
|
|
- CREATE TABLE IF NOT EXISTS symbols (
|
|
|
|
|
|
|
+ // Create live_prices table for SQLite
|
|
|
|
|
+ const createLivePricesTableQuery = `
|
|
|
|
|
+ CREATE TABLE IF NOT EXISTS live_prices (
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
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
|
|
|
|
|
|
|
+ symbol TEXT NOT NULL,
|
|
|
|
|
+ market TEXT NOT NULL,
|
|
|
|
|
+ price REAL NOT NULL,
|
|
|
|
|
+ updated_at DATETIME NOT NULL,
|
|
|
|
|
+ UNIQUE(symbol, market)
|
|
|
);
|
|
);
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
- 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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ await executeNonQuery(createLivePricesTableQuery);
|
|
|
|
|
+ logger.info('SQLite live_prices table created successfully');
|
|
|
|
|
|
|
|
- // 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' }
|
|
|
|
|
|
|
+ // Create indexes for live_prices
|
|
|
|
|
+ const createLivePricesIndexes = [
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_symbol ON live_prices(symbol);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_market ON live_prices(market);',
|
|
|
|
|
+ 'CREATE INDEX IF NOT EXISTS idx_live_updated_at ON live_prices(updated_at);'
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- 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}`);
|
|
|
|
|
|
|
+ for (const indexQuery of createLivePricesIndexes) {
|
|
|
|
|
+ await executeNonQuery(indexQuery);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- logger.info('Sample price data generation completed');
|
|
|
|
|
|
|
+ logger.info('SQLite live_prices indexes created successfully');
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- logger.error('Error generating sample price data:', error);
|
|
|
|
|
|
|
+ logger.error('Error creating SQLite tables:', error);
|
|
|
throw 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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Removed all sample data generation methods as per user requirements
|
|
|
|
|
|
|
|
async reset() {
|
|
async reset() {
|
|
|
try {
|
|
try {
|
|
|
logger.info('Resetting database...');
|
|
logger.info('Resetting database...');
|
|
|
|
|
|
|
|
- const dbType = process.env.DB_TYPE || 'sqlite';
|
|
|
|
|
|
|
+ const dbType = process.env.DB_TYPE || 'postgres';
|
|
|
|
|
|
|
|
- // Drop tables
|
|
|
|
|
|
|
+ // Drop tables in correct order (considering foreign key constraints)
|
|
|
if (dbType === 'sqlite') {
|
|
if (dbType === 'sqlite') {
|
|
|
- await executeNonQuery('DROP TABLE IF EXISTS price_data');
|
|
|
|
|
- await executeNonQuery('DROP TABLE IF EXISTS symbols');
|
|
|
|
|
|
|
+ await executeNonQuery('DROP TABLE IF EXISTS candles_h1');
|
|
|
|
|
+ await executeNonQuery('DROP TABLE IF EXISTS live_prices');
|
|
|
} else {
|
|
} else {
|
|
|
- await executeNonQuery('DROP TABLE IF EXISTS price_data');
|
|
|
|
|
- await executeNonQuery('DROP TABLE IF EXISTS symbols');
|
|
|
|
|
|
|
+ await executeNonQuery('DROP TABLE IF EXISTS candles_h1');
|
|
|
|
|
+ await executeNonQuery('DROP TABLE IF EXISTS live_prices');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
this.initialized = false;
|
|
this.initialized = false;
|
|
@@ -437,41 +269,41 @@ class DatabaseInitializer {
|
|
|
|
|
|
|
|
async getStats() {
|
|
async getStats() {
|
|
|
try {
|
|
try {
|
|
|
- const dbType = process.env.DB_TYPE || 'sqlite';
|
|
|
|
|
|
|
+ const dbType = process.env.DB_TYPE || 'postgres';
|
|
|
|
|
|
|
|
- let symbolCount, priceCount;
|
|
|
|
|
|
|
+ let candlesH1Count, livePricesCount;
|
|
|
|
|
|
|
|
if (dbType === 'sqlite') {
|
|
if (dbType === 'sqlite') {
|
|
|
- symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
|
|
|
|
|
- priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
|
|
|
|
|
|
|
+ candlesH1Count = await executeQuery('SELECT COUNT(*) as count FROM candles_h1');
|
|
|
|
|
+ livePricesCount = await executeQuery('SELECT COUNT(*) as count FROM live_prices');
|
|
|
} else {
|
|
} else {
|
|
|
- symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
|
|
|
|
|
- priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
|
|
|
|
|
|
|
+ candlesH1Count = await executeQuery('SELECT COUNT(*) as count FROM candles_h1');
|
|
|
|
|
+ livePricesCount = await executeQuery('SELECT COUNT(*) as count FROM live_prices');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Handle different database result formats
|
|
// Handle different database result formats
|
|
|
- let symbols = 0;
|
|
|
|
|
- let priceRecords = 0;
|
|
|
|
|
|
|
+ let candlesH1 = 0;
|
|
|
|
|
+ let livePrices = 0;
|
|
|
|
|
|
|
|
- if (symbolCount && symbolCount.length > 0) {
|
|
|
|
|
- symbols = symbolCount[0].count || 0;
|
|
|
|
|
|
|
+ if (candlesH1Count && candlesH1Count.length > 0) {
|
|
|
|
|
+ candlesH1 = candlesH1Count[0].count || 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (priceCount && priceCount.length > 0) {
|
|
|
|
|
- priceRecords = priceCount[0].count || 0;
|
|
|
|
|
|
|
+ if (livePricesCount && livePricesCount.length > 0) {
|
|
|
|
|
+ livePrices = livePricesCount[0].count || 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- symbols,
|
|
|
|
|
- price_records: priceRecords,
|
|
|
|
|
|
|
+ candles_h1: candlesH1,
|
|
|
|
|
+ live_prices: livePrices,
|
|
|
initialized: this.initialized
|
|
initialized: this.initialized
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
logger.error('Error getting database stats:', error);
|
|
logger.error('Error getting database stats:', error);
|
|
|
return {
|
|
return {
|
|
|
- symbols: 0,
|
|
|
|
|
- price_records: 0,
|
|
|
|
|
|
|
+ candles_h1: 0,
|
|
|
|
|
+ live_prices: 0,
|
|
|
initialized: false
|
|
initialized: false
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|