danghungithp's picture
Upload 1398 files
bec48e1 verified
// ...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...