249 lines
9.3 KiB
JavaScript
249 lines
9.3 KiB
JavaScript
// === ГЛОБАЛЬНЫЕ ДАННЫЕ ===
|
|
let currentPair = 'EURUSD';
|
|
let currentTimeframe = '15';
|
|
let tradingWidget = null;
|
|
|
|
// === НОВОСТИ (учебные данные) ===
|
|
const newsData = [
|
|
{ id: 1, title: 'Решение по процентной ставке ФРС (США)', impact: 'high', currency: 'USD', time: '14:00', actual: '', forecast: '5.50%', previous: '5.50%' },
|
|
{ id: 2, title: 'Инфляция CPI (Еврозона)', impact: 'high', currency: 'EUR', time: '12:00', actual: '2.4%', forecast: '2.3%', previous: '2.6%' },
|
|
{ id: 3, title: 'ВВП Великобритании (квартал)', impact: 'high', currency: 'GBP', time: '09:30', actual: '0.2%', forecast: '0.2%', previous: '0.1%' },
|
|
{ id: 4, title: 'Запасы сырой нефти (США)', impact: 'medium', currency: 'USD', time: '17:30', actual: '-2.5M', forecast: '-1.0M', previous: '+1.2M' },
|
|
{ id: 5, title: 'Индекс потребительских настроений (США)', impact: 'medium', currency: 'USD', time: '16:00', actual: '69.5', forecast: '68.0', previous: '67.9' },
|
|
{ id: 6, title: 'Розничные продажи (Япония)', impact: 'low', currency: 'JPY', time: '08:30', actual: '1.8%', forecast: '1.5%', previous: '1.2%' },
|
|
{ id: 7, title: 'Индекс деловой активности PMI (Германия)', impact: 'medium', currency: 'EUR', time: '11:00', actual: '47.5', forecast: '48.0', previous: '47.8' },
|
|
{ id: 8, title: 'Заявки на пособие по безработице (США)', impact: 'medium', currency: 'USD', time: '15:30', actual: '220K', forecast: '215K', previous: '218K' },
|
|
{ id: 9, title: 'Баланс торгового счёта (Китай)', impact: 'high', currency: 'CNY', time: '05:00', actual: '+75.0B', forecast: '+70.0B', previous: '+68.5B' },
|
|
{ id: 10, title: 'Индекс цен на недвижимость (Швейцария)', impact: 'low', currency: 'CHF', time: '10:00', actual: '0.3%', forecast: '0.2%', previous: '0.1%' }
|
|
];
|
|
|
|
// === СИГНАЛЫ (учебные) ===
|
|
const signalsData = [
|
|
{ pair: 'EUR/USD', direction: 'buy', entry: 1.0850, sl: 1.0830, tp: 1.0890, rsi: 35, ma: 'Bullish', time: '10:45' },
|
|
{ pair: 'GBP/USD', direction: 'sell', entry: 1.2680, sl: 1.2710, tp: 1.2620, rsi: 68, ma: 'Bearish', time: '10:30' },
|
|
{ pair: 'USD/JPY', direction: 'hold', entry: 149.50, sl: 149.20, tp: 150.10, rsi: 52, ma: 'Neutral', time: '10:15' },
|
|
{ pair: 'XAU/USD', direction: 'buy', entry: 2035.00, sl: 2025.00, tp: 2055.00, rsi: 42, ma: 'Bullish', time: '09:50' },
|
|
{ pair: 'BTC/USD', direction: 'sell', entry: 42500, sl: 43200, tp: 41100, rsi: 72, ma: 'Bearish', time: '09:20' }
|
|
];
|
|
|
|
// === ИНИЦИАЛИЗАЦИЯ ===
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initTabs();
|
|
initCharts();
|
|
initCalculator();
|
|
initNews();
|
|
initSignals();
|
|
});
|
|
|
|
// === ВКЛАДКИ ===
|
|
function initTabs() {
|
|
const tabs = document.querySelectorAll('.tab');
|
|
const contents = document.querySelectorAll('.tab-content');
|
|
|
|
tabs.forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
const target = tab.dataset.tab;
|
|
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
contents.forEach(c => c.classList.remove('active'));
|
|
|
|
tab.classList.add('active');
|
|
document.getElementById(target).classList.add('active');
|
|
|
|
// Перерисовать график если перешли на вкладку
|
|
if (target === 'charts' && tradingWidget) {
|
|
loadWidget(currentPair, currentTimeframe);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// === ГРАФИКИ (TradingView) ===
|
|
function initCharts() {
|
|
const pairBtns = document.querySelectorAll('.pair-btn');
|
|
const tfBtns = document.querySelectorAll('.tf-btn');
|
|
|
|
// Загрузить первый график
|
|
loadWidget('EURUSD', '15');
|
|
|
|
pairBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
pairBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
currentPair = btn.dataset.pair;
|
|
loadWidget(currentPair, currentTimeframe);
|
|
});
|
|
});
|
|
|
|
tfBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
tfBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
currentTimeframe = btn.dataset.tf;
|
|
loadWidget(currentPair, currentTimeframe);
|
|
});
|
|
});
|
|
}
|
|
|
|
function loadWidget(pair, timeframe) {
|
|
const container = document.getElementById('tradingview-widget');
|
|
container.innerHTML = '';
|
|
|
|
const intervalMap = {
|
|
'1': '1',
|
|
'5': '5',
|
|
'15': '15',
|
|
'60': '60',
|
|
'D': 'D'
|
|
};
|
|
|
|
const symbolMap = {
|
|
'EURUSD': 'FX:EURUSD',
|
|
'GBPUSD': 'FX:GBPUSD',
|
|
'USDJPY': 'FX:USDJPY',
|
|
'XAUUSD': 'OANDA:XAUUSD',
|
|
'BTCUSD': 'COINBASE:BTCUSD'
|
|
};
|
|
|
|
tradingWidget = new TradingView.widget({
|
|
width: '100%',
|
|
height: 500,
|
|
symbol: symbolMap[pair] || 'FX:EURUSD',
|
|
interval: intervalMap[timeframe] || '15',
|
|
timezone: 'Asia/Almaty',
|
|
theme: 'dark',
|
|
style: '1',
|
|
locale: 'ru',
|
|
toolbar_bg: '#f1f3f6',
|
|
enable_publishing: false,
|
|
allow_symbol_change: true,
|
|
container_id: 'tradingview-widget',
|
|
hide_side_toolbar: false,
|
|
details: true,
|
|
hotlist: true,
|
|
calendar: true,
|
|
withdateranges: true,
|
|
range: '1D'
|
|
});
|
|
}
|
|
|
|
// === КАЛЬКУЛЯТОР ===
|
|
function initCalculator() {
|
|
const calcBtn = document.getElementById('calculate-btn');
|
|
calcBtn.addEventListener('click', calculateRisk);
|
|
}
|
|
|
|
function calculateRisk() {
|
|
const deposit = parseFloat(document.getElementById('deposit').value) || 100;
|
|
const riskPercent = parseFloat(document.getElementById('risk-percent').value) || 2;
|
|
const pair = document.getElementById('pair-select').value;
|
|
const entryPrice = parseFloat(document.getElementById('entry-price').value);
|
|
const stopLoss = parseFloat(document.getElementById('stop-loss').value);
|
|
|
|
// Риск в долларах
|
|
const riskAmount = deposit * (riskPercent / 100);
|
|
document.getElementById('risk-amount').textContent = '$' + riskAmount.toFixed(2);
|
|
|
|
// Расчёт лота (упрощённо для стандарта)
|
|
let pipValue = 10; // Стандартный лот для EUR/USD
|
|
if (pair === 'USDJPY') pipValue = 6.7;
|
|
if (pair === 'XAUUSD') pipValue = 100;
|
|
if (pair === 'BTCUSD') pipValue = 1;
|
|
|
|
const lotSize = stopLoss > 0 ? (riskAmount / (stopLoss * pipValue)).toFixed(2) : '0.01';
|
|
document.getElementById('lot-size').textContent = lotSize;
|
|
|
|
// Тейк-профит 1:2
|
|
const tp = stopLoss * 2;
|
|
document.getElementById('take-profit').textContent = '+' + tp + ' пунктов';
|
|
|
|
// Прибыль
|
|
const profit = riskAmount * 2;
|
|
const profitEl = document.getElementById('profit-amount');
|
|
profitEl.textContent = '+$' + profit.toFixed(2);
|
|
profitEl.className = 'result-value profit';
|
|
|
|
// Соотношение
|
|
document.getElementById('rr-ratio').textContent = '1:2';
|
|
}
|
|
|
|
// === НОВОСТИ ===
|
|
function initNews() {
|
|
const filters = document.querySelectorAll('.news-filter');
|
|
|
|
renderNews('all');
|
|
|
|
filters.forEach(filter => {
|
|
filter.addEventListener('click', () => {
|
|
filters.forEach(f => f.classList.remove('active'));
|
|
filter.classList.add('active');
|
|
renderNews(filter.dataset.impact);
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderNews(filter) {
|
|
const container = document.getElementById('news-list');
|
|
const filtered = filter === 'all' ? newsData : newsData.filter(n => n.impact === filter);
|
|
|
|
container.innerHTML = filtered.map(news => `
|
|
<div class="news-item ${news.impact}">
|
|
<div class="news-impact">${news.impact === 'high' ? '🔴' : news.impact === 'medium' ? '🟡' : '🟢'}</div>
|
|
<div class="news-content">
|
|
<div class="news-title">${news.title}</div>
|
|
<div class="news-meta">
|
|
${news.currency} • ${news.time} •
|
|
Факт: ${news.actual || '—'} • Прогноз: ${news.forecast} • Пред: ${news.previous}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// === СИГНАЛЫ ===
|
|
function initSignals() {
|
|
renderSignals();
|
|
}
|
|
|
|
function renderSignals() {
|
|
const tbody = document.getElementById('signals-body');
|
|
|
|
tbody.innerHTML = signalsData.map(signal => `
|
|
<tr>
|
|
<td><strong>${signal.pair}</strong></td>
|
|
<td>${getDirectionBadge(signal.direction)}</td>
|
|
<td>${signal.entry}</td>
|
|
<td style="color: var(--accent-red)">${signal.sl}</td>
|
|
<td style="color: var(--accent-green)">${signal.tp}</td>
|
|
<td>${getRSIColor(signal.rsi)}</td>
|
|
<td>${getMABadge(signal.ma)}</td>
|
|
<td>${signal.time}</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
function getDirectionBadge(dir) {
|
|
const map = {
|
|
'buy': '<span style="color: var(--accent-green)">📈 Покупка</span>',
|
|
'sell': '<span style="color: var(--accent-red)">📉 Продажа</span>',
|
|
'hold': '<span style="color: var(--text-secondary)">⏸️ Флэт</span>'
|
|
};
|
|
return map[dir] || dir;
|
|
}
|
|
|
|
function getRSIColor(rsi) {
|
|
let color = 'var(--text-primary)';
|
|
if (rsi < 30) color = 'var(--accent-green)';
|
|
if (rsi > 70) color = 'var(--accent-red)';
|
|
return `<span style="color: ${color}">${rsi}</span>`;
|
|
}
|
|
|
|
function getMABadge(ma) {
|
|
const map = {
|
|
'Bullish': '<span style="color: var(--accent-green)">▲ Бычий</span>',
|
|
'Bearish': '<span style="color: var(--accent-red)">▼ Медвежий</span>',
|
|
'Neutral': '<span style="color: var(--text-secondary)">▬ Флэт</span>'
|
|
};
|
|
return map[ma] || ma;
|
|
}
|