300 lines
18 KiB
PHP
300 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Forex Trading Journal Database Layer (SQLite)
|
|
* High-performance, robust SQLite storage for Forex accounts, trades, and analytics.
|
|
*/
|
|
|
|
class Database {
|
|
private static ?PDO $pdo = null;
|
|
private static string $dbFile = __DIR__ . '/forex_journal.db';
|
|
|
|
public static function getConnection(): PDO {
|
|
if (self::$pdo === null) {
|
|
$isNew = !file_exists(self::$dbFile);
|
|
self::$pdo = new PDO('sqlite:' . self::$dbFile);
|
|
self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
|
|
// Enable WAL mode for high concurrency and performance
|
|
self::$pdo->exec('PRAGMA journal_mode = WAL;');
|
|
self::$pdo->exec('PRAGMA foreign_keys = ON;');
|
|
|
|
self::initializeSchema();
|
|
|
|
if ($isNew || self::isAccountTableEmpty()) {
|
|
self::seedInitialData();
|
|
}
|
|
}
|
|
return self::$pdo;
|
|
}
|
|
|
|
private static function initializeSchema(): void {
|
|
$db = self::$pdo;
|
|
|
|
// 1. Trading Accounts Table
|
|
$db->exec("
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
broker TEXT DEFAULT 'IC Markets',
|
|
account_number TEXT DEFAULT '',
|
|
currency TEXT DEFAULT 'USD',
|
|
initial_balance REAL NOT NULL DEFAULT 10000.0,
|
|
leverage REAL DEFAULT 100.0,
|
|
account_type TEXT DEFAULT 'Real', -- Real, Demo, PropFirm
|
|
is_default INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
");
|
|
|
|
// 2. Trades Table
|
|
$db->exec("
|
|
CREATE TABLE IF NOT EXISTS trades (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
account_id INTEGER NOT NULL,
|
|
ticket TEXT DEFAULT '',
|
|
symbol TEXT NOT NULL, -- EURUSD, XAUUSD, GBPUSD, etc.
|
|
trade_type TEXT NOT NULL, -- buy, sell
|
|
lot_size REAL NOT NULL DEFAULT 0.1,
|
|
open_price REAL NOT NULL,
|
|
close_price REAL DEFAULT NULL,
|
|
stop_loss REAL DEFAULT NULL,
|
|
take_profit REAL DEFAULT NULL,
|
|
open_time DATETIME NOT NULL,
|
|
close_time DATETIME DEFAULT NULL,
|
|
pips REAL DEFAULT 0.0,
|
|
profit REAL DEFAULT 0.0, -- Net P&L in currency ($)
|
|
commission REAL DEFAULT 0.0,
|
|
swap REAL DEFAULT 0.0,
|
|
status TEXT DEFAULT 'closed', -- open, closed
|
|
strategy TEXT DEFAULT 'Price Action',
|
|
timeframe TEXT DEFAULT 'M15',
|
|
session TEXT DEFAULT 'London', -- London, New York, Asian, Overlap
|
|
risk_reward REAL DEFAULT NULL,
|
|
risk_percent REAL DEFAULT 1.0,
|
|
emotion TEXT DEFAULT 'Disciplined', -- Disciplined, Greedy, Fearful, Revenge, Confident, Impatient, Neutral
|
|
entry_notes TEXT DEFAULT '',
|
|
exit_notes TEXT DEFAULT '',
|
|
lessons TEXT DEFAULT '',
|
|
screenshot_entry TEXT DEFAULT '',
|
|
screenshot_exit TEXT DEFAULT '',
|
|
tags TEXT DEFAULT '',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
|
|
);
|
|
");
|
|
|
|
// 3. Daily Notes / Journal Mindset Log
|
|
$db->exec("
|
|
CREATE TABLE IF NOT EXISTS daily_logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
account_id INTEGER NOT NULL,
|
|
log_date DATE NOT NULL,
|
|
notes TEXT DEFAULT '',
|
|
mindset_score INTEGER DEFAULT 5, -- 1 to 5
|
|
market_condition TEXT DEFAULT 'Normal',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE,
|
|
UNIQUE(account_id, log_date)
|
|
);
|
|
");
|
|
|
|
// 4. Strategies / Setups Table
|
|
$db->exec("
|
|
CREATE TABLE IF NOT EXISTS strategies (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
description TEXT DEFAULT '',
|
|
color TEXT DEFAULT '#3b82f6',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
");
|
|
|
|
// Create indexes for fast analytical aggregations
|
|
$db->exec("CREATE INDEX IF NOT EXISTS idx_trades_account ON trades(account_id);");
|
|
$db->exec("CREATE INDEX IF NOT EXISTS idx_trades_open_time ON trades(open_time);");
|
|
$db->exec("CREATE INDEX IF NOT EXISTS idx_trades_close_time ON trades(close_time);");
|
|
$db->exec("CREATE INDEX IF NOT EXISTS idx_trades_symbol ON trades(symbol);");
|
|
$db->exec("CREATE INDEX IF NOT EXISTS idx_trades_status ON trades(status);");
|
|
}
|
|
|
|
private static function isAccountTableEmpty(): bool {
|
|
$stmt = self::$pdo->query("SELECT COUNT(*) FROM accounts");
|
|
return ((int)$stmt->fetchColumn()) === 0;
|
|
}
|
|
|
|
public static function seedInitialData(): void {
|
|
$db = self::$pdo;
|
|
|
|
// Default Strategies
|
|
$strategies = [
|
|
['Price Action', 'Break of structure, support & resistance', '#3b82f6'],
|
|
['ICT / SMC', 'Order blocks, Fair Value Gaps (FVG) and liquidity sweeps', '#8b5cf6'],
|
|
['Trend Following', 'Moving average pullbacks and trend continuation', '#10b981'],
|
|
['Scalping', 'Fast momentum moves on M1/M5 charts', '#f59e0b'],
|
|
['Reversal / Divergence', 'RSI/MACD divergence at key supply/demand levels', '#ec4899'],
|
|
['News Trading', 'High-impact economic news releases', '#ef4444'],
|
|
];
|
|
|
|
$stmtStrat = $db->prepare("INSERT OR IGNORE INTO strategies (name, description, color) VALUES (?, ?, ?)");
|
|
foreach ($strategies as $strat) {
|
|
$stmtStrat->execute($strat);
|
|
}
|
|
|
|
// Default Accounts
|
|
$stmtAcc = $db->prepare("
|
|
INSERT INTO accounts (name, broker, account_number, currency, initial_balance, leverage, account_type, is_default)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
");
|
|
$stmtAcc->execute(['حساب معاملاتی اصلی (Main Live)', 'IC Markets', '8841920', 'USD', 10000.0, 100.0, 'Real', 1]);
|
|
$mainAccountId = (int)$db->lastInsertId();
|
|
|
|
$stmtAcc->execute(['چالش پراپ فرم ۱۰۰ هزار دلاری (FTMO Prop)', 'FTMO', '2948173', 'USD', 100000.0, 100.0, 'PropFirm', 0]);
|
|
|
|
// Seed rich realistic trading history for main account
|
|
self::generateSampleTrades($mainAccountId, 10000.0);
|
|
}
|
|
|
|
public static function generateSampleTrades(int $accountId, float $startBalance = 10000.0): void {
|
|
$db = self::$pdo;
|
|
|
|
// Trade definition array:
|
|
// [0:symbol, 1:type, 2:lot, 3:openPrice, 4:closePrice, 5:sl, 6:tp, 7:pips, 8:profit, 9:comm, 10:swap, 11:strat, 12:tf, 13:session, 14:rr, 15:emotion, 16:entryNotes, 17:exitNotes, 18:lessons]
|
|
$tradeDataList = [
|
|
['EURUSD', 'buy', 1.0, 1.08200, 1.08650, 1.07950, 1.08700, 45.0, 450.0, -7.0, 0.0, 'ICT / SMC', 'H1', 'London', 1.8, 'Disciplined', 'جاروب نقدینگی لندن و واکنش به FVG', 'رسیدن به هدف نقدینگی سقف روز قبل', 'پایبندی به استراتژی'],
|
|
['XAUUSD', 'buy', 0.5, 2350.20, 2368.50, 2342.00, 2370.00, 183.0, 915.0, -5.0, -2.1, 'Price Action', 'M15', 'New York', 2.2, 'Confident', 'تست مجدد سطح حمایتی ۲۳۵۰ با کندل تاییدیه پین بار', 'خروج در نزدیکی تارگت با سود عالی', 'صبر برای پولبک کلید موفقیت بود'],
|
|
['GBPUSD', 'sell', 1.2, 1.27600, 1.27150, 1.27850, 1.27000, 45.0, 540.0, -8.4, 1.5, 'Trend Following', 'M15', 'London', 1.8, 'Disciplined', 'شکست خط روند نزولی و پولبک به EMA50', 'ریسک فری شد و در تارگت ۲ بسته شد', 'مدیریت ترید عالی'],
|
|
['USDJPY', 'buy', 1.0, 154.800, 154.450, 154.400, 155.600, -35.0, -225.0, -7.0, 0.0, 'Scalping', 'M5', 'Asian', 0.0, 'Fearful', 'ورود با مومنتوم صعودی ضعیف در نشست آسیا', 'اصابت به استاپ لاس به دلیل اسپرد بالا', 'در سشن آسیا نباید روی جفتهای کمنوسان بدون کاتالیزور معامله کرد'],
|
|
['EURUSD', 'sell', 1.5, 1.08900, 1.08350, 1.09150, 1.08200, 55.0, 825.0, -10.5, 0.0, 'ICT / SMC', 'M15', 'New York', 2.2, 'Disciplined', 'شکار نقدینگی سشن نیویورک و تایید با اردر بلاک خرسی', 'کست کامل سود در کف سشن لندن', 'ورود دقیق روی اوردربلاک'],
|
|
['XAUUSD', 'sell', 0.8, 2385.00, 2392.50, 2392.00, 2370.00, -75.0, -600.0, -8.0, 0.0, 'Reversal / Divergence', 'M15', 'New York', 0.0, 'Greedy', 'ورود برخلاف روند قدرتمند بدون واگرایی قطعی', 'استاپ خورد، طلا شتاب شدید صعودی داشت', 'هرگز نباید جلوی قطار سریعالسیر ایستاد!'],
|
|
['BTCUSD', 'buy', 0.2, 63200.0, 65100.0, 62400.0, 65500.0, 1900.0, 380.0, -3.0, -0.5, 'Trend Following', 'H4', 'London', 2.4, 'Confident', 'حفظ حمایت رند ۶۳۰۰۰ و تشکیل الگوی دابل باتم', 'خروج پلهای در ۶۵۱۰۰', 'معامله سوینگی تمیز'],
|
|
['US30', 'buy', 0.5, 39200.0, 39650.0, 39000.0, 39700.0, 450.0, 225.0, -5.0, 0.0, 'Scalping', 'M5', 'New York', 2.2, 'Disciplined', 'اوپن سشن نیویورک و جهش شاخص داوجونز', 'خروج سریع با تارگت تعیین شده', 'مدیریت ریسک عالی'],
|
|
['GBPUSD', 'buy', 1.0, 1.26800, 1.27400, 1.26500, 1.27500, 60.0, 600.0, -7.0, 0.0, 'ICT / SMC', 'M15', 'London', 2.0, 'Disciplined', 'برگشت قیمت از ناحیه تقاضا و جاروب کف آسیا', 'تارگت لمس شد', 'برنامه معاملاتی مو به مو اجرا شد'],
|
|
['EURUSD', 'sell', 1.0, 1.08500, 1.08800, 1.08800, 1.08000, -30.0, -300.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 0.0, 'Impatient', 'ورود عجولانه قبل از بسته شدن کندل ۱۵ دقیقه', 'برگشت قیمت به بالا و استاپ', 'صبر برای کلوز کندل ضروری است'],
|
|
['XAUUSD', 'buy', 0.7, 2360.00, 2378.00, 2353.00, 2380.00, 180.0, 1260.0, -7.0, -1.8, 'ICT / SMC', 'H1', 'Overlap', 2.5, 'Disciplined', 'تلاقی FVG با لول ۵۰٪ فیبوناچی در اورلپ لندن/نیویورک', 'تارگت کامل محقق شد', 'یکی از بهترین معاملات ماه'],
|
|
['AUDUSD', 'sell', 1.0, 0.66800, 0.66350, 0.67050, 0.66200, 45.0, 450.0, -7.0, 1.2, 'Trend Following', 'H1', 'Asian', 1.8, 'Neutral', 'شکست رنج قیمتی و تاییدیه با دادههای اشتغال استرالیا', 'خروج با سود خوب', 'انطباق خوب با تحلیل فاندامنتال'],
|
|
['USDJPY', 'sell', 1.0, 156.200, 155.300, 156.600, 155.000, 90.0, 580.0, -7.0, 0.0, 'Price Action', 'H1', 'New York', 2.2, 'Disciplined', 'سقف دو قلو در تایم فریم یک ساعته با کندل اینگالف نزولی', 'خروج در حمایت کلیدی ۱۵۵.۳۰', 'معامله بسیار روان و بدون استرس'],
|
|
['EURUSD', 'buy', 1.2, 1.08300, 1.08100, 1.08050, 1.08900, -20.0, -240.0, -8.4, 0.0, 'Scalping', 'M5', 'London', 0.0, 'Neutral', 'تلاش برای گرفتن بریک اوت سریع که فیک اوت شد', 'حد ضرر نجات داد', 'ضرر کوچک و کنترلشده'],
|
|
['XAUUSD', 'buy', 0.6, 2375.00, 2394.00, 2368.00, 2395.00, 190.0, 1140.0, -6.0, 0.0, 'ICT / SMC', 'M15', 'New York', 2.7, 'Confident', 'سوییپ نقدینگی کف روزانه در اخبار CPI و واکنش انفجاری', 'خروج در تارگت ۲۳۹۴', 'مدیریت پوزیشن عالی حین خبر'],
|
|
['GBPUSD', 'sell', 1.0, 1.27500, 1.27100, 1.27800, 1.27000, 40.0, 400.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 1.3, 'Disciplined', 'برخورد به سقف کانال نزولی', 'برداشت سود مطمئن', 'معامله استاندارد طبق پلن'],
|
|
['BTCUSD', 'sell', 0.25, 66500.0, 64200.0, 67200.0, 64000.0, 2300.0, 575.0, -3.5, 0.0, 'Reversal / Divergence', 'H4', 'New York', 3.2, 'Disciplined', 'واگرایی منفی مشهود در RSI تایم ۴ ساعته', 'ریزش عالی بیتکوین و سیو سود', 'نسبت ریسک به ریوارد بالای ۳'],
|
|
['US30', 'sell', 0.4, 39800.0, 39950.0, 39950.0, 39400.0, -150.0, -60.0, -4.0, 0.0, 'Scalping', 'M5', 'New York', 0.0, 'Neutral', 'ترید خلاف ترند در اوپن بازار', 'استاپ کوچک خورد', 'مدیریت ریسک عالی'],
|
|
['EURUSD', 'buy', 1.0, 1.08400, 1.08920, 1.08150, 1.09000, 52.0, 520.0, -7.0, 0.0, 'ICT / SMC', 'H1', 'London', 2.1, 'Disciplined', 'پر شدن FVG سشن لندن و چرخش روند', 'تارگت لمس شد', 'آرامش و صبر کامل'],
|
|
['XAUUSD', 'buy', 0.5, 2390.00, 2415.00, 2382.00, 2420.00, 250.0, 1250.0, -5.0, -2.5, 'Trend Following', 'H4', 'New York', 3.1, 'Confident', 'شکست سقف تاریخی قبلی و پولبک استاندارد', 'سود فوقالعاده با تارگت بالای ۲۴۱۵', 'رعایت تمام اصول استراتژی'],
|
|
['USDJPY', 'buy', 1.0, 155.100, 155.650, 154.750, 155.800, 55.0, 355.0, -7.0, 0.0, 'Price Action', 'M15', 'Asian', 1.5, 'Disciplined', 'حمایت معتبر در سشن توکیو', 'خروج با سود خوب در شروع سشن لندن', 'ترید تمیز'],
|
|
['GBPUSD', 'buy', 1.0, 1.26900, 1.26600, 1.26600, 1.27600, -30.0, -300.0, -7.0, 0.0, 'Price Action', 'M15', 'London', 0.0, 'Fearful', 'انتشار دادههای ضعیف بریتانیا و استاپ خوردن', 'استاپ رعایت شد', 'پایبندی به استاپ لاس نجاتبخش بود'],
|
|
['EURUSD', 'sell', 1.0, 1.08800, 1.08300, 1.09050, 1.08200, 50.0, 500.0, -7.0, 0.0, 'ICT / SMC', 'M15', 'New York', 2.0, 'Disciplined', 'شکار نقدینگی خریداران بالای ۱.۰۸۸۰ و ریزش سریع', 'خروج در کف روز', 'ستاپ کاملاً مکانیکی و تست شده']
|
|
];
|
|
|
|
$stmtTrade = $db->prepare("
|
|
INSERT INTO trades (
|
|
account_id, ticket, symbol, trade_type, lot_size,
|
|
open_price, close_price, stop_loss, take_profit,
|
|
open_time, close_time, pips, profit, commission, swap,
|
|
status, strategy, timeframe, session, risk_reward, emotion,
|
|
entry_notes, exit_notes, lessons, tags
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
");
|
|
|
|
$baseTime = time() - (count($tradeDataList) * 86400 * 0.9);
|
|
$ticketBase = 7418290;
|
|
|
|
foreach ($tradeDataList as $i => $t) {
|
|
$ticket = (string)($ticketBase + $i * 17);
|
|
$openTs = $baseTime + ($i * 75000) + rand(1000, 15000);
|
|
$durationSec = rand(1800, 28800);
|
|
$closeTs = $openTs + $durationSec;
|
|
|
|
$openTimeStr = date('Y-m-d H:i:s', $openTs);
|
|
$closeTimeStr = date('Y-m-d H:i:s', $closeTs);
|
|
|
|
$stmtTrade->execute([
|
|
$accountId,
|
|
$ticket,
|
|
$t[0],
|
|
$t[1],
|
|
$t[2],
|
|
$t[3],
|
|
$t[4],
|
|
$t[5],
|
|
$t[6],
|
|
$openTimeStr,
|
|
$closeTimeStr,
|
|
$t[7],
|
|
$t[8],
|
|
$t[9],
|
|
$t[10],
|
|
'closed',
|
|
$t[11],
|
|
$t[12],
|
|
$t[13],
|
|
$t[14],
|
|
$t[15],
|
|
$t[16],
|
|
$t[17],
|
|
$t[18],
|
|
'Live,DayTrade,Forex'
|
|
]);
|
|
}
|
|
|
|
// Add 2 open trades
|
|
$stmtTrade->execute([
|
|
$accountId,
|
|
(string)($ticketBase + 991),
|
|
'XAUUSD',
|
|
'buy',
|
|
0.5,
|
|
2405.50,
|
|
null,
|
|
2396.00,
|
|
2425.00,
|
|
date('Y-m-d H:i:s', time() - 3600),
|
|
null,
|
|
45.0,
|
|
225.0,
|
|
-5.0,
|
|
0.0,
|
|
'open',
|
|
'ICT / SMC',
|
|
'H1',
|
|
'London',
|
|
2.0,
|
|
'Disciplined',
|
|
'ورود پس از شکست مقاومت ۲۴۰۰ و تثبیت بالای آن',
|
|
'',
|
|
'',
|
|
'OpenTrade,Swing'
|
|
]);
|
|
|
|
$stmtTrade->execute([
|
|
$accountId,
|
|
(string)($ticketBase + 992),
|
|
'EURUSD',
|
|
'sell',
|
|
1.0,
|
|
1.08620,
|
|
null,
|
|
1.08850,
|
|
1.08100,
|
|
date('Y-m-d H:i:s', time() - 7200),
|
|
null,
|
|
12.0,
|
|
120.0,
|
|
-7.0,
|
|
0.0,
|
|
'open',
|
|
'Price Action',
|
|
'M15',
|
|
'London',
|
|
2.2,
|
|
'Confident',
|
|
'تشکیل الگوی پوشای نزولی در لول مقاومت روزانه',
|
|
'',
|
|
'',
|
|
'OpenTrade,Intraday'
|
|
]);
|
|
}
|
|
}
|