@@ -134,6 +134,7 @@ export class FeederPersistenceService {
|
||||
categoryId: category.dbId,
|
||||
name: sName,
|
||||
oddValue: sValue,
|
||||
openingValue: sValue,
|
||||
position: sPos,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,9 +99,121 @@ export class PredictionSettlementTask {
|
||||
this.logger.log(
|
||||
`Settlement finished: scanned=${scanned} updated=${updated}`,
|
||||
);
|
||||
|
||||
const settled = await this.computeOddsMovementAndCleanup();
|
||||
this.logger.log(
|
||||
`Odds movement: computed=${settled.computed} cleaned=${settled.cleaned}`,
|
||||
);
|
||||
|
||||
return { scanned, updated };
|
||||
}
|
||||
|
||||
private async computeOddsMovementAndCleanup(): Promise<{
|
||||
computed: number;
|
||||
cleaned: number;
|
||||
}> {
|
||||
let computed = 0;
|
||||
let cleaned = 0;
|
||||
|
||||
const finishedMatchIds = await this.prisma.$queryRaw<{ id: string }[]>(
|
||||
Prisma.sql`
|
||||
SELECT DISTINCT oh.match_id AS id
|
||||
FROM odds_history oh
|
||||
JOIN matches m ON m.id = oh.match_id
|
||||
WHERE m.status = 'FT'
|
||||
LIMIT 500
|
||||
`,
|
||||
);
|
||||
|
||||
if (finishedMatchIds.length === 0) return { computed, cleaned };
|
||||
|
||||
const matchIds = finishedMatchIds.map((r) => r.id);
|
||||
|
||||
for (const matchId of matchIds) {
|
||||
try {
|
||||
await this.computeMovementForMatch(matchId);
|
||||
computed++;
|
||||
} catch (e) {
|
||||
this.logger.warn(`Movement calc failed for ${matchId}: ${e}`);
|
||||
}
|
||||
|
||||
await this.prisma.oddsHistory.deleteMany({ where: { matchId } });
|
||||
cleaned++;
|
||||
}
|
||||
|
||||
return { computed, cleaned };
|
||||
}
|
||||
|
||||
private async computeMovementForMatch(matchId: string): Promise<void> {
|
||||
const selections = await this.prisma.$queryRaw<
|
||||
{
|
||||
name: string;
|
||||
category_name: string;
|
||||
opening_value: string | null;
|
||||
odd_value: string | null;
|
||||
}[]
|
||||
>(Prisma.sql`
|
||||
SELECT os.name, oc.name AS category_name,
|
||||
os.opening_value, os.odd_value
|
||||
FROM odd_selections os
|
||||
JOIN odd_categories oc ON oc.db_id = os.odd_category_db_id
|
||||
WHERE oc.match_id = ${matchId}
|
||||
AND os.opening_value IS NOT NULL
|
||||
AND os.odd_value IS NOT NULL
|
||||
`);
|
||||
|
||||
const movements: Record<string, number> = {};
|
||||
|
||||
for (const sel of selections) {
|
||||
const opening = parseFloat(sel.opening_value!);
|
||||
const closing = parseFloat(sel.odd_value!);
|
||||
if (!Number.isFinite(opening) || !Number.isFinite(closing) || opening <= 0)
|
||||
continue;
|
||||
|
||||
const movement = ((closing - opening) / opening) * 100;
|
||||
const cat = (sel.category_name ?? "").toLowerCase();
|
||||
const name = (sel.name ?? "").toLowerCase();
|
||||
|
||||
if (cat.includes("maç sonucu") || cat.includes("mac sonucu") || cat === "ms") {
|
||||
if (name === "1") movements.home = movement;
|
||||
else if (name === "x" || name === "0") movements.draw = movement;
|
||||
else if (name === "2") movements.away = movement;
|
||||
} else if (
|
||||
(cat.includes("2,5") || cat.includes("2.5")) &&
|
||||
(cat.includes("alt/üst") || cat.includes("alt/ust"))
|
||||
) {
|
||||
if (name.includes("üst") || name.includes("ust") || name.includes("over"))
|
||||
movements.o25 = movement;
|
||||
} else if (
|
||||
cat.includes("karşılıklı gol") ||
|
||||
cat.includes("karsilikli gol") ||
|
||||
cat === "kg"
|
||||
) {
|
||||
if (name === "var" || name.includes("yes")) movements.btts = movement;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(movements).length === 0) return;
|
||||
|
||||
const vals = Object.values(movements);
|
||||
const sharpness =
|
||||
vals.length > 0
|
||||
? vals.reduce((sum, v) => sum + Math.abs(v), 0) / vals.length
|
||||
: 0;
|
||||
|
||||
await this.prisma.footballAiFeature.updateMany({
|
||||
where: { matchId },
|
||||
data: {
|
||||
oddsMovementHome: movements.home ?? null,
|
||||
oddsMovementDraw: movements.draw ?? null,
|
||||
oddsMovementAway: movements.away ?? null,
|
||||
oddsMovementO25: movements.o25 ?? null,
|
||||
oddsMovementBtts: movements.btts ?? null,
|
||||
oddsSharpness: sharpness || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private settleRow(
|
||||
row: UnresolvedRow,
|
||||
): { outcome: string; unitProfit: number } | null {
|
||||
|
||||
Reference in New Issue
Block a user