Initial commit: Trading Portal - FastAPI + React + PostgreSQL

This commit is contained in:
2026-07-03 13:08:09 +00:00
commit 34a1e91541
198 changed files with 35110 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
const en: Record<string, string> = {
// ── Nav ──
'nav.tradingPortal': 'Trading Portal',
'nav.profile': '👤 {name}',
'nav.panelToggle': 'Toggle panel',
'nav.logout': '🚪 Logout',
// ── Login ──
'login.title': 'Trading Portal',
'login.subtitle': 'Sign in to your account',
'login.username': 'Username',
'login.password': 'Password',
'login.submit': 'Sign In',
'login.signing': 'Signing in...',
'login.error': 'Invalid username or password',
// ── Dashboard ──
'dash.chart': '📈 Chart',
'dash.watchlist': '👁️ Watchlist',
'dash.order': '📝 Order',
'dash.signals': '📡 Signals',
'dash.quickBuy': '⚡ Quick Buy',
'dash.quickSell': '⚡ Quick Sell',
'dash.tradeSize': 'Trade Size (USDT)',
// ── ChartToolbar ──
'chart.exchange': 'Exchange',
'chart.symbol': 'Symbol',
'chart.search': 'Search symbols...',
'chart.loading': 'Loading...',
'chart.noData': 'No data',
'chart.error': 'Error loading symbols',
// ── Watchlist ──
'wl.title': '👁️ Watchlist',
'wl.search': 'Search symbols...',
'wl.add': '+',
'wl.remove': '✕',
'wl.loading': 'Loading...',
'wl.empty': 'No symbols in watchlist. Click + to add.',
'wl.error': 'Failed to load watchlist',
// ── Order Panel ──
'order.buy': 'Buy',
'order.sell': 'Sell',
'order.limit': 'Limit',
'order.market': 'Market',
'order.stop': 'Stop',
'order.amount': 'Amount',
'order.price': 'Price',
'order.total': 'Total',
'order.place': 'Place Order',
'order.placing': 'Placing...',
'order.success': '✅ Order placed successfully',
'order.loginRequired': '🔒 Login required',
'order.noKey': '⚠️ No API key configured',
'order.balance': 'Balance: {amount}',
'order.available': 'Available: {amount}',
// ── Signals ──
'sig.title': '📡 Live Signals',
'sig.strongBuy': '🚀 STRONG BUY',
'sig.buy': '📈 BUY',
'sig.strongSell': '🔻 STRONG SELL',
'sig.sell': '📉 SELL',
'sig.cautionLong': '⚠️ CAUTION (LONG)',
'sig.cautionShort': '⚠️ CAUTION (SHORT)',
'sig.squeeze': '⚡ SQUEEZE ALERT',
'sig.noSignal': 'No signals yet — waiting for new candles...',
'sig.loading': 'Loading signals...',
'sig.price': 'Price',
'sig.rsi': 'RSI',
'sig.bb': 'BB',
'sig.signal': 'Signal',
'sig.time': 'Time',
// ── Profile ──
'profile.tabs.info': 'ℹ️ Info',
'profile.tabs.keys': '🔑 API Keys',
'profile.tabs.history': '📜 History',
'profile.tabs.prefs': '⚙️ Settings',
'profile.tabs.sessions': '🔐 Sessions',
'profile.info.title': 'Profile Info',
'profile.info.email': 'Email',
'profile.info.displayName': 'Display Name',
'profile.info.save': 'Save Profile',
'profile.info.saved': '✅ Profile saved ✓',
'profile.info.error': 'Save failed',
'profile.password.title': 'Change Password',
'profile.password.old': 'Current Password',
'profile.password.new': 'New Password',
'profile.password.confirm': 'Confirm Password',
'profile.password.btn': 'Change Password',
'profile.password.validLength': '8+ characters',
'profile.password.validNumber': 'Need 1 number',
'profile.password.validSpecial': 'Need 1 special char',
'profile.password.fillAll': 'Fill all fields',
'profile.password.success': '✅ Password changed ✓',
'profile.keys.title': 'API Keys',
'profile.keys.add': '+ Add Key',
'profile.keys.edit': '✏️',
'profile.keys.delete': '🗑️',
'profile.keys.test': 'Test',
'profile.keys.empty': 'No API keys configured',
'profile.keys.exchange': 'Exchange',
'profile.keys.key': 'API Key',
'profile.keys.secret': 'Secret',
'profile.keys.passphrase': 'Passphrase',
'profile.keys.saving': 'Saving...',
'profile.keys.addTitle': 'Add API Key',
'profile.keys.editTitle': 'Edit API Key',
'profile.keys.deleteConfirm': 'Are you sure you want to delete this key?',
'profile.prefs.title': '⚙️ Settings',
'profile.prefs.exchange': 'Default Exchange',
'profile.prefs.timeframe': 'Default Timeframe',
'profile.prefs.theme': 'Theme',
'profile.prefs.upColor': 'Up Color',
'profile.prefs.downColor': 'Down Color',
'profile.prefs.notifSignal': '🔔 Signal Notifications',
'profile.prefs.notifTrade': '💬 Trade Execution',
'profile.prefs.notifTradeDesc': 'Gửi thông báo Telegram khi có lệnh thật được đặt',
'profile.prefs.language': 'Language',
'profile.prefs.tradeSize': '💰 Trade Size (USDT)',
'profile.prefs.tradeSizeDesc': 'Số USDT dùng cho mỗi lệnh — Signal trades & Auto trade',
'profile.prefs.autoTrade': '🤖 Auto Trade',
'profile.prefs.autoTradeDesc': 'Tự động đặt lệnh Spot mua/bán thật — cần chọn token bên dưới',
'profile.prefs.autoTradeWarning': '⚠️ Auto Trade đang BẬT — hệ thống sẽ tự động đặt lệnh market thật khi có signal',
'profile.prefs.autoTradeTokens': '🪙 Auto Trade Tokens',
'profile.prefs.autoTradeTokensDesc': 'Chọn token muốn Auto Trade — chỉ những token được chọn mới được auto trade',
'profile.prefs.searchToken': '🔍 Tìm token...',
'profile.prefs.noTokens': '⚠️ Chưa chọn token nào — Auto Trade sẽ không hoạt động',
'profile.prefs.tracking': '📊 Auto Trades đang theo dõi',
'profile.prefs.trackingDesc': '{n} cặp — click ✕ để bỏ theo dõi',
'profile.prefs.trackingPaused': '📊 Auto Trades (tạm dừng)',
'profile.prefs.trackingPausedDesc': 'Auto trade đang tắt — {n} cặp đã lưu',
'profile.prefs.save': '💾 Save Settings',
'profile.prefs.saved': '✅ Settings saved ✓',
'profile.prefs.spot': 'SPOT',
'profile.sessions.title': 'Active Sessions',
'profile.sessions.current': '(current)',
'profile.sessions.revoke': 'Revoke',
'profile.sessions.empty': 'No active sessions',
'profile.sessions.browser': 'Browser',
'profile.sessions.os': 'OS',
'profile.sessions.device': 'Device',
'profile.sessions.ip': 'IP',
'profile.sessions.created': 'Created',
'profile.history.signals': '📈 Signal Trades',
'profile.history.real': '💹 Real Trades',
'profile.history.empty': 'No trades yet',
'profile.history.side': 'Side',
'profile.history.symbol': 'Symbol',
'profile.history.price': 'Price',
'profile.history.qty': 'Qty',
'profile.history.pnl': 'PnL',
'profile.history.time': 'Time',
// ── Signal History ──
'sigHistory.pnl': 'Total PnL',
'sigHistory.winRate': 'Win Rate',
'sigHistory.trades': '{n} trades',
};
export default en;
+36
View File
@@ -0,0 +1,36 @@
import { createContext, useContext, type ReactNode, type FC } from 'react';
import en from './en';
import vi from './vi';
const locales = { en, vi } as const;
type Lang = keyof typeof locales;
type TContext = {
lang: Lang;
t: (key: string, vars?: Record<string, string | number>) => string;
setLang: (l: Lang) => void;
};
const Ctx = createContext<TContext>({
lang: 'en',
t: (k) => k,
setLang: () => {},
});
export const useT = () => useContext(Ctx);
type Props = { lang: Lang; setLang: (l: Lang) => void; children: ReactNode };
export const TProvider: FC<Props> = ({ lang, setLang, children }) => {
const t = (key: string, vars?: Record<string, string | number>): string => {
const dict = locales[lang] ?? en;
let msg = (dict as Record<string, string>)[key];
if (msg === undefined) msg = (en as Record<string, string>)[key] ?? key;
if (vars) {
for (const [k, v] of Object.entries(vars)) {
msg = msg.replace(`{${k}}`, String(v));
}
}
return msg;
};
return <Ctx.Provider value={{ lang, t, setLang }}>{children}</Ctx.Provider>;
};
+170
View File
@@ -0,0 +1,170 @@
const vi: Record<string, string> = {
// ── Nav ──
'nav.tradingPortal': '📊 Trading Portal',
'nav.profile': '👤 {name}',
'nav.panelToggle': 'Mở/tắt bảng',
'nav.logout': '🚪 Đăng xuất',
// ── Login ──
'login.title': 'Trading Portal',
'login.subtitle': 'Đăng nhập tài khoản',
'login.username': 'Tên đăng nhập',
'login.password': 'Mật khẩu',
'login.submit': 'Đăng nhập',
'login.signing': 'Đang đăng nhập...',
'login.error': 'Sai tên đăng nhập hoặc mật khẩu',
// ── Dashboard ──
'dash.chart': '📈 Biểu đồ',
'dash.watchlist': '👁️ Danh sách',
'dash.order': '📝 Đặt lệnh',
'dash.signals': '📡 Tín hiệu',
'dash.quickBuy': '⚡ Mua nhanh',
'dash.quickSell': '⚡ Bán nhanh',
'dash.tradeSize': 'Khối lượng (USDT)',
// ── ChartToolbar ──
'chart.exchange': 'Sàn',
'chart.symbol': 'Cặp',
'chart.search': 'Tìm symbol...',
'chart.loading': 'Đang tải...',
'chart.noData': 'Không có dữ liệu',
'chart.error': 'Lỗi tải symbol',
// ── Watchlist ──
'wl.title': '👁️ Danh sách',
'wl.search': 'Tìm symbol...',
'wl.add': '+',
'wl.remove': '✕',
'wl.loading': 'Đang tải...',
'wl.empty': 'Chưa có symbol nào. Click + để thêm.',
'wl.error': 'Lỗi tải danh sách',
// ── Order Panel ──
'order.buy': 'Mua',
'order.sell': 'Bán',
'order.limit': 'Giới hạn',
'order.market': 'Thị trường',
'order.stop': 'Dừng',
'order.amount': 'Số lượng',
'order.price': 'Giá',
'order.total': 'Tổng',
'order.place': 'Đặt lệnh',
'order.placing': 'Đang đặt...',
'order.success': '✅ Đặt lệnh thành công',
'order.loginRequired': '🔒 Cần đăng nhập',
'order.noKey': '⚠️ Chưa cấu hình API key',
'order.balance': 'Số dư: {amount}',
'order.available': 'Khả dụng: {amount}',
// ── Signals ──
'sig.title': '📡 Tín hiệu trực tiếp',
'sig.strongBuy': '🚀 MUA MẠNH',
'sig.buy': '📈 MUA',
'sig.strongSell': '🔻 BÁN MẠNH',
'sig.sell': '📉 BÁN',
'sig.cautionLong': '⚠️ THẬN TRỌNG (LONG)',
'sig.cautionShort': '⚠️ THẬN TRỌNG (SHORT)',
'sig.squeeze': '⚡ SQUEEZE ALERT',
'sig.noSignal': 'Chưa có tín hiệu — đang chờ nến mới...',
'sig.loading': 'Đang tải tín hiệu...',
'sig.price': 'Giá',
'sig.rsi': 'RSI',
'sig.bb': 'BB',
'sig.signal': 'Tín hiệu',
'sig.time': 'Thời gian',
// ── Profile ──
'profile.tabs.info': 'ℹ️ Thông tin',
'profile.tabs.keys': '🔑 API Keys',
'profile.tabs.history': '📜 Lịch sử',
'profile.tabs.prefs': '⚙️ Cài đặt',
'profile.tabs.sessions': '🔐 Phiên đăng nhập',
'profile.info.title': 'Thông tin cá nhân',
'profile.info.email': 'Email',
'profile.info.displayName': 'Tên hiển thị',
'profile.info.save': 'Lưu thông tin',
'profile.info.saved': '✅ Đã lưu thông tin ✓',
'profile.info.error': 'Lỗi lưu thông tin',
'profile.password.title': 'Đổi mật khẩu',
'profile.password.old': 'Mật khẩu hiện tại',
'profile.password.new': 'Mật khẩu mới',
'profile.password.confirm': 'Xác nhận mật khẩu',
'profile.password.btn': 'Đổi mật khẩu',
'profile.password.validLength': 'Tối thiểu 8 ký tự',
'profile.password.validNumber': 'Cần ít nhất 1 số',
'profile.password.validSpecial': 'Cần ít nhất 1 ký tự đặc biệt',
'profile.password.fillAll': 'Điền đầy đủ thông tin',
'profile.password.success': '✅ Đổi mật khẩu thành công ✓',
'profile.keys.title': 'API Keys',
'profile.keys.add': '+ Thêm Key',
'profile.keys.edit': '✏️',
'profile.keys.delete': '🗑️',
'profile.keys.test': 'Kiểm tra',
'profile.keys.empty': 'Chưa có API key nào',
'profile.keys.exchange': 'Sàn',
'profile.keys.key': 'API Key',
'profile.keys.secret': 'Secret Key',
'profile.keys.passphrase': 'Passphrase',
'profile.keys.saving': 'Đang lưu...',
'profile.keys.addTitle': 'Thêm API Key',
'profile.keys.editTitle': 'Sửa API Key',
'profile.keys.deleteConfirm': 'Bạn chắc chắn muốn xoá key này?',
'profile.prefs.title': '⚙️ Cài đặt',
'profile.prefs.exchange': 'Sàn mặc định',
'profile.prefs.timeframe': 'Khung thời gian',
'profile.prefs.theme': 'Giao diện',
'profile.prefs.upColor': 'Màu tăng',
'profile.prefs.downColor': 'Màu giảm',
'profile.prefs.notifSignal': '🔔 Thông báo tín hiệu',
'profile.prefs.notifTrade': '💬 Thực hiện lệnh',
'profile.prefs.notifTradeDesc': 'Gửi thông báo Telegram khi có lệnh thật được đặt',
'profile.prefs.language': 'Ngôn ngữ',
'profile.prefs.tradeSize': '💰 Khối lượng (USDT)',
'profile.prefs.tradeSizeDesc': 'Số USDT dùng cho mỗi lệnh — Signal trades & Auto trade',
'profile.prefs.autoTrade': '🤖 Auto Trade',
'profile.prefs.autoTradeDesc': 'Tự động đặt lệnh Spot mua/bán thật — cần chọn token bên dưới',
'profile.prefs.autoTradeWarning': '⚠️ Auto Trade đang BẬT — hệ thống sẽ tự động đặt lệnh market thật khi có signal',
'profile.prefs.autoTradeTokens': '🪙 Token Auto Trade',
'profile.prefs.autoTradeTokensDesc': 'Chọn token muốn Auto Trade — chỉ những token được chọn mới được auto trade',
'profile.prefs.searchToken': '🔍 Tìm token...',
'profile.prefs.noTokens': '⚠️ Chưa chọn token nào — Auto Trade sẽ không hoạt động',
'profile.prefs.tracking': '📊 Auto Trades đang theo dõi',
'profile.prefs.trackingDesc': '{n} cặp — click ✕ để bỏ theo dõi',
'profile.prefs.trackingPaused': '📊 Auto Trades (tạm dừng)',
'profile.prefs.trackingPausedDesc': 'Auto trade đang tắt — {n} cặp đã lưu',
'profile.prefs.save': '💾 Lưu cài đặt',
'profile.prefs.saved': '✅ Đã lưu cài đặt ✓',
'profile.prefs.spot': 'SPOT',
'profile.sessions.title': 'Phiên đăng nhập',
'profile.sessions.current': '(hiện tại)',
'profile.sessions.revoke': 'Thu hồi',
'profile.sessions.empty': 'Không có phiên nào',
'profile.sessions.browser': 'Trình duyệt',
'profile.sessions.os': 'HĐH',
'profile.sessions.device': 'Thiết bị',
'profile.sessions.ip': 'IP',
'profile.sessions.created': 'Tạo lúc',
'profile.history.signals': '📈 Lệnh tín hiệu',
'profile.history.real': '💹 Lệnh thật',
'profile.history.empty': 'Chưa có lệnh nào',
'profile.history.side': 'Lệnh',
'profile.history.symbol': 'Cặp',
'profile.history.price': 'Giá',
'profile.history.qty': 'SL',
'profile.history.pnl': 'PnL',
'profile.history.time': 'Thời gian',
// ── Signal History ──
'sigHistory.pnl': 'Tổng PnL',
'sigHistory.winRate': 'Tỉ lệ thắng',
'sigHistory.trades': '{n} lệnh',
};
export default vi;