Spaces:
Runtime error
Runtime error
// ...existing code from old app.js, adjusted for new path... | |
// Sử dụng fetch tới /api/stock_data ở cùng domain Flask | |
async function fetchStockData(symbol) { | |
try { | |
const response = await fetch(`/api/stock_data?symbol=${symbol}`); | |
const data = await response.json(); | |
if (!Array.isArray(data) || data.length === 0) { | |
alert('Không có dữ liệu cho mã cổ phiếu này!'); | |
return; | |
} | |
processStockData(data); | |
} catch (error) { | |
alert('Lỗi khi lấy dữ liệu từ server: ' + error); | |
} | |
} | |
function processStockData(data) { | |
// Chuẩn hóa dữ liệu cho các hàm chỉ báo | |
const priceArr = data.map(d => d.price); | |
const volumeArr = data.map(d => d.volume); | |
const pv = calculatePV(priceArr, volumeArr); | |
const mfi = calculateMFI(data, volumeArr); | |
const vsaAnalysis = analyzeVSA(priceArr, volumeArr); | |
// Vẽ biểu đồ | |
chart.renderPriceVolumeChart({labels: priceArr.map((_,i)=>i+1), values: priceArr}, {labels: volumeArr.map((_,i)=>i+1), values: volumeArr}); | |
chart.renderPVChart({labels: pv.map((_,i)=>i+1), values: pv}); | |
chart.renderMFIChart({labels: mfi.map((_,i)=>i+1), values: mfi}); | |
displayAnalysisResults(vsaAnalysis); | |
} | |
function analyzeVSA(priceArr, volumeArr) { | |
// Phân tích VSA cơ bản: breakout, volume spike, điểm mua/bán, cắt lỗ, chốt lời | |
if (priceArr.length < 20) return { message: 'Không đủ dữ liệu để phân tích VSA.' }; | |
const n = priceArr.length; | |
// Tìm breakout: giá vượt đỉnh 20 phiên gần nhất kèm volume tăng mạnh | |
let breakoutIdx = -1; | |
let max20 = -Infinity; | |
for (let i = 19; i < n; i++) { | |
max20 = Math.max(...priceArr.slice(i-19, i)); | |
if (priceArr[i] > max20 && volumeArr[i] > 1.5 * avg(volumeArr.slice(i-19, i))) { | |
breakoutIdx = i; | |
break; | |
} | |
} | |
// Điểm mua: breakout đầu tiên | |
let buyPoint = breakoutIdx !== -1 ? priceArr[breakoutIdx] : null; | |
let buyIdx = breakoutIdx; | |
// Cắt lỗ: 5% dưới điểm mua | |
let stopLoss = buyPoint ? +(buyPoint * 0.95).toFixed(2) : null; | |
// Chốt lời: 15% trên điểm mua | |
let takeProfit = buyPoint ? +(buyPoint * 1.15).toFixed(2) : null; | |
// Tìm điểm bán: khi giá giảm thủng MA10 sau breakout | |
let sellIdx = null; | |
let ma10 = movingAvg(priceArr, 10); | |
if (buyIdx !== -1) { | |
for (let i = buyIdx + 1; i < n; i++) { | |
if (priceArr[i] < ma10[i]) { | |
sellIdx = i; | |
break; | |
} | |
} | |
} | |
let sellPoint = sellIdx !== null ? priceArr[sellIdx] : null; | |
// Một số chỉ báo VSA đơn giản | |
let volumeSpike = []; | |
let avgVol = avg(volumeArr); | |
for (let i = 1; i < n; i++) { | |
if (volumeArr[i] > 2 * avgVol && priceArr[i] > priceArr[i-1]) { | |
volumeSpike.push(i); | |
} | |
} | |
return { | |
buyPoint, buyIdx, stopLoss, takeProfit, sellPoint, sellIdx, | |
volumeSpike, | |
breakoutIdx, | |
message: null | |
}; | |
} | |
function avg(arr) { | |
if (!arr.length) return 0; | |
return arr.reduce((a, b) => a + b, 0) / arr.length; | |
} | |
function movingAvg(arr, window) { | |
let res = []; | |
for (let i = 0; i < arr.length; i++) { | |
if (i < window - 1) res.push(null); | |
else res.push(avg(arr.slice(i - window + 1, i + 1))); | |
} | |
return res; | |
} | |
function displayAnalysisResults(vsa) { | |
const el = document.getElementById('analysis-results'); | |
if (!vsa || vsa.message) { | |
el.innerHTML = `<div class='vsa-message'>${vsa ? vsa.message : 'Không có dữ liệu VSA.'}</div>`; | |
return; | |
} | |
let html = `<h3>Kết quả phân tích VSA</h3>`; | |
if (vsa.buyPoint) html += `<div><b>Điểm mua (breakout):</b> ${vsa.buyPoint} (phiên ${vsa.buyIdx+1})</div>`; | |
if (vsa.stopLoss) html += `<div><b>Cắt lỗ:</b> ${vsa.stopLoss}</div>`; | |
if (vsa.takeProfit) html += `<div><b>Chốt lời:</b> ${vsa.takeProfit}</div>`; | |
if (vsa.sellPoint) html += `<div><b>Điểm bán (thủng MA10):</b> ${vsa.sellPoint} (phiên ${vsa.sellIdx+1})</div>`; | |
if (vsa.volumeSpike && vsa.volumeSpike.length) | |
html += `<div><b>Volume spike tăng giá:</b> ${vsa.volumeSpike.map(i=>`phiên ${i+1}`).join(', ')}</div>`; | |
if (vsa.breakoutIdx !== -1) | |
html += `<div><b>Breakout xác nhận tại phiên:</b> ${vsa.breakoutIdx+1}</div>`; | |
el.innerHTML = html; | |
} | |
document.getElementById('analyze-button').onclick = function() { | |
const symbol = document.getElementById('stock-symbol').value.trim().toUpperCase(); | |
if (!symbol) { | |
alert('Vui lòng nhập mã cổ phiếu!'); | |
return; | |
} | |
fetchStockData(symbol); | |
}; | |
// ...existing code... | |