databaseInit.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. const { executeQuery, executeNonQuery } = require('../config/database');
  2. const { logger } = require('./logger');
  3. const fs = require('fs');
  4. const path = require('path');
  5. class DatabaseInitializer {
  6. constructor() {
  7. this.initialized = false;
  8. }
  9. async initialize() {
  10. if (this.initialized) {
  11. logger.info('Database already initialized');
  12. return;
  13. }
  14. try {
  15. logger.info('Starting database initialization...');
  16. // Create tables based on database type
  17. const dbType = process.env.DB_TYPE || 'sqlite';
  18. switch (dbType) {
  19. case 'mysql':
  20. case 'postgres':
  21. await this.initializeRelationalDatabase();
  22. break;
  23. case 'sqlite':
  24. default:
  25. await this.initializeSQLite();
  26. break;
  27. }
  28. // Insert sample data
  29. await this.insertSampleData();
  30. this.initialized = true;
  31. logger.info('Database initialization completed successfully');
  32. } catch (error) {
  33. logger.error('Database initialization failed:', error);
  34. throw error;
  35. }
  36. }
  37. async initializeRelationalDatabase() {
  38. try {
  39. const dbType = process.env.DB_TYPE || 'mysql';
  40. if (dbType === 'mysql') {
  41. await this.initializeMySQL();
  42. } else if (dbType === 'postgres') {
  43. await this.initializePostgreSQL();
  44. }
  45. } catch (error) {
  46. logger.error('Error creating relational database tables:', error);
  47. throw error;
  48. }
  49. }
  50. async initializeMySQL() {
  51. try {
  52. // Create price_data table for MySQL
  53. const createPriceTableQuery = `
  54. CREATE TABLE IF NOT EXISTS price_data (
  55. id INT PRIMARY KEY AUTO_INCREMENT,
  56. symbol VARCHAR(10) NOT NULL,
  57. timeframe VARCHAR(5) NOT NULL,
  58. timestamp DATETIME NOT NULL,
  59. open DECIMAL(15,8) NOT NULL,
  60. high DECIMAL(15,8) NOT NULL,
  61. low DECIMAL(15,8) NOT NULL,
  62. close DECIMAL(15,8) NOT NULL,
  63. volume BIGINT NOT NULL,
  64. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  65. INDEX idx_symbol_timeframe (symbol, timeframe),
  66. INDEX idx_timestamp (timestamp),
  67. INDEX idx_symbol_timestamp (symbol, timestamp)
  68. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  69. `;
  70. await executeNonQuery(createPriceTableQuery);
  71. logger.info('MySQL price data table created successfully');
  72. // Create symbols table for MySQL
  73. const createSymbolsTableQuery = `
  74. CREATE TABLE IF NOT EXISTS symbols (
  75. id INT PRIMARY KEY AUTO_INCREMENT,
  76. symbol VARCHAR(10) UNIQUE NOT NULL,
  77. name VARCHAR(100) NOT NULL,
  78. category VARCHAR(20) NOT NULL,
  79. exchange VARCHAR(20) NOT NULL,
  80. is_active BOOLEAN DEFAULT TRUE,
  81. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  82. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  83. INDEX idx_symbol (symbol),
  84. INDEX idx_category (category),
  85. INDEX idx_exchange (exchange)
  86. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  87. `;
  88. await executeNonQuery(createSymbolsTableQuery);
  89. logger.info('MySQL symbols table created successfully');
  90. } catch (error) {
  91. logger.error('Error creating MySQL tables:', error);
  92. throw error;
  93. }
  94. }
  95. async initializePostgreSQL() {
  96. try {
  97. // Create price_data table for PostgreSQL
  98. const createPriceTableQuery = `
  99. CREATE TABLE IF NOT EXISTS price_data (
  100. id SERIAL PRIMARY KEY,
  101. symbol VARCHAR(10) NOT NULL,
  102. timeframe VARCHAR(5) NOT NULL,
  103. timestamp TIMESTAMP NOT NULL,
  104. open DECIMAL(15,8) NOT NULL,
  105. high DECIMAL(15,8) NOT NULL,
  106. low DECIMAL(15,8) NOT NULL,
  107. close DECIMAL(15,8) NOT NULL,
  108. volume BIGINT NOT NULL,
  109. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  110. );
  111. `;
  112. await executeNonQuery(createPriceTableQuery);
  113. logger.info('PostgreSQL price data table created successfully');
  114. // Create indexes for PostgreSQL
  115. const createIndexes = [
  116. 'CREATE INDEX IF NOT EXISTS idx_symbol_timeframe ON price_data(symbol, timeframe);',
  117. 'CREATE INDEX IF NOT EXISTS idx_timestamp ON price_data(timestamp);',
  118. 'CREATE INDEX IF NOT EXISTS idx_symbol_timestamp ON price_data(symbol, timestamp);'
  119. ];
  120. for (const indexQuery of createIndexes) {
  121. await executeNonQuery(indexQuery);
  122. }
  123. logger.info('PostgreSQL indexes created successfully');
  124. // Create symbols table for PostgreSQL
  125. const createSymbolsTableQuery = `
  126. CREATE TABLE IF NOT EXISTS symbols (
  127. id SERIAL PRIMARY KEY,
  128. symbol VARCHAR(10) UNIQUE NOT NULL,
  129. name VARCHAR(100) NOT NULL,
  130. category VARCHAR(20) NOT NULL,
  131. exchange VARCHAR(20) NOT NULL,
  132. is_active BOOLEAN DEFAULT TRUE,
  133. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  134. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  135. );
  136. `;
  137. await executeNonQuery(createSymbolsTableQuery);
  138. logger.info('PostgreSQL symbols table created successfully');
  139. } catch (error) {
  140. logger.error('Error creating PostgreSQL tables:', error);
  141. throw error;
  142. }
  143. }
  144. async initializeSQLite() {
  145. try {
  146. // Create price_data table
  147. const createPriceTableQuery = `
  148. CREATE TABLE IF NOT EXISTS price_data (
  149. id INTEGER PRIMARY KEY AUTOINCREMENT,
  150. symbol TEXT NOT NULL,
  151. timeframe TEXT NOT NULL,
  152. timestamp DATETIME NOT NULL,
  153. open REAL NOT NULL,
  154. high REAL NOT NULL,
  155. low REAL NOT NULL,
  156. close REAL NOT NULL,
  157. volume INTEGER NOT NULL,
  158. created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  159. );
  160. `;
  161. await executeNonQuery(createPriceTableQuery);
  162. logger.info('SQLite price data table created successfully');
  163. // Create indexes for better performance
  164. const createIndexes = [
  165. 'CREATE INDEX IF NOT EXISTS idx_symbol_timeframe ON price_data(symbol, timeframe);',
  166. 'CREATE INDEX IF NOT EXISTS idx_timestamp ON price_data(timestamp);',
  167. 'CREATE INDEX IF NOT EXISTS idx_symbol_timestamp ON price_data(symbol, timestamp);'
  168. ];
  169. for (const indexQuery of createIndexes) {
  170. await executeNonQuery(indexQuery);
  171. }
  172. logger.info('SQLite indexes created successfully');
  173. // Create symbols table
  174. const createSymbolsTableQuery = `
  175. CREATE TABLE IF NOT EXISTS symbols (
  176. id INTEGER PRIMARY KEY AUTOINCREMENT,
  177. symbol TEXT UNIQUE NOT NULL,
  178. name TEXT NOT NULL,
  179. category TEXT NOT NULL,
  180. exchange TEXT NOT NULL,
  181. is_active INTEGER DEFAULT 1,
  182. created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  183. updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
  184. );
  185. `;
  186. await executeNonQuery(createSymbolsTableQuery);
  187. logger.info('SQLite symbols table created successfully');
  188. } catch (error) {
  189. logger.error('Error creating SQLite tables:', error);
  190. throw error;
  191. }
  192. }
  193. async insertSampleData() {
  194. try {
  195. const dbType = process.env.DB_TYPE || 'sqlite';
  196. // Check if symbols already exist
  197. let existingSymbols;
  198. if (dbType === 'sqlite') {
  199. existingSymbols = await executeQuery('SELECT COUNT(*) as count FROM symbols');
  200. } else {
  201. existingSymbols = await executeQuery('SELECT COUNT(*) as count FROM symbols');
  202. }
  203. // Handle different database result formats
  204. let symbolCount = 0;
  205. if (existingSymbols && existingSymbols.length > 0) {
  206. symbolCount = existingSymbols[0].count || 0;
  207. }
  208. if (symbolCount > 0) {
  209. logger.info('Sample symbols already exist, skipping insertion');
  210. return;
  211. }
  212. // Insert sample symbols
  213. const sampleSymbols = [
  214. { symbol: 'EURUSD', name: 'Euro vs US Dollar', category: 'forex', exchange: 'FX' },
  215. { symbol: 'GBPUSD', name: 'British Pound vs US Dollar', category: 'forex', exchange: 'FX' },
  216. { symbol: 'USDJPY', name: 'US Dollar vs Japanese Yen', category: 'forex', exchange: 'FX' },
  217. { symbol: 'USDCHF', name: 'US Dollar vs Swiss Franc', category: 'forex', exchange: 'FX' },
  218. { symbol: 'USDCAD', name: 'US Dollar vs Canadian Dollar', category: 'forex', exchange: 'FX' },
  219. { symbol: 'AUDUSD', name: 'Australian Dollar vs US Dollar', category: 'forex', exchange: 'FX' },
  220. { symbol: 'NZDUSD', name: 'New Zealand Dollar vs US Dollar', category: 'forex', exchange: 'FX' },
  221. { symbol: 'BTCUSD', name: 'Bitcoin vs US Dollar', category: 'crypto', exchange: 'Crypto' },
  222. { symbol: 'ETHUSD', name: 'Ethereum vs US Dollar', category: 'crypto', exchange: 'Crypto' },
  223. { symbol: 'AAPL', name: 'Apple Inc.', category: 'stocks', exchange: 'NASDAQ' },
  224. { symbol: 'GOOGL', name: 'Alphabet Inc.', category: 'stocks', exchange: 'NASDAQ' },
  225. { symbol: 'MSFT', name: 'Microsoft Corporation', category: 'stocks', exchange: 'NASDAQ' },
  226. { symbol: 'TSLA', name: 'Tesla Inc.', category: 'stocks', exchange: 'NASDAQ' },
  227. { symbol: 'AMZN', name: 'Amazon.com Inc.', category: 'stocks', exchange: 'NASDAQ' },
  228. { symbol: 'META', name: 'Meta Platforms Inc.', category: 'stocks', exchange: 'NASDAQ' }
  229. ];
  230. for (const symbolData of sampleSymbols) {
  231. let insertQuery;
  232. if (dbType === 'sqlite') {
  233. insertQuery = `
  234. INSERT OR IGNORE INTO symbols (symbol, name, category, exchange, is_active)
  235. VALUES (?, ?, ?, ?, 1)
  236. `;
  237. } else if (dbType === 'postgres') {
  238. insertQuery = `
  239. INSERT INTO symbols (symbol, name, category, exchange, is_active)
  240. VALUES (?, ?, ?, ?, ?)
  241. ON CONFLICT (symbol) DO NOTHING
  242. `;
  243. } else {
  244. insertQuery = `
  245. INSERT IGNORE INTO symbols (symbol, name, category, exchange, is_active)
  246. VALUES (?, ?, ?, ?, 1)
  247. `;
  248. }
  249. await executeNonQuery(insertQuery, [
  250. symbolData.symbol,
  251. symbolData.name,
  252. symbolData.category,
  253. symbolData.exchange,
  254. true
  255. ]);
  256. }
  257. logger.info(`Inserted ${sampleSymbols.length} sample symbols`);
  258. // Generate sample price data for the last 1000 H1 candles
  259. await this.generateSamplePriceData(sampleSymbols.slice(0, 7)); // Generate for first 7 symbols
  260. } catch (error) {
  261. logger.error('Error inserting sample data:', error);
  262. throw error;
  263. }
  264. }
  265. async generateSamplePriceData(symbols) {
  266. try {
  267. logger.info('Generating sample price data...');
  268. const now = new Date();
  269. const dbType = process.env.DB_TYPE || 'sqlite';
  270. for (const symbolData of symbols) {
  271. logger.info(`Generating price data for ${symbolData.symbol}...`);
  272. // Generate 1000 H1 candles (about 41 days of data)
  273. const priceData = [];
  274. let currentPrice = this.getInitialPrice(symbolData.symbol);
  275. for (let i = 999; i >= 0; i--) {
  276. const timestamp = new Date(now.getTime() - (i * 60 * 60 * 1000)); // H1 intervals
  277. const open = currentPrice;
  278. // Generate realistic price movement (±1%)
  279. const change = (Math.random() - 0.5) * 0.02;
  280. const close = open * (1 + change);
  281. const high = Math.max(open, close) * (1 + Math.random() * 0.001);
  282. const low = Math.min(open, close) * (1 - Math.random() * 0.001);
  283. const volume = Math.floor(Math.random() * 1000000) + 100000;
  284. priceData.push({
  285. symbol: symbolData.symbol,
  286. timeframe: 'H1',
  287. timestamp,
  288. open: open.toFixed(8),
  289. high: high.toFixed(8),
  290. low: low.toFixed(8),
  291. close: close.toFixed(8),
  292. volume
  293. });
  294. currentPrice = close;
  295. }
  296. // Insert price data in batches
  297. const batchSize = 100;
  298. for (let i = 0; i < priceData.length; i += batchSize) {
  299. const batch = priceData.slice(i, i + batchSize);
  300. for (const price of batch) {
  301. let insertQuery;
  302. if (dbType === 'sqlite') {
  303. insertQuery = `
  304. INSERT OR IGNORE INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
  305. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  306. `;
  307. } else if (dbType === 'postgres') {
  308. insertQuery = `
  309. INSERT INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
  310. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  311. ON CONFLICT DO NOTHING
  312. `;
  313. } else {
  314. insertQuery = `
  315. INSERT IGNORE INTO price_data (symbol, timeframe, timestamp, open, high, low, close, volume)
  316. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  317. `;
  318. }
  319. await executeNonQuery(insertQuery, [
  320. price.symbol,
  321. price.timeframe,
  322. price.timestamp,
  323. price.open,
  324. price.high,
  325. price.low,
  326. price.close,
  327. price.volume
  328. ]);
  329. }
  330. }
  331. logger.info(`Generated ${priceData.length} price records for ${symbolData.symbol}`);
  332. }
  333. logger.info('Sample price data generation completed');
  334. } catch (error) {
  335. logger.error('Error generating sample price data:', error);
  336. throw error;
  337. }
  338. }
  339. getInitialPrice(symbol) {
  340. const initialPrices = {
  341. 'EURUSD': 1.0850,
  342. 'GBPUSD': 1.2650,
  343. 'USDJPY': 150.50,
  344. 'USDCHF': 0.9150,
  345. 'USDCAD': 1.3450,
  346. 'AUDUSD': 0.6550,
  347. 'NZDUSD': 0.6050
  348. };
  349. return initialPrices[symbol] || 1.0000;
  350. }
  351. async reset() {
  352. try {
  353. logger.info('Resetting database...');
  354. const dbType = process.env.DB_TYPE || 'sqlite';
  355. // Drop tables
  356. if (dbType === 'sqlite') {
  357. await executeNonQuery('DROP TABLE IF EXISTS price_data');
  358. await executeNonQuery('DROP TABLE IF EXISTS symbols');
  359. } else {
  360. await executeNonQuery('DROP TABLE IF EXISTS price_data');
  361. await executeNonQuery('DROP TABLE IF EXISTS symbols');
  362. }
  363. this.initialized = false;
  364. logger.info('Database reset completed');
  365. // Reinitialize
  366. await this.initialize();
  367. } catch (error) {
  368. logger.error('Error resetting database:', error);
  369. throw error;
  370. }
  371. }
  372. async getStats() {
  373. try {
  374. const dbType = process.env.DB_TYPE || 'sqlite';
  375. let symbolCount, priceCount;
  376. if (dbType === 'sqlite') {
  377. symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
  378. priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
  379. } else {
  380. symbolCount = await executeQuery('SELECT COUNT(*) as count FROM symbols');
  381. priceCount = await executeQuery('SELECT COUNT(*) as count FROM price_data');
  382. }
  383. // Handle different database result formats
  384. let symbols = 0;
  385. let priceRecords = 0;
  386. if (symbolCount && symbolCount.length > 0) {
  387. symbols = symbolCount[0].count || 0;
  388. }
  389. if (priceCount && priceCount.length > 0) {
  390. priceRecords = priceCount[0].count || 0;
  391. }
  392. return {
  393. symbols,
  394. price_records: priceRecords,
  395. initialized: this.initialized
  396. };
  397. } catch (error) {
  398. logger.error('Error getting database stats:', error);
  399. return {
  400. symbols: 0,
  401. price_records: 0,
  402. initialized: false
  403. };
  404. }
  405. }
  406. }
  407. const databaseInitializer = new DatabaseInitializer();
  408. // Export functions for external use
  409. const initializeDatabase = () => databaseInitializer.initialize();
  410. const resetDatabase = () => databaseInitializer.reset();
  411. const getDatabaseStats = () => databaseInitializer.getStats();
  412. module.exports = {
  413. DatabaseInitializer,
  414. initializeDatabase,
  415. resetDatabase,
  416. getDatabaseStats,
  417. databaseInitializer
  418. };