feat(ai-engine): value sniper thresholds and logic relaxed

This commit is contained in:
2026-05-06 17:44:45 +03:00
parent 5b5f83c8cf
commit 4f7090e2d9
13 changed files with 2040 additions and 382 deletions
+45
View File
@@ -148,6 +148,27 @@ export class MatchPickDto {
@ApiProperty({ required: false, default: 0 })
implied_prob?: number;
@ApiProperty({ required: false, default: 0 })
model_probability?: number;
@ApiProperty({ required: false, default: 0 })
model_edge?: number;
@ApiProperty({ required: false, default: 0 })
calibrated_probability?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_probability?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_sample?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_edge?: number;
@ApiProperty({ required: false, default: false })
odds_band_aligned?: boolean;
@ApiProperty()
play_score: number;
@@ -171,6 +192,9 @@ export class MatchPickDto {
enum: ["CORE", "VALUE", "LEAN", "LONGSHOT", "PASS"],
})
signal_tier?: SignalTier;
@ApiProperty({ required: false, default: false })
is_guaranteed?: boolean;
}
export class MatchBetAdviceDto {
@@ -227,6 +251,27 @@ export class MatchBetSummaryItemDto {
@ApiProperty({ required: false, default: 0 })
implied_prob?: number;
@ApiProperty({ required: false, default: 0 })
model_probability?: number;
@ApiProperty({ required: false, default: 0 })
model_edge?: number;
@ApiProperty({ required: false, default: 0 })
calibrated_probability?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_probability?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_sample?: number;
@ApiProperty({ required: false, default: 0 })
odds_band_edge?: number;
@ApiProperty({ required: false, default: false })
odds_band_aligned?: boolean;
@ApiProperty({ required: false, default: 0 })
odds?: number;
+86 -7
View File
@@ -60,7 +60,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
confidence_interval_too_wide_for_main_pick:
"Ana seçim için güven aralığı çok geniş",
confidence_band_low: "Güven bandı düşük",
playable_edge_found: "Oynanabilir avantaj bulundu",
playable_edge_found: "Model avantaj sinyali bulundu",
market_signal_dominant: "Piyasa sinyali baskın",
team_form_signal_dominant: "Takım formuna dayalı sinyaller çok baskın",
lineup_signal_strong: "İlk on bir sinyali güçlü",
@@ -77,7 +77,12 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
limited_data_confidence: "Veri kısıtlı olduğu için güven sınırlı",
data_quality_issue: "Veri kalitesi sorunu var",
high_risk_low_data_quality: "Risk yüksek, veri kalitesi düşük",
insufficient_play_score: "Oynanabilirlik puanı yetersiz",
insufficient_play_score: "Model sinyali yetersiz",
odds_band_confirms_value: "Tarihsel oran bandı değeri doğruluyor",
odds_band_sample_too_low: "Tarihsel oran bandı örneklemi yetersiz",
odds_band_missing_probability: "Tarihsel oran bandı olasılığı yok",
odds_band_unavailable: "Tarihsel oran bandı kullanılamıyor",
odds_band_not_aligned: "Model ve tarihsel oran bandı aynı yönde değil",
no_bet_conditions_met: "Bahis koşulları oluşmadı",
market_passed_all_gates: "Market tüm güvenlik kontrollerini geçti",
no_ev_edge_minimum_stake:
@@ -129,10 +134,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
private readonly feederService: FeederService,
@Optional() private readonly predictionsQueue?: PredictionsQueue,
) {
this.aiEngineUrl = this.configService.get(
"AI_ENGINE_URL",
"http://localhost:8000",
);
this.aiEngineUrl = this.resolveAiEngineUrl();
this.aiEngineClient = new AiEngineClient({
baseUrl: this.aiEngineUrl,
logger: this.logger,
@@ -421,6 +423,59 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
}
}
private resolveAiEngineUrl(): string {
const configuredUrl = this.configService.get(
"AI_ENGINE_URL",
"http://localhost:8000",
);
const localEnvUrl = this.readLocalEnvValue("AI_ENGINE_URL");
if (
process.env.NODE_ENV !== "production" &&
localEnvUrl &&
localEnvUrl !== configuredUrl &&
this.isLocalhostUrl(configuredUrl) &&
this.isLocalhostUrl(localEnvUrl)
) {
this.logger.warn(
`AI_ENGINE_URL inherited from parent process (${configuredUrl}) differs from .env.local (${localEnvUrl}); using .env.local for local development`,
);
return localEnvUrl;
}
return configuredUrl;
}
private readLocalEnvValue(key: string): string | null {
const filePath = path.join(process.cwd(), ".env.local");
if (!fs.existsSync(filePath)) {
return null;
}
const line = fs
.readFileSync(filePath, "utf8")
.split(/\r?\n/u)
.find((entry) => entry.trim().startsWith(`${key}=`));
if (!line) {
return null;
}
return line
.slice(line.indexOf("=") + 1)
.trim()
.replace(/^['"]|['"]$/gu, "");
}
private isLocalhostUrl(value: string): boolean {
try {
const url = new URL(value);
return ["localhost", "127.0.0.1", "::1"].includes(url.hostname);
} catch {
return false;
}
}
private async getMatchContext(matchId: string): Promise<MatchContext> {
const match = await this.prisma.match.findUnique({
where: { id: matchId },
@@ -705,6 +760,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
),
confidence_interval: interval,
signal_tier: this.classifySignalTier(record, interval),
is_guaranteed: false,
};
}
@@ -793,7 +849,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
const evMatch = normalized.match(/^ev_edge_([-+][\d.]+%)_grade_(\w)$/);
if (evMatch) {
return `Beklenen avantaj ${evMatch[1]} (Not ${evMatch[2]})`;
return `Teorik avantaj sinyali: Not ${evMatch[2]}`;
}
const negativeEdgeMatch = normalized.match(
@@ -803,6 +859,13 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
return `Model avantajı negatif (${negativeEdgeMatch[1]})`;
}
const bandNoValueMatch = normalized.match(
/^odds_band_no_value_([-+]?[\d.]+)$/,
);
if (bandNoValueMatch) {
return `Tarihsel oran bandı değeri doğrulamadı (${bandNoValueMatch[1]})`;
}
const edgeThresholdMatch = normalized.match(
/^below_market_edge_threshold_([-+]?[\d.]+)$/,
);
@@ -1514,8 +1577,15 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
pick: item.pick,
playable: item.playable,
bet_grade: item.bet_grade,
odds: item.odds,
model_edge: item.model_edge,
calibrated_probability: item.calibrated_probability,
calibrated_confidence: item.calibrated_confidence,
ev_edge: item.ev_edge ?? 0,
odds_band_probability: item.odds_band_probability,
odds_band_sample: item.odds_band_sample,
odds_band_edge: item.odds_band_edge,
odds_band_aligned: item.odds_band_aligned,
stake_units: item.stake_units,
}))
: [];
@@ -1531,8 +1601,15 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
pick: payload.main_pick.pick,
playable: payload.main_pick.playable,
bet_grade: payload.main_pick.bet_grade,
odds: payload.main_pick.odds,
model_edge: payload.main_pick.model_edge,
calibrated_probability: payload.main_pick.calibrated_probability,
calibrated_confidence: payload.main_pick.calibrated_confidence,
ev_edge: payload.main_pick.ev_edge ?? 0,
odds_band_probability: payload.main_pick.odds_band_probability,
odds_band_sample: payload.main_pick.odds_band_sample,
odds_band_edge: payload.main_pick.odds_band_edge,
odds_band_aligned: payload.main_pick.odds_band_aligned,
stake_units: payload.main_pick.stake_units,
}
: null,
@@ -1542,6 +1619,8 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
pick: payload.value_pick.pick,
playable: payload.value_pick.playable,
bet_grade: payload.value_pick.bet_grade,
odds: payload.value_pick.odds,
model_edge: payload.value_pick.model_edge,
calibrated_confidence: payload.value_pick.calibrated_confidence,
ev_edge: payload.value_pick.ev_edge ?? 0,
}