update forecast ai page

This commit is contained in:
MuslemRahimi 2025-03-13 10:41:07 +01:00
parent 57fa0224bf
commit fa70a10672
2 changed files with 158 additions and 113 deletions

View File

@ -41,10 +41,10 @@
AI Score Definition
</h3>
<div class=" p-2">
Revenue, also called sales, is the amount of money a company
receives from its business activities, such as sales of products
or services. Revenue does not take any expenses into account and
is therefore different from profits.
The AI Score uses fundamental and technical indicators, along
with statistical data, to predict if a stock will be bullish in
the next quarter. It ranges from 1 to 10, with higher scores
indicating a greater likelihood of the prediction being correct.
</div>
<!--
<div class="px-2">
@ -67,10 +67,10 @@
AI Trend Forecast Definition
</h3>
<div class=" p-2">
Revenue, also called sales, is the amount of money a company
receives from its business activities, such as sales of products
or services. Revenue does not take any expenses into account and
is therefore different from profits.
The AI Trend Model forecasts stock trends by analyzing
historical price data. It detects patterns, seasonality, and
trends to predict future price movements, making it useful for
predicting price targets for the next 12 months.
</div>
<!--
<div class="px-2">

View File

@ -16,128 +16,172 @@
return `Q${quarter} '${year}`;
};
const price = data?.getStockQuote?.price?.toFixed(2) || 0;
const calculatePriceChange = (targetPrice) =>
targetPrice && price ? ((targetPrice / price - 1) * 100)?.toFixed(2) : 0;
const avgPriceTarget = data?.getPriceAnalysis?.avgPriceTarget || 0;
const medianPriceTarget = data?.getPriceAnalysis?.medianPriceTarget || 0;
const lowPriceTarget = data?.getPriceAnalysis?.lowPriceTarget || 0;
const highPriceTarget = data?.getPriceAnalysis?.highPriceTarget || 0;
function prepareDataset() {
price = data?.getStockQuote?.price?.toFixed(2) || 0;
const lowChange = calculatePriceChange(lowPriceTarget);
const medianChange = calculatePriceChange(medianPriceTarget);
const avgChange = calculatePriceChange(avgPriceTarget);
const highChange = calculatePriceChange(highPriceTarget);
avgPriceTarget = data?.getPriceAnalysis?.avgPriceTarget || 0;
medianPriceTarget = data?.getPriceAnalysis?.medianPriceTarget || 0;
lowPriceTarget = data?.getPriceAnalysis?.lowPriceTarget || 0;
highPriceTarget = data?.getPriceAnalysis?.highPriceTarget || 0;
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
const historicalData = data?.getHistoricalPrice || [];
lowChange = calculatePriceChange(lowPriceTarget);
medianChange = calculatePriceChange(medianPriceTarget);
avgChange = calculatePriceChange(avgPriceTarget);
highChange = calculatePriceChange(highPriceTarget);
const backtestList = data?.getAIScore?.backtest || [];
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
historicalData = data?.getHistoricalPrice || [];
// Append the latest historical date (using "time") if available. Note that this entry may not include a score.
if (historicalData && historicalData.length) {
const latest = historicalData.at(-1);
backtestList.push({ date: latest.time });
backtestList = data?.getAIScore?.backtest || [];
// Append the latest historical date (using "time") if available. Note that this entry may not include a score.
if (historicalData && historicalData?.length) {
const latest = historicalData?.at(-1);
backtestList?.push({ date: latest.time });
const seenDates = new Set();
backtestList = backtestList?.filter((item) => {
// Check if the date is already seen
if (seenDates?.has(item?.date)) {
// If yes, skip this duplicate (delete the last one)
return false;
}
// Otherwise, record the date and keep the item
seenDates?.add(item?.date);
return true;
});
}
processedData = backtestList?.map((item) => {
const dateStr = item.date;
const targetTime = new Date(dateStr).getTime();
let closestPoint = historicalData[0];
let minDiff = Infinity;
historicalData.forEach((point) => {
const pointTime = new Date(point.time).getTime();
const diff = Math.abs(pointTime - targetTime);
if (diff < minDiff) {
minDiff = diff;
closestPoint = point;
}
});
// Base data point with x as the target date timestamp and y as the close price from the closest historical point
const dataPoint = { x: targetTime, y: closestPoint.close };
// If a score is provided, add marker configuration based on its value.
if (item.hasOwnProperty("score")) {
let markerColor, markerSymbol;
if (item.score > 6) {
// Bullish: green marker with an upward triangle
markerColor = "#2ecc71";
markerSymbol = "triangle-up";
} else if (item.score < 5) {
// Bearish: red marker with a downward triangle
markerColor = "#e74c3c";
markerSymbol = "triangle-down";
} else {
// Neutral (score exactly 5): yellow marker with a circle
markerColor = "#f1c40f";
markerSymbol = "circle";
}
dataPoint.marker = {
symbol: markerSymbol,
radius: 4,
fillColor: markerColor,
lineWidth: 2,
lineColor: $mode === "light" ? "black" : "white",
};
dataPoint.dataLabels = {
enabled: true,
format: String(item.score),
style: {
color: $mode === "light" ? "black" : "white",
fontWeight: "bold",
fontSize: "14px",
},
y: -10,
};
}
return dataPoint;
});
tableDates = processedData
?.slice(0, -1)
?.map((item) => formatDateToQuarter(item?.x));
tableScore = processedData
?.slice(0, -1)
?.map((item) => item?.dataLabels?.format);
// Compute percentage change
tableQuarterChange = processedData
?.slice(0, -1)
?.map((item, index, arr) => {
const prevY = arr[index - 1]?.y; // Get the previous value
if (prevY == null || item.y == null) return null; // Handle missing values
const change = ((item.y - prevY) / prevY) * 100; // Calculate percentage change
return {
quarter: tableDates[index],
change: Number(change?.toFixed(2)), // Format to 2 decimal places
};
})
?.filter(Boolean); // Remove null values
// Compute Average Return
returns = processedData
?.slice(1) // Skip the first value since there's no previous value for it
?.map((item, index) => {
const prevY = processedData[index]?.y;
if (prevY == null || item.y == null) return null;
const returnPercentage = ((item.y - prevY) / prevY) * 100;
return returnPercentage;
})
.filter(Boolean); // Remove null values
avgReturn =
returns?.reduce((sum, returnPercentage) => sum + returnPercentage, 0) /
returns?.length;
}
let price;
let avgPriceTarget;
let medianPriceTarget;
let lowPriceTarget;
let highPriceTarget;
let lowChange;
let medianChange;
let avgChange;
let highChange;
let historicalData;
let backtestList;
// For each backtest entry, find the historical price with the closest available time.
// Then, if a score exists, attach a marker and data label based on the score.
const processedData = backtestList?.map((item) => {
const dateStr = item.date;
const targetTime = new Date(dateStr).getTime();
let closestPoint = historicalData[0];
let minDiff = Infinity;
let processedData = [];
historicalData.forEach((point) => {
const pointTime = new Date(point.time).getTime();
const diff = Math.abs(pointTime - targetTime);
if (diff < minDiff) {
minDiff = diff;
closestPoint = point;
}
});
let tableDates;
// Base data point with x as the target date timestamp and y as the close price from the closest historical point
const dataPoint = { x: targetTime, y: closestPoint.close };
// If a score is provided, add marker configuration based on its value.
if (item.hasOwnProperty("score")) {
let markerColor, markerSymbol;
if (item.score > 6) {
// Bullish: green marker with an upward triangle
markerColor = "#2ecc71";
markerSymbol = "triangle-up";
} else if (item.score < 5) {
// Bearish: red marker with a downward triangle
markerColor = "#e74c3c";
markerSymbol = "triangle-down";
} else {
// Neutral (score exactly 5): yellow marker with a circle
markerColor = "#f1c40f";
markerSymbol = "circle";
}
dataPoint.marker = {
symbol: markerSymbol,
radius: 4,
fillColor: markerColor,
lineWidth: 2,
lineColor: $mode === "light" ? "black" : "white",
};
dataPoint.dataLabels = {
enabled: true,
format: String(item.score),
style: {
color: $mode === "light" ? "black" : "white",
fontWeight: "bold",
fontSize: "14px",
},
y: -10,
};
}
return dataPoint;
});
const tableDates = processedData
?.slice(0, -1)
?.map((item) => formatDateToQuarter(item.x));
const tableScore = processedData
?.slice(0, -1)
?.map((item) => item?.dataLabels?.format);
let tableScore;
// Compute percentage change
const tableQuarterChange = processedData
?.slice(0, -1)
.map((item, index, arr) => {
const prevY = arr[index - 1]?.y; // Get the previous value
if (prevY == null || item.y == null) return null; // Handle missing values
const change = ((item.y - prevY) / prevY) * 100; // Calculate percentage change
return {
quarter: tableDates[index],
change: Number(change?.toFixed(2)), // Format to 2 decimal places
};
})
?.filter(Boolean); // Remove null values
let tableQuarterChange;
// Compute Average Return
const returns = processedData
?.slice(1) // Skip the first value since there's no previous value for it
?.map((item, index) => {
const prevY = processedData[index]?.y;
if (prevY == null || item.y == null) return null;
const returnPercentage = ((item.y - prevY) / prevY) * 100;
return returnPercentage;
})
.filter(Boolean); // Remove null values
let returns;
const avgReturn =
returns?.reduce((sum, returnPercentage) => sum + returnPercentage, 0) /
returns?.length;
let avgReturn;
function getAIScorePlot() {
const solidData = processedData.slice(0, -1);
@ -422,7 +466,8 @@
let configScore = null;
$: {
if ($mode) {
if ($stockTicker || $mode) {
prepareDataset();
configScore = getAIScorePlot() || null;
config = getPriceForecastChart() || null;
}