feat(ai-engine): value sniper thresholds and logic relaxed
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user