60 Commits

Author SHA1 Message Date
fahricansecer 15c6313246 Merge branch 'main' of https://gitea.bilgich.com/fahricansecer/iddaai-be
Deploy Iddaai Backend / build-and-deploy (push) Successful in 54s
2026-05-24 02:44:52 +03:00
fahricansecer 1b420a425e Update .gitignore 2026-05-24 02:43:10 +03:00
fahricansecer 55e62d8fe5 .gitea/workflows/deploy.yml Güncelle
Deploy Iddaai Backend / build-and-deploy (push) Successful in 4m56s
2026-05-24 02:30:14 +03:00
fahricansecer 21e05148c8 feat: league tier system + retrained V25 models (48 quality leagues)
Deploy Iddaai Backend / build-and-deploy (push) Failing after 3m56s
- Add LeagueTier DB model and Prisma schema
- Add league-tiers service (CRUD, sync, retrain trigger)
- Add league-tiers controller with admin API endpoints
- Add /v1/admin/retrain endpoint in AI engine (extract→train→reload pipeline)
- Retrain V25 Pro with 48 quality leagues (MS accuracy: 26.9%→51.4%)
- Update qualified_leagues.json (443→48 leagues)
- Include V25 model files in repo for Docker deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-20 21:57:15 +03:00
fahricansecer e001ce9ab5 fix: guarantee iddaai-ai-engine network alias on every deploy
Deploy Iddaai Backend / build-and-deploy (push) Successful in 29s
2026-05-20 10:40:00 +03:00
fahricansecer 9481ad7094 changes
Deploy Iddaai Backend / build-and-deploy (push) Successful in 42s
2026-05-20 10:10:28 +03:00
fahricansecer 1d4aa36602 gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 31s
2026-05-18 00:08:50 +03:00
fahricansecer 5574a3c59d feat: separate commentary endpoint - non-blocking Ollama
Deploy Iddaai Backend / build-and-deploy (push) Successful in 30s
2026-05-17 16:47:05 +03:00
fahricansecer 94c7a4481a main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s
2026-05-17 02:17:22 +03:00
fahricansecer 17ace9bd12 feat: Ollama AI expert commentary integration
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s
- OllamaClient utility for llama3.2:3b API calls (timeout 30s, non-fatal)
- OllamaCommentary service builds structured Turkish prompt from prediction data
- PredictionsService enriches response with ai_expert_commentary field
- Frontend prediction-card displays AI commentary section above match_commentary
2026-05-17 02:09:04 +03:00
fahricansecer 2b87669f41 gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 31s
2026-05-13 16:56:14 +03:00
fahricansecer 2507678bc0 gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 32s
2026-05-12 17:41:49 +03:00
fahricansecer 2b8dce665f gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 1m8s
2026-05-12 03:06:54 +03:00
fahricansecer b6d64b59bf main
Deploy Iddaai Backend / build-and-deploy (push) Failing after 2m6s
2026-05-12 02:43:02 +03:00
fahricansecer f8599bdb9a gg
Deploy Iddaai Backend / build-and-deploy (push) Failing after 2m1s
2026-05-11 23:11:41 +03:00
fahricansecer 4dcc4ced50 gg
Deploy Iddaai Backend / build-and-deploy (push) Failing after 2m15s
2026-05-11 20:50:31 +03:00
fahricansecer 70fdc066c7 Merge branch 'v28'
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s
2026-05-10 22:52:21 +03:00
fahricansecer f3362f266c gg 2026-05-10 22:52:05 +03:00
fahricansecer 8ce8fa5b94 Merge pull request 'gg' (#6) from v28 into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 39s
Reviewed-on: #6
2026-05-10 10:39:32 +03:00
fahricansecer c525b12dfd gg 2026-05-10 10:37:45 +03:00
fahricansecer 497b5d8d3b Merge pull request 'feat(ai-engine): value sniper thresholds and logic relaxed' (#5) from v28 into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 30s
Reviewed-on: #5
2026-05-06 17:56:24 +03:00
fahricansecer 4f7090e2d9 feat(ai-engine): value sniper thresholds and logic relaxed 2026-05-06 17:44:45 +03:00
fahricansecer 5b5f83c8cf fix(ai-engine): remove target leakage from training data extraction
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s
- goals_form now uses avg of last 5 historical matches instead of current match goals
- squad_quality removes current match goals/assists, uses only pre-match known data
- adds temporal filtering via match_id -> mst_utc mapping
2026-05-05 22:35:04 +03:00
fahricansecer bfddcaca7d gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s
2026-05-05 21:27:06 +03:00
fahricansecer 56d560af08 Update single_match_orchestrator.py
Deploy Iddaai Backend / build-and-deploy (push) Successful in 8s
2026-05-05 20:59:59 +03:00
fahricansecer 4bc51cfa99 fix(ai-engine): hoist ms_edge before score prediction branch to prevent UnboundLocalError
Deploy Iddaai Backend / build-and-deploy (push) Successful in 5s
2026-05-05 20:34:14 +03:00
fahricansecer fdb8a5d0f0 fix(ai-engine): sync FEATURE_COLS with trained models (82→102 features)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s
- Load feature columns dynamically from feature_cols.json
- Add 20 missing odds_*_present boolean flags to fallback list
- Fixes LightGBM 'features in data (82) != training data (102)' crash
2026-05-05 20:29:55 +03:00
fahricansecer 22596e69f2 fix(predictions): circuit breaker resilience + graceful degradation
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
- Reset consecutiveFailures on cooldown expiry (half-open state)
  so a single retry failure doesn't immediately re-open the circuit
- Exclude AI Engine app-level 500s from circuit breaker count
  (only network/infra errors: timeout, 502, 503, 504, 429)
- Return null gracefully instead of throwing 503 when no cache exists
- Add DB fallback for non-cooldown AI Engine failures
- Remove blocking wait-and-retry that held requests for up to 20s
2026-05-05 20:19:25 +03:00
fahricansecer f32badbd8f fix(predictions): cooldown fallback cascade + circuit breaker tuning
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
- Add 4-level fallback when AI circuit breaker fires cooldown:
  1) In-memory cache (10min TTL)
  2) DB stored prediction (no TTL filter)
  3) DB cached prediction (with model version check)
  4) Wait out cooldown + retry once (max 20s wait)
- Raise circuit breaker threshold from 3 to 5 consecutive failures
- Reduce cooldown duration from 30s to 15s for faster recovery
- Add extractCooldownMs helper to parse remaining ms from error detail
2026-05-05 20:11:19 +03:00
fahricansecer 5645b38f20 main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 32s
2026-05-05 17:09:11 +03:00
fahricansecer 244d8f5366 feat(ai): expand training to 68K+ matches, add score model, backfill implied odds
Deploy Iddaai Backend / build-and-deploy (push) Successful in 6s
- extract_training_data.py: switch from top_leagues.json (23) to qualified_leagues.json (265)
- update_implied_odds.py: new script to backfill implied odds from real market data
- train_score_model.py: rewrite with v25 102-feature set + temporal split
- single_match_orchestrator.py: integrate ML score model with heuristic fallback
2026-05-05 16:04:00 +03:00
fahricansecer 9bb8f39bca gg
Deploy Iddaai Backend / build-and-deploy (push) Successful in 2m45s
2026-05-05 14:06:20 +03:00
fahricansecer 7a1cf14e2f Update matches.service.ts
Deploy Iddaai Backend / build-and-deploy (push) Successful in 28s
2026-05-05 10:47:00 +03:00
fahricansecer 62c797d299 Update matches.service.ts
Deploy Iddaai Backend / build-and-deploy (push) Successful in 29s
2026-05-05 10:13:23 +03:00
fahricansecer 34cc4a6cbb Update matches.service.ts
Deploy Iddaai Backend / build-and-deploy (push) Successful in 30s
2026-05-05 01:04:56 +03:00
fahricansecer 27e96da31d main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 29s
2026-05-04 18:00:40 +03:00
fahricansecer 145a8b336b fix(feeder): preserve pre-match odds when match goes live
Deploy Iddaai Backend / build-and-deploy (push) Successful in 29s
Live odds have missing selections (e.g. '1' key removed from Maç Sonucu
after kickoff), causing the AI model to produce wildly incorrect predictions
(e.g. 3.5% home win for Bristol City). Two guards added:

1. fetchOddsForMatches: Exclude live/finished matches from odds fetch query
2. processMatchOdds: Skip odds/lineups/sidelined overwrite if match already
   has pre-match odds and is live/finished
2026-05-02 16:32:42 +03:00
fahricansecer 7a8960edb8 chore: remove debug checkpoint logs and temp SQL files
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s
2026-04-26 17:09:22 +03:00
fahricansecer 691c52f610 perf: replace Prisma relation queries with raw SQL for getExistingMatchIds and getMissingScopes - fixes Pi hang
Deploy Iddaai Backend / build-and-deploy (push) Successful in 39s
2026-04-26 17:07:19 +03:00
fahricansecer bc461429f6 debug: add checkpoint timestamps to processDate for hang diagnosis
Deploy Iddaai Backend / build-and-deploy (push) Successful in 46s
2026-04-26 17:04:46 +03:00
fahricansecer a338d02244 main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 2m42s
2026-04-26 03:07:18 +03:00
fahricansecer 1623432039 fix: watchdog force-kill with SIGKILL fallback when process.exit is blocked 2026-04-26 02:27:51 +03:00
fahricansecer 4c7930e9d2 feat: add watchdog timer to detect and recover from hung API requests
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
2026-04-25 11:20:30 +03:00
fahricansecer ec463cb927 fix: make canvas import optional for ARM64 compatibility 2026-04-25 02:41:53 +03:00
fahricansecer eab95c4e5c Update feeder.service.ts
Deploy Iddaai Backend / build-and-deploy (push) Successful in 30s
2026-04-25 02:23:38 +03:00
fahricansecer 9027cc9900 v28
Deploy Iddaai Backend / build-and-deploy (push) Successful in 3m21s
2026-04-24 23:46:28 +03:00
fahricansecer 3875f2a512 Create v28-pro-max-architecture.md
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
2026-04-24 02:30:26 +03:00
fahricansecer 300dceeb4b Merge branch 'main' of https://gitea.bilgich.com/fahricansecer/iddaai-be
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
2026-04-24 02:10:48 +03:00
fahricansecer ad01976fb9 fix: lineup data normalization + tomorrow match sync + player field mapping 2026-04-24 02:09:58 +03:00
fahricansecer 6880eb92f5 Merge pull request 'v26-shadow' (#4) from v26-shadow into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 27s
Reviewed-on: #4
2026-04-24 01:15:54 +03:00
fahricansecer 9e2edd590c Merge branch 'main' into v26-shadow 2026-04-24 01:15:18 +03:00
fahricansecer b5c2edf346 gg 2026-04-24 01:15:05 +03:00
fahricansecer bf7473c1e7 Merge pull request 'fix: update version tags to v28 and temporarily disable cache for predictions' (#3) from v26-shadow into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 31s
Reviewed-on: #3
2026-04-24 00:30:55 +03:00
fahricansecer 1f26a5bf2f fix: update version tags to v28 and temporarily disable cache for predictions 2026-04-24 00:11:00 +03:00
fahricansecer fb53fdf1df Merge pull request 'v26-shadow' (#2) from v26-shadow into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 2m51s
Reviewed-on: #2
2026-04-23 22:29:23 +03:00
fahricansecer 634204acf0 v28 2026-04-23 22:22:59 +03:00
fahricansecer df428ed1e8 gg 2026-04-22 02:17:02 +03:00
fahricansecer 2ccd6831eb gg 2026-04-21 16:53:56 +03:00
fahricansecer 1346924387 gg 2026-04-19 13:23:00 +03:00
fahricansecer e4c74025e5 Merge pull request 'cron' (#1) from cron into main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 2m48s
Reviewed-on: #1
2026-04-16 17:22:36 +03:00
249 changed files with 166917 additions and 8910 deletions
+33 -9
View File
@@ -11,13 +11,27 @@ jobs:
- name: Kodu Cek - name: Kodu Cek
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Docker Build - name: Docker Build (Backend)
run: docker build -t iddaai-be:latest . run: docker build -t iddaai-be:latest .
- name: Eski Konteyneri Sil - name: Docker Build (AI Engine)
run: docker rm -f iddaai-be || true run: docker build -t iddaai-ai-engine:latest ./ai-engine
- name: Yeni Versiyonu Baslat - name: Eski Konteynerleri Sil
run: |
docker rm -f iddaai-be || true
docker rm -f iddaai-ai-engine || true
- name: AI Engine'i Baslat
run: |
docker run -d \
--name iddaai-ai-engine \
--restart unless-stopped \
--network iddaai_iddaai-network \
-e DATABASE_URL='${{ secrets.DATABASE_URL }}' \
iddaai-ai-engine:latest
- name: Backend'i Baslat
run: | run: |
docker run -d \ docker run -d \
--name iddaai-be \ --name iddaai-be \
@@ -25,11 +39,21 @@ jobs:
--network iddaai_iddaai-network \ --network iddaai_iddaai-network \
-p 127.0.0.1:1810:3005 \ -p 127.0.0.1:1810:3005 \
-e NODE_ENV=production \ -e NODE_ENV=production \
-e DATABASE_URL='postgresql://iddaai_user:IddaA1_S4crET!@iddaai-postgres:5432/iddaai_db?schema=public' \ -e DATABASE_URL='${{ secrets.DATABASE_URL }}' \
-e REDIS_HOST='iddaai-redis' \ -e REDIS_HOST='${{ secrets.REDIS_HOST }}' \
-e REDIS_PORT='6379' \ -e REDIS_PORT='${{ secrets.REDIS_PORT }}' \
-e REDIS_PASSWORD='IddaA1_Redis_Pass!' \ -e REDIS_PASSWORD='${{ secrets.REDIS_PASSWORD }}' \
-e AI_ENGINE_URL='http://iddaai-ai-engine:8000' \ -e AI_ENGINE_URL='http://iddaai-ai-engine:8000' \
-e JWT_SECRET='b7V8jM2wP1L5mQxs2RdfFkAsLpI2oG!w' \ -e JWT_SECRET='${{ secrets.JWT_SECRET }}' \
-e JWT_ACCESS_EXPIRATION='1d' \ -e JWT_ACCESS_EXPIRATION='1d' \
iddaai-be:latest /bin/sh -c "npx prisma migrate deploy && node dist/src/main.js" iddaai-be:latest /bin/sh -c "npx prisma migrate deploy && node dist/src/main.js"
- name: Saglik Kontrolu
run: |
sleep 10
echo "=== AI Engine logs ==="
docker logs --tail 30 iddaai-ai-engine || true
echo "=== Backend logs ==="
docker logs --tail 30 iddaai-be || true
echo "=== AI Engine health ==="
docker exec iddaai-ai-engine python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8000/health').read().decode())" || echo "AI engine health check failed"
+9 -3
View File
@@ -21,7 +21,10 @@ venv/
env/ env/
# Database / Docker Volumes # Database / Docker Volumes
data/ /data/
ai-engine/data/**/*.csv
ai-engine/data/v26_shadow/
ai-engine/data/__pycache__/
postgres-data/ postgres-data/
redis-data/ redis-data/
@@ -42,7 +45,10 @@ uploads/
public/uploads/ public/uploads/
# Large Datasets and ML Models # Large Datasets and ML Models
ai-engine/models/ ai-engine/models/*
models/ !ai-engine/models/*.py
!ai-engine/models/v25/
models/*
!models/*.py
colab_export/ colab_export/
+3 -3
View File
@@ -16,7 +16,7 @@ RUN npm ci
COPY . . COPY . .
# Generate Prisma client # Generate Prisma client
RUN npx prisma generate RUN DATABASE_URL="postgresql://dummy:dummy@localhost/dummy" npx prisma generate
# Build the application # Build the application
RUN npm run build RUN npm run build
@@ -38,7 +38,7 @@ RUN apk add --no-cache --virtual .build-deps python3 make g++ cairo-dev pango-de
# Copy Prisma schema and generate client # Copy Prisma schema and generate client
COPY prisma ./prisma COPY prisma ./prisma
RUN npx prisma generate RUN DATABASE_URL="postgresql://dummy:dummy@localhost/dummy" npx prisma generate
# Copy built application # Copy built application
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist
@@ -47,7 +47,7 @@ COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src/i18n ./dist/i18n COPY --from=builder /app/src/i18n ./dist/i18n
# Copy league filter config files (critical: without these, feeder stores ALL matches) # Copy league filter config files (critical: without these, feeder stores ALL matches)
COPY top_leagues.json basketball_top_leagues.json ./ COPY qualified_leagues.json top_leagues.json basketball_top_leagues.json ./
# Set environment # Set environment
ENV NODE_ENV=production ENV NODE_ENV=production
@@ -0,0 +1,874 @@
{
"meta":{"test_sets":["test"],"test_metrics":[{"best_value":"Min","name":"Logloss"}],"learn_metrics":[{"best_value":"Min","name":"Logloss"}],"launch_mode":"Train","parameters":"","iteration_count":2000,"learn_sets":["learn"],"name":"experiment"},
"iterations":[
{"learn":[0.692389481],"iteration":0,"passed_time":0.04679785798,"remaining_time":93.54891809,"test":[0.6924099937]},
{"learn":[0.6916338586],"iteration":1,"passed_time":0.08350330552,"remaining_time":83.41980222,"test":[0.6916660956]},
{"learn":[0.6910159214],"iteration":2,"passed_time":0.132821758,"remaining_time":88.41501689,"test":[0.691108145]},
{"learn":[0.6903417151],"iteration":3,"passed_time":0.162826233,"remaining_time":81.25029026,"test":[0.6904585078]},
{"learn":[0.6896961461],"iteration":4,"passed_time":0.1969265393,"remaining_time":78.57368918,"test":[0.689812816]},
{"learn":[0.6890979366],"iteration":5,"passed_time":0.2309352918,"remaining_time":76.74749531,"test":[0.689192261]},
{"learn":[0.6884946167],"iteration":6,"passed_time":0.2693987513,"remaining_time":76.70167304,"test":[0.6886032715]},
{"learn":[0.6879503686],"iteration":7,"passed_time":0.3199759681,"remaining_time":79.67401607,"test":[0.6880706742]},
{"learn":[0.6874528094],"iteration":8,"passed_time":0.3645802206,"remaining_time":80.65324659,"test":[0.6876192378]},
{"learn":[0.6869036785],"iteration":9,"passed_time":0.4116507506,"remaining_time":81.91849936,"test":[0.6870868859]},
{"learn":[0.6863761921],"iteration":10,"passed_time":0.4562469316,"remaining_time":82.49774064,"test":[0.6865493528]},
{"learn":[0.6859038678],"iteration":11,"passed_time":0.491541699,"remaining_time":81.43207481,"test":[0.686105086]},
{"learn":[0.685410175],"iteration":12,"passed_time":0.5221556769,"remaining_time":79.80948692,"test":[0.6856345086]},
{"learn":[0.6849483392],"iteration":13,"passed_time":0.5553110353,"remaining_time":78.77483686,"test":[0.6852027185]},
{"learn":[0.6845417792],"iteration":14,"passed_time":0.5952927147,"remaining_time":78.77706925,"test":[0.6848238481]},
{"learn":[0.6841038875],"iteration":15,"passed_time":0.6300274185,"remaining_time":78.12339989,"test":[0.6844045699]},
{"learn":[0.6836957422],"iteration":16,"passed_time":0.662600544,"remaining_time":77.29040464,"test":[0.6840077621]},
{"learn":[0.6832947461],"iteration":17,"passed_time":0.7004221698,"remaining_time":77.12426337,"test":[0.6836197496]},
{"learn":[0.6829014105],"iteration":18,"passed_time":0.7300844347,"remaining_time":76.12090869,"test":[0.6832475033]},
{"learn":[0.6825264546],"iteration":19,"passed_time":0.7641559459,"remaining_time":75.65143865,"test":[0.6829012069]},
{"learn":[0.6822106577],"iteration":20,"passed_time":0.8040792063,"remaining_time":75.77489282,"test":[0.6825880966]},
{"learn":[0.6818649349],"iteration":21,"passed_time":0.8356039756,"remaining_time":75.12839381,"test":[0.6822424968]},
{"learn":[0.6815467855],"iteration":22,"passed_time":0.8861440327,"remaining_time":76.16985881,"test":[0.6819180513]},
{"learn":[0.6812293319],"iteration":23,"passed_time":0.920219319,"remaining_time":75.76472393,"test":[0.6816384467]},
{"learn":[0.6808837443],"iteration":24,"passed_time":0.960164738,"remaining_time":75.8530143,"test":[0.6813262593]},
{"learn":[0.6805816494],"iteration":25,"passed_time":0.9895547925,"remaining_time":75.13004463,"test":[0.6810353411]},
{"learn":[0.6803209634],"iteration":26,"passed_time":1.025550161,"remaining_time":74.94112844,"test":[0.6808138172]},
{"learn":[0.6800350862],"iteration":27,"passed_time":1.060852064,"remaining_time":74.71429535,"test":[0.6805550049]},
{"learn":[0.6797703947],"iteration":28,"passed_time":1.10467538,"remaining_time":75.07983357,"test":[0.680347991]},
{"learn":[0.6794926675],"iteration":29,"passed_time":1.141766834,"remaining_time":74.97602208,"test":[0.680089679]},
{"learn":[0.6792251865],"iteration":30,"passed_time":1.180421588,"remaining_time":74.9758099,"test":[0.6798451919]},
{"learn":[0.6789670166],"iteration":31,"passed_time":1.213674604,"remaining_time":74.64098814,"test":[0.6796090443]},
{"learn":[0.678722402],"iteration":32,"passed_time":1.245848393,"remaining_time":74.26011482,"test":[0.6793890865]},
{"learn":[0.678476935],"iteration":33,"passed_time":1.287262512,"remaining_time":74.43406171,"test":[0.6791683772]},
{"learn":[0.6782297335],"iteration":34,"passed_time":1.327473991,"remaining_time":74.52818262,"test":[0.6789766369]},
{"learn":[0.6780226701],"iteration":35,"passed_time":1.3760549,"remaining_time":75.07143955,"test":[0.6787930242]},
{"learn":[0.6778291026],"iteration":36,"passed_time":1.427620019,"remaining_time":75.74102965,"test":[0.6786087714]},
{"learn":[0.6776045324],"iteration":37,"passed_time":1.468182407,"remaining_time":75.80457587,"test":[0.6784161299]},
{"learn":[0.6773969079],"iteration":38,"passed_time":1.508647379,"remaining_time":75.85788487,"test":[0.6782227897]},
{"learn":[0.6771819602],"iteration":39,"passed_time":1.549435187,"remaining_time":75.92232419,"test":[0.6780242369]},
{"learn":[0.6769816736],"iteration":40,"passed_time":1.586036608,"remaining_time":75.78160282,"test":[0.6778499631]},
{"learn":[0.6767984027],"iteration":41,"passed_time":1.621458864,"remaining_time":75.59086802,"test":[0.6776975784]},
{"learn":[0.6766201184],"iteration":42,"passed_time":1.663424818,"remaining_time":75.70517136,"test":[0.6775231674]},
{"learn":[0.6764394377],"iteration":43,"passed_time":1.70110089,"remaining_time":75.62166686,"test":[0.6773582124]},
{"learn":[0.6762698797],"iteration":44,"passed_time":1.739954496,"remaining_time":75.59135644,"test":[0.6772234666]},
{"learn":[0.6760974263],"iteration":45,"passed_time":1.776461223,"remaining_time":75.46098325,"test":[0.6770659843]},
{"learn":[0.6759245179],"iteration":46,"passed_time":1.819761638,"remaining_time":75.61690381,"test":[0.6769049529]},
{"learn":[0.6757673909],"iteration":47,"passed_time":1.869479807,"remaining_time":76.02551217,"test":[0.6767664194]},
{"learn":[0.6756172628],"iteration":48,"passed_time":1.916010121,"remaining_time":76.28848462,"test":[0.6766584917]},
{"learn":[0.675474531],"iteration":49,"passed_time":1.953635244,"remaining_time":76.19177452,"test":[0.6765507257]},
{"learn":[0.6753286933],"iteration":50,"passed_time":1.993876686,"remaining_time":76.19736591,"test":[0.6764489911]},
{"learn":[0.6751900513],"iteration":51,"passed_time":2.038943041,"remaining_time":76.38194316,"test":[0.6763947956]},
{"learn":[0.6750574835],"iteration":52,"passed_time":2.080276765,"remaining_time":76.42073325,"test":[0.6762778712]},
{"learn":[0.6749329567],"iteration":53,"passed_time":2.158576742,"remaining_time":77.78871001,"test":[0.6761865366]},
{"learn":[0.6748033265],"iteration":54,"passed_time":2.220619687,"remaining_time":78.52918711,"test":[0.6760679685]},
{"learn":[0.6746797823],"iteration":55,"passed_time":2.286959228,"remaining_time":79.39015604,"test":[0.6759774874]},
{"learn":[0.674535525],"iteration":56,"passed_time":2.328472096,"remaining_time":79.3723032,"test":[0.6758500622]},
{"learn":[0.6744256514],"iteration":57,"passed_time":2.367031568,"remaining_time":79.25474665,"test":[0.6757625065]},
{"learn":[0.674310819],"iteration":58,"passed_time":2.409161286,"remaining_time":79.25732298,"test":[0.6756876412]},
{"learn":[0.6741967947],"iteration":59,"passed_time":2.444825903,"remaining_time":79.04937087,"test":[0.6756151069]},
{"learn":[0.6740879654],"iteration":60,"passed_time":2.48484996,"remaining_time":78.98564055,"test":[0.6755303655]},
{"learn":[0.6739772476],"iteration":61,"passed_time":2.521603395,"remaining_time":78.8204416,"test":[0.6754565036]},
{"learn":[0.67388281],"iteration":62,"passed_time":2.554102332,"remaining_time":78.5285114,"test":[0.6753738983]},
{"learn":[0.6737789726],"iteration":63,"passed_time":2.593937938,"remaining_time":78.46662263,"test":[0.6752897299]},
{"learn":[0.6736812332],"iteration":64,"passed_time":2.623889155,"remaining_time":78.11116175,"test":[0.6752115539]},
{"learn":[0.6735930009],"iteration":65,"passed_time":2.660795108,"remaining_time":77.96935967,"test":[0.6751595431]},
{"learn":[0.6734947116],"iteration":66,"passed_time":2.695822592,"remaining_time":77.77649358,"test":[0.6750764658]},
{"learn":[0.6733961481],"iteration":67,"passed_time":2.725876686,"remaining_time":77.44696703,"test":[0.6750179194]},
{"learn":[0.6732990195],"iteration":68,"passed_time":2.761848366,"remaining_time":77.29172746,"test":[0.6749408803]},
{"learn":[0.6732133575],"iteration":69,"passed_time":2.791847449,"remaining_time":76.97522253,"test":[0.6748795802]},
{"learn":[0.673111539],"iteration":70,"passed_time":2.824541003,"remaining_time":76.73999429,"test":[0.674790372]},
{"learn":[0.6730080451],"iteration":71,"passed_time":2.861023716,"remaining_time":76.61185729,"test":[0.6747239773]},
{"learn":[0.6729157861],"iteration":72,"passed_time":2.897136588,"remaining_time":76.47646857,"test":[0.6746701254]},
{"learn":[0.6728347949],"iteration":73,"passed_time":2.935718661,"remaining_time":76.40802894,"test":[0.6746120937]},
{"learn":[0.6727640693],"iteration":74,"passed_time":3.040023476,"remaining_time":78.02726921,"test":[0.6745550085]},
{"learn":[0.6726808811],"iteration":75,"passed_time":3.097341794,"remaining_time":78.41165279,"test":[0.6744855074]},
{"learn":[0.6726029645],"iteration":76,"passed_time":3.152948955,"remaining_time":78.74182909,"test":[0.6744264172]},
{"learn":[0.6725356026],"iteration":77,"passed_time":3.216126808,"remaining_time":79.24866314,"test":[0.674381715]},
{"learn":[0.6724606887],"iteration":78,"passed_time":3.256861302,"remaining_time":79.19532355,"test":[0.6743331681]},
{"learn":[0.6723849561],"iteration":79,"passed_time":3.305679851,"remaining_time":79.33631641,"test":[0.67428564]},
{"learn":[0.6723050519],"iteration":80,"passed_time":3.348083566,"remaining_time":79.32064647,"test":[0.6742202413]},
{"learn":[0.6722508802],"iteration":81,"passed_time":3.38129387,"remaining_time":79.08928832,"test":[0.6741620971]},
{"learn":[0.6721773904],"iteration":82,"passed_time":3.41660066,"remaining_time":78.91112609,"test":[0.6741109453]},
{"learn":[0.6721007598],"iteration":83,"passed_time":3.48099347,"remaining_time":79.39980344,"test":[0.6740556003]},
{"learn":[0.6720353564],"iteration":84,"passed_time":3.535359896,"remaining_time":79.64957884,"test":[0.6740146772]},
{"learn":[0.6719790902],"iteration":85,"passed_time":3.581806996,"remaining_time":79.71603012,"test":[0.673983295]},
{"learn":[0.6719140024],"iteration":86,"passed_time":3.612293661,"remaining_time":79.42893993,"test":[0.6739595301]},
{"learn":[0.6718573633],"iteration":87,"passed_time":3.644530261,"remaining_time":79.18570293,"test":[0.6739336659]},
{"learn":[0.671795602],"iteration":88,"passed_time":3.67809653,"remaining_time":78.97575809,"test":[0.673890361]},
{"learn":[0.6717369134],"iteration":89,"passed_time":3.712417516,"remaining_time":78.78574951,"test":[0.673863586]},
{"learn":[0.6716711079],"iteration":90,"passed_time":3.743502971,"remaining_time":78.53128759,"test":[0.6738190616]},
{"learn":[0.6716070843],"iteration":91,"passed_time":3.775351679,"remaining_time":78.2975109,"test":[0.6737799295]},
{"learn":[0.6715517232],"iteration":92,"passed_time":3.806186247,"remaining_time":78.04728142,"test":[0.6737364374]},
{"learn":[0.6714957378],"iteration":93,"passed_time":3.83798807,"remaining_time":77.82133257,"test":[0.6737093719]},
{"learn":[0.6714364567],"iteration":94,"passed_time":3.871278973,"remaining_time":77.62933099,"test":[0.6736630475]},
{"learn":[0.6713881758],"iteration":95,"passed_time":3.913531039,"remaining_time":77.6183656,"test":[0.67364367]},
{"learn":[0.6713336502],"iteration":96,"passed_time":3.945433866,"remaining_time":77.40371802,"test":[0.6735998081]},
{"learn":[0.6712700267],"iteration":97,"passed_time":3.989716281,"remaining_time":77.43306496,"test":[0.6735526984]},
{"learn":[0.6712154424],"iteration":98,"passed_time":4.020621946,"remaining_time":77.20406384,"test":[0.6735012924]},
{"learn":[0.6711600413],"iteration":99,"passed_time":4.053732144,"remaining_time":77.02091074,"test":[0.6734818024]},
{"learn":[0.6711060533],"iteration":100,"passed_time":4.084124711,"remaining_time":76.78963194,"test":[0.6734379341]},
{"learn":[0.6710494943],"iteration":101,"passed_time":4.116434744,"remaining_time":76.59797199,"test":[0.6734059869]},
{"learn":[0.6709936897],"iteration":102,"passed_time":4.148330356,"remaining_time":76.40177365,"test":[0.6733740852]},
{"learn":[0.6709472183],"iteration":103,"passed_time":4.176511193,"remaining_time":76.14101176,"test":[0.6733330971]},
{"learn":[0.6708914508],"iteration":104,"passed_time":4.2025065,"remaining_time":75.84523636,"test":[0.6733060254]},
{"learn":[0.6708388195],"iteration":105,"passed_time":4.232975206,"remaining_time":75.63448151,"test":[0.6732755898]},
{"learn":[0.6707885854],"iteration":106,"passed_time":4.261364958,"remaining_time":75.39031649,"test":[0.6732294722]},
{"learn":[0.6707454167],"iteration":107,"passed_time":4.290824713,"remaining_time":75.1688922,"test":[0.6732035176]},
{"learn":[0.6706973013],"iteration":108,"passed_time":4.324192493,"remaining_time":75.01878903,"test":[0.673196437]},
{"learn":[0.6706577031],"iteration":109,"passed_time":4.351512102,"remaining_time":74.76688976,"test":[0.6731652709]},
{"learn":[0.67061108],"iteration":110,"passed_time":4.38641502,"remaining_time":74.64808984,"test":[0.673138808]},
{"learn":[0.6705625485],"iteration":111,"passed_time":4.424063991,"remaining_time":74.57707871,"test":[0.6731062725]},
{"learn":[0.6705146484],"iteration":112,"passed_time":4.45863849,"remaining_time":74.45531709,"test":[0.6730726625]},
{"learn":[0.6704704423],"iteration":113,"passed_time":4.497153675,"remaining_time":74.40027922,"test":[0.6730285927]},
{"learn":[0.6704155922],"iteration":114,"passed_time":4.533368584,"remaining_time":74.30782417,"test":[0.6729872702]},
{"learn":[0.6703687117],"iteration":115,"passed_time":4.564651269,"remaining_time":74.13623268,"test":[0.6729721425]},
{"learn":[0.6703324232],"iteration":116,"passed_time":4.596824343,"remaining_time":73.98136956,"test":[0.6729564624]},
{"learn":[0.6702884624],"iteration":117,"passed_time":4.628377967,"remaining_time":73.81870623,"test":[0.6729312424]},
{"learn":[0.670253478],"iteration":118,"passed_time":4.668052254,"remaining_time":73.78660748,"test":[0.6729354345]},
{"learn":[0.6702140804],"iteration":119,"passed_time":4.692108266,"remaining_time":73.50969617,"test":[0.6729085401]},
{"learn":[0.6701682529],"iteration":120,"passed_time":4.723741667,"remaining_time":73.354633,"test":[0.6728898322]},
{"learn":[0.6701320588],"iteration":121,"passed_time":4.756626425,"remaining_time":73.22085595,"test":[0.6728773638]},
{"learn":[0.6700939824],"iteration":122,"passed_time":4.788008428,"remaining_time":73.06578714,"test":[0.6728618874]},
{"learn":[0.6700655902],"iteration":123,"passed_time":4.815546648,"remaining_time":72.85456058,"test":[0.6728540413]},
{"learn":[0.6700190743],"iteration":124,"passed_time":4.843186806,"remaining_time":72.64780209,"test":[0.6728441291]},
{"learn":[0.6699792296],"iteration":125,"passed_time":4.875548614,"remaining_time":72.51411192,"test":[0.672815631]},
{"learn":[0.6699379404],"iteration":126,"passed_time":4.916953662,"remaining_time":72.51538748,"test":[0.6728082021]},
{"learn":[0.669895454],"iteration":127,"passed_time":4.952918369,"remaining_time":72.43643115,"test":[0.6727900064]},
{"learn":[0.6698563938],"iteration":128,"passed_time":4.991585558,"remaining_time":72.39733782,"test":[0.6727649552]},
{"learn":[0.6698215571],"iteration":129,"passed_time":5.028084166,"remaining_time":72.32705685,"test":[0.6727467657]},
{"learn":[0.6697857067],"iteration":130,"passed_time":5.059198996,"remaining_time":72.18048033,"test":[0.6727396032]},
{"learn":[0.6697449303],"iteration":131,"passed_time":5.096035515,"remaining_time":72.1166238,"test":[0.6727245271]},
{"learn":[0.6697052425],"iteration":132,"passed_time":5.125282589,"remaining_time":71.94663604,"test":[0.6726955143]},
{"learn":[0.6696695553],"iteration":133,"passed_time":5.156392608,"remaining_time":71.80469109,"test":[0.67269209]},
{"learn":[0.6696269265],"iteration":134,"passed_time":5.190402292,"remaining_time":71.70444647,"test":[0.672677932]},
{"learn":[0.6695969271],"iteration":135,"passed_time":5.221466142,"remaining_time":71.56480065,"test":[0.6726540285]},
{"learn":[0.6695489786],"iteration":136,"passed_time":5.251144663,"remaining_time":71.40790151,"test":[0.6726288583]},
{"learn":[0.6695173859],"iteration":137,"passed_time":5.274361693,"remaining_time":71.16566285,"test":[0.6725863431]},
{"learn":[0.6694811164],"iteration":138,"passed_time":5.309398952,"remaining_time":71.08483058,"test":[0.6725837967]},
{"learn":[0.6694477439],"iteration":139,"passed_time":5.344693175,"remaining_time":71.00806646,"test":[0.6725772977]},
{"learn":[0.6694082161],"iteration":140,"passed_time":5.377737126,"remaining_time":70.90222211,"test":[0.6725685594]},
{"learn":[0.6693679185],"iteration":141,"passed_time":5.416087925,"remaining_time":70.8668406,"test":[0.6725553829]},
{"learn":[0.6693341916],"iteration":142,"passed_time":5.452286939,"remaining_time":70.80347444,"test":[0.6725484347]},
{"learn":[0.6692933159],"iteration":143,"passed_time":5.490006789,"remaining_time":70.7600875,"test":[0.6725306172]},
{"learn":[0.6692619696],"iteration":144,"passed_time":5.521869859,"remaining_time":70.64185233,"test":[0.672543149]},
{"learn":[0.6692229289],"iteration":145,"passed_time":5.553520721,"remaining_time":70.5221056,"test":[0.6725196247]},
{"learn":[0.6691840164],"iteration":146,"passed_time":5.582178524,"remaining_time":70.3658286,"test":[0.6725226452]},
{"learn":[0.6691581406],"iteration":147,"passed_time":5.611368671,"remaining_time":70.21793769,"test":[0.6725056913]},
{"learn":[0.6691177196],"iteration":148,"passed_time":5.636941079,"remaining_time":70.02669757,"test":[0.6724771476]},
{"learn":[0.6690851126],"iteration":149,"passed_time":5.673704689,"remaining_time":69.97569117,"test":[0.6724439435]},
{"learn":[0.6690518144],"iteration":150,"passed_time":5.706346207,"remaining_time":69.87439826,"test":[0.672442532]},
{"learn":[0.6690149711],"iteration":151,"passed_time":5.738210991,"remaining_time":69.76456521,"test":[0.6724303064]},
{"learn":[0.668993877],"iteration":152,"passed_time":5.765951318,"remaining_time":69.60596133,"test":[0.6724235788]},
{"learn":[0.6689596579],"iteration":153,"passed_time":5.795573467,"remaining_time":69.47161442,"test":[0.6724294499]},
{"learn":[0.6689372651],"iteration":154,"passed_time":5.81744896,"remaining_time":69.24640858,"test":[0.6724285935]},
{"learn":[0.6689003045],"iteration":155,"passed_time":5.853529431,"remaining_time":69.19171968,"test":[0.6724172017]},
{"learn":[0.6688680182],"iteration":156,"passed_time":5.888380392,"remaining_time":69.12283479,"test":[0.6724130745]},
{"learn":[0.6688348164],"iteration":157,"passed_time":5.924601775,"remaining_time":69.07035741,"test":[0.6723860878]},
{"learn":[0.6687947046],"iteration":158,"passed_time":5.964531924,"remaining_time":69.06102687,"test":[0.6723707604]},
{"learn":[0.6687605251],"iteration":159,"passed_time":5.996805452,"remaining_time":68.9632627,"test":[0.6723566111]},
{"learn":[0.668726253],"iteration":160,"passed_time":6.022341459,"remaining_time":68.78935368,"test":[0.6723469906]},
{"learn":[0.6686862718],"iteration":161,"passed_time":6.05082584,"remaining_time":68.65072774,"test":[0.6723287161]},
{"learn":[0.668663478],"iteration":162,"passed_time":6.079027554,"remaining_time":68.51026759,"test":[0.6723155898]},
{"learn":[0.6686399521],"iteration":163,"passed_time":6.108511297,"remaining_time":68.38552891,"test":[0.6722970834]},
{"learn":[0.6686058279],"iteration":164,"passed_time":6.140719309,"remaining_time":68.29224202,"test":[0.6722872244]},
{"learn":[0.6685761282],"iteration":165,"passed_time":6.169540017,"remaining_time":68.16226742,"test":[0.6722800481]},
{"learn":[0.6685469327],"iteration":166,"passed_time":6.2020892,"remaining_time":68.07442817,"test":[0.6722550973]},
{"learn":[0.6685157003],"iteration":167,"passed_time":6.231576547,"remaining_time":67.95385854,"test":[0.6722394313]},
{"learn":[0.6684805143],"iteration":168,"passed_time":6.263261652,"remaining_time":67.85817802,"test":[0.6722204135]},
{"learn":[0.6684485765],"iteration":169,"passed_time":6.295102833,"remaining_time":67.7649305,"test":[0.6721982148]},
{"learn":[0.6684144429],"iteration":170,"passed_time":6.325415964,"remaining_time":67.65605729,"test":[0.6721971176]},
{"learn":[0.6683849752],"iteration":171,"passed_time":6.35697084,"remaining_time":67.56129474,"test":[0.6721880705]},
{"learn":[0.6683568537],"iteration":172,"passed_time":6.395913563,"remaining_time":67.5452837,"test":[0.672179176]},
{"learn":[0.6683266628],"iteration":173,"passed_time":6.437330522,"remaining_time":67.55497433,"test":[0.6721769709]},
{"learn":[0.6682937842],"iteration":174,"passed_time":6.472195712,"remaining_time":67.49575528,"test":[0.6721693215]},
{"learn":[0.6682657097],"iteration":175,"passed_time":6.503044842,"remaining_time":67.395192,"test":[0.6721581386]},
{"learn":[0.6682301443],"iteration":176,"passed_time":6.533528251,"remaining_time":67.29164972,"test":[0.6721638661]},
{"learn":[0.6681995916],"iteration":177,"passed_time":6.562589882,"remaining_time":67.17437509,"test":[0.6721598475]},
{"learn":[0.6681658267],"iteration":178,"passed_time":6.590816982,"remaining_time":67.04959623,"test":[0.6721433342]},
{"learn":[0.6681422687],"iteration":179,"passed_time":6.624646227,"remaining_time":66.98253407,"test":[0.6721335599]},
{"learn":[0.6681216601],"iteration":180,"passed_time":6.655147334,"remaining_time":66.88239227,"test":[0.6721300594]},
{"learn":[0.6680899019],"iteration":181,"passed_time":6.687788902,"remaining_time":66.80439684,"test":[0.6721153533]},
{"learn":[0.6680676394],"iteration":182,"passed_time":6.718057043,"remaining_time":66.7033314,"test":[0.6721076397]},
{"learn":[0.6680413672],"iteration":183,"passed_time":6.751300957,"remaining_time":66.6324051,"test":[0.6721009911]},
{"learn":[0.6680088406],"iteration":184,"passed_time":6.784288393,"remaining_time":66.55936991,"test":[0.6720999252]},
{"learn":[0.6679873982],"iteration":185,"passed_time":6.810905309,"remaining_time":66.42463565,"test":[0.6720953028]},
{"learn":[0.6679663544],"iteration":186,"passed_time":6.832974292,"remaining_time":66.24696466,"test":[0.6720942505]},
{"learn":[0.6679417375],"iteration":187,"passed_time":6.867184511,"remaining_time":66.18796986,"test":[0.6720856237]},
{"learn":[0.6679100197],"iteration":188,"passed_time":6.918652024,"remaining_time":66.29459691,"test":[0.6720876136]},
{"learn":[0.667881208],"iteration":189,"passed_time":6.96948149,"remaining_time":66.39348156,"test":[0.6720880182]},
{"learn":[0.6678475427],"iteration":190,"passed_time":7.018176318,"remaining_time":66.47058094,"test":[0.6720743856]},
{"learn":[0.6678310341],"iteration":191,"passed_time":7.074099623,"remaining_time":66.61443812,"test":[0.6720598415]},
{"learn":[0.6678060257],"iteration":192,"passed_time":7.117099742,"remaining_time":66.63522919,"test":[0.6720563492]},
{"learn":[0.6677789336],"iteration":193,"passed_time":7.191058554,"remaining_time":66.94356571,"test":[0.6720389527]},
{"learn":[0.6677478773],"iteration":194,"passed_time":7.2421897,"remaining_time":67.03667902,"test":[0.6720317324]},
{"learn":[0.6677212408],"iteration":195,"passed_time":7.282401129,"remaining_time":67.02781447,"test":[0.672000736]},
{"learn":[0.667704316],"iteration":196,"passed_time":7.317019235,"remaining_time":66.96744,"test":[0.6719895017]},
{"learn":[0.6676819639],"iteration":197,"passed_time":7.351194179,"remaining_time":66.90329248,"test":[0.6719725302]},
{"learn":[0.6676554448],"iteration":198,"passed_time":7.389840926,"remaining_time":66.87991712,"test":[0.6719770493]},
{"learn":[0.6676318346],"iteration":199,"passed_time":7.432994652,"remaining_time":66.89695187,"test":[0.6719667172]},
{"learn":[0.6676074705],"iteration":200,"passed_time":7.471295231,"remaining_time":66.86995085,"test":[0.6719511616]},
{"learn":[0.6675849784],"iteration":201,"passed_time":7.506377837,"remaining_time":66.8141948,"test":[0.6719427289]},
{"learn":[0.6675631744],"iteration":202,"passed_time":7.540821494,"remaining_time":66.75298633,"test":[0.6719299116]},
{"learn":[0.6675397619],"iteration":203,"passed_time":7.56808212,"remaining_time":66.62880141,"test":[0.6719106583]},
{"learn":[0.6675169086],"iteration":204,"passed_time":7.605676901,"remaining_time":66.59604896,"test":[0.6718967065]},
{"learn":[0.6674864762],"iteration":205,"passed_time":7.638300222,"remaining_time":66.51995436,"test":[0.671890967]},
{"learn":[0.6674670714],"iteration":206,"passed_time":7.665554951,"remaining_time":66.39777791,"test":[0.6718896293]},
{"learn":[0.6674375599],"iteration":207,"passed_time":7.700277678,"remaining_time":66.34085384,"test":[0.6718883534]},
{"learn":[0.6674148457],"iteration":208,"passed_time":7.734145802,"remaining_time":66.27681881,"test":[0.6718827289]},
{"learn":[0.6673974446],"iteration":209,"passed_time":7.766232144,"remaining_time":66.19788351,"test":[0.6718763224]},
{"learn":[0.6673812139],"iteration":210,"passed_time":7.796801222,"remaining_time":66.1065279,"test":[0.67187262]},
{"learn":[0.6673515687],"iteration":211,"passed_time":7.831891449,"remaining_time":66.05387693,"test":[0.6718590402]},
{"learn":[0.6673197956],"iteration":212,"passed_time":7.871259964,"remaining_time":66.0372843,"test":[0.6718455115]},
{"learn":[0.6672900754],"iteration":213,"passed_time":7.910110502,"remaining_time":66.01615587,"test":[0.6718253747]},
{"learn":[0.6672550009],"iteration":214,"passed_time":7.951342226,"remaining_time":66.01463197,"test":[0.671794877]},
{"learn":[0.6672271563],"iteration":215,"passed_time":7.989001461,"remaining_time":65.98323429,"test":[0.6717873786]},
{"learn":[0.667204521],"iteration":216,"passed_time":8.025973631,"remaining_time":65.94613357,"test":[0.6717765089]},
{"learn":[0.667181968],"iteration":217,"passed_time":8.058434478,"remaining_time":65.87215707,"test":[0.6717616726]},
{"learn":[0.6671640023],"iteration":218,"passed_time":8.087145957,"remaining_time":65.76806826,"test":[0.6717499215]},
{"learn":[0.66714351],"iteration":219,"passed_time":8.112590578,"remaining_time":65.63823286,"test":[0.6717326052]},
{"learn":[0.6671167156],"iteration":220,"passed_time":8.148644349,"remaining_time":65.59474342,"test":[0.6717161937]},
{"learn":[0.6670915937],"iteration":221,"passed_time":8.197662625,"remaining_time":65.65515382,"test":[0.6717056951]},
{"learn":[0.6670595279],"iteration":222,"passed_time":8.239228431,"remaining_time":65.65519696,"test":[0.6717021438]},
{"learn":[0.667033994],"iteration":223,"passed_time":8.268371203,"remaining_time":65.55637168,"test":[0.6716868488]},
{"learn":[0.6670008246],"iteration":224,"passed_time":8.298555216,"remaining_time":65.46638004,"test":[0.6716751909]},
{"learn":[0.6669858319],"iteration":225,"passed_time":8.327401394,"remaining_time":65.36641625,"test":[0.671670116]},
{"learn":[0.6669553964],"iteration":226,"passed_time":8.357648377,"remaining_time":65.27802014,"test":[0.6716558757]},
{"learn":[0.6669274683],"iteration":227,"passed_time":8.384989701,"remaining_time":65.16755154,"test":[0.6716559962]},
{"learn":[0.666896348],"iteration":228,"passed_time":8.418297538,"remaining_time":65.1039517,"test":[0.6716487875]},
{"learn":[0.6668698686],"iteration":229,"passed_time":8.453919972,"remaining_time":65.05842761,"test":[0.6716427451]},
{"learn":[0.6668513411],"iteration":230,"passed_time":8.49049033,"remaining_time":65.02024846,"test":[0.6716323255]},
{"learn":[0.6668309985],"iteration":231,"passed_time":8.523986676,"remaining_time":64.95865708,"test":[0.6716303547]},
{"learn":[0.6668058585],"iteration":232,"passed_time":8.550998228,"remaining_time":64.84812819,"test":[0.6716309509]},
{"learn":[0.6667845908],"iteration":233,"passed_time":8.575382398,"remaining_time":64.71848425,"test":[0.6716215401]},
{"learn":[0.6667582863],"iteration":234,"passed_time":8.607602961,"remaining_time":64.64859245,"test":[0.6716162103]},
{"learn":[0.6667332943],"iteration":235,"passed_time":8.6353786,"remaining_time":64.54579597,"test":[0.6716135097]},
{"learn":[0.6667070085],"iteration":236,"passed_time":8.66085309,"remaining_time":64.42651476,"test":[0.6716156696]},
{"learn":[0.6666907315],"iteration":237,"passed_time":8.691362456,"remaining_time":64.34529684,"test":[0.6716020054]},
{"learn":[0.6666633028],"iteration":238,"passed_time":8.719983169,"remaining_time":64.25058728,"test":[0.6715921704]},
{"learn":[0.6666406707],"iteration":239,"passed_time":8.746012652,"remaining_time":64.13742611,"test":[0.6715804466]},
{"learn":[0.6666134624],"iteration":240,"passed_time":8.773898765,"remaining_time":64.03853912,"test":[0.6715882966]},
{"learn":[0.6665850522],"iteration":241,"passed_time":8.803292064,"remaining_time":63.9511878,"test":[0.6715753942]},
{"learn":[0.6665631193],"iteration":242,"passed_time":8.833976809,"remaining_time":63.87365125,"test":[0.6715752261]},
{"learn":[0.6665412643],"iteration":243,"passed_time":8.862338006,"remaining_time":63.7797768,"test":[0.6715625509]},
{"learn":[0.6665168385],"iteration":244,"passed_time":8.892424073,"remaining_time":63.69879285,"test":[0.6715628214]},
{"learn":[0.6664904845],"iteration":245,"passed_time":8.932383667,"remaining_time":63.68862175,"test":[0.6715601629]},
{"learn":[0.6664678274],"iteration":246,"passed_time":8.962911123,"remaining_time":63.61126801,"test":[0.6715576255]},
{"learn":[0.6664539777],"iteration":247,"passed_time":8.991624872,"remaining_time":63.52147894,"test":[0.6715550274]},
{"learn":[0.6664334121],"iteration":248,"passed_time":9.021847081,"remaining_time":63.44278811,"test":[0.6715448645]},
{"learn":[0.6664121724],"iteration":249,"passed_time":9.05121341,"remaining_time":63.35849387,"test":[0.6715308166]},
{"learn":[0.666392034],"iteration":250,"passed_time":9.085113431,"remaining_time":63.30622865,"test":[0.671519334]},
{"learn":[0.666366899],"iteration":251,"passed_time":9.110250512,"remaining_time":63.19332498,"test":[0.6715184071]},
{"learn":[0.6663414098],"iteration":252,"passed_time":9.137253573,"remaining_time":63.09399997,"test":[0.6715163019]},
{"learn":[0.6663157816],"iteration":253,"passed_time":9.174559864,"remaining_time":63.06606899,"test":[0.6715096094]},
{"learn":[0.6662989799],"iteration":254,"passed_time":9.196898204,"remaining_time":62.93563673,"test":[0.6714992963]},
{"learn":[0.6662696102],"iteration":255,"passed_time":9.238149902,"remaining_time":62.9348962,"test":[0.6714917256]},
{"learn":[0.6662479711],"iteration":256,"passed_time":9.267818291,"remaining_time":62.85528125,"test":[0.671477406]},
{"learn":[0.6662231874],"iteration":257,"passed_time":9.297538986,"remaining_time":62.77640665,"test":[0.6714741542]},
{"learn":[0.6661947927],"iteration":258,"passed_time":9.324772701,"remaining_time":62.68119411,"test":[0.6714576155]},
{"learn":[0.6661669951],"iteration":259,"passed_time":9.357824574,"remaining_time":62.62544138,"test":[0.6714473645]},
{"learn":[0.6661426137],"iteration":260,"passed_time":9.388345461,"remaining_time":62.55299907,"test":[0.6714427232]},
{"learn":[0.6661216749],"iteration":261,"passed_time":9.427290804,"remaining_time":62.53676114,"test":[0.6714364275]},
{"learn":[0.6660983123],"iteration":262,"passed_time":9.461913185,"remaining_time":62.49179925,"test":[0.6714339587]},
{"learn":[0.6660803402],"iteration":263,"passed_time":9.496090562,"remaining_time":62.44398945,"test":[0.6714336287]},
{"learn":[0.6660617842],"iteration":264,"passed_time":9.524189317,"remaining_time":62.35648477,"test":[0.6714283568]},
{"learn":[0.6660443878],"iteration":265,"passed_time":9.55372419,"remaining_time":62.27878852,"test":[0.6714271895]},
{"learn":[0.6660176079],"iteration":266,"passed_time":9.590356068,"remaining_time":62.2475171,"test":[0.671413471]},
{"learn":[0.6659967546],"iteration":267,"passed_time":9.620235131,"remaining_time":62.17256436,"test":[0.6714072396]},
{"learn":[0.6659751467],"iteration":268,"passed_time":9.645948482,"remaining_time":62.0711406,"test":[0.6714002677]},
{"learn":[0.6659539329],"iteration":269,"passed_time":9.682675077,"remaining_time":62.04084401,"test":[0.6714001163]},
{"learn":[0.6659263951],"iteration":270,"passed_time":9.711914203,"remaining_time":61.96272936,"test":[0.6713933952]},
{"learn":[0.6659038921],"iteration":271,"passed_time":9.739142426,"remaining_time":61.87219894,"test":[0.6713926761]},
{"learn":[0.6658767418],"iteration":272,"passed_time":9.768751964,"remaining_time":61.79719649,"test":[0.6713836619]},
{"learn":[0.6658510507],"iteration":273,"passed_time":9.804576737,"remaining_time":61.76167682,"test":[0.6713772112]},
{"learn":[0.6658210119],"iteration":274,"passed_time":9.848653906,"remaining_time":61.77791996,"test":[0.6713603715]},
{"learn":[0.6657963011],"iteration":275,"passed_time":9.88663261,"remaining_time":61.75563268,"test":[0.6713560246]},
{"learn":[0.6657748552],"iteration":276,"passed_time":9.925808942,"remaining_time":61.74068161,"test":[0.6713837913]},
{"learn":[0.6657490013],"iteration":277,"passed_time":9.965409489,"remaining_time":61.72818396,"test":[0.6713684274]},
{"learn":[0.665732402],"iteration":278,"passed_time":9.99537326,"remaining_time":61.65604796,"test":[0.6713619356]},
{"learn":[0.6657118786],"iteration":279,"passed_time":10.02216777,"remaining_time":61.5647449,"test":[0.6713584836]},
{"learn":[0.665684467],"iteration":280,"passed_time":10.05593393,"remaining_time":61.51654955,"test":[0.6713673572]},
{"learn":[0.6656584634],"iteration":281,"passed_time":10.08025153,"remaining_time":61.41089406,"test":[0.6713625568]},
{"learn":[0.6656309991],"iteration":282,"passed_time":10.11102202,"remaining_time":61.34496401,"test":[0.6713542652]},
{"learn":[0.6656073482],"iteration":283,"passed_time":10.14714598,"remaining_time":61.31162855,"test":[0.6713512017]},
{"learn":[0.6655890957],"iteration":284,"passed_time":10.17528061,"remaining_time":61.23019734,"test":[0.671342038]},
{"learn":[0.6655665563],"iteration":285,"passed_time":10.2021403,"remaining_time":61.14149818,"test":[0.6713279798]},
{"learn":[0.6655452454],"iteration":286,"passed_time":10.23423432,"remaining_time":61.08447174,"test":[0.6713123285]},
{"learn":[0.6655255286],"iteration":287,"passed_time":10.26481698,"remaining_time":61.0186343,"test":[0.6713035326]},
{"learn":[0.6655053548],"iteration":288,"passed_time":10.29945844,"remaining_time":60.97707056,"test":[0.6713022203]},
{"learn":[0.6654893396],"iteration":289,"passed_time":10.32366496,"remaining_time":60.87402441,"test":[0.671296041]},
{"learn":[0.6654648912],"iteration":290,"passed_time":10.35344703,"remaining_time":60.80426453,"test":[0.6712829551]},
{"learn":[0.6654442759],"iteration":291,"passed_time":10.3949915,"remaining_time":60.8035804,"test":[0.6712769751]},
{"learn":[0.6654173127],"iteration":292,"passed_time":10.43148765,"remaining_time":60.77320621,"test":[0.6712702915]},
{"learn":[0.6653914518],"iteration":293,"passed_time":10.47162738,"remaining_time":60.76393303,"test":[0.6712379343]},
{"learn":[0.6653648946],"iteration":294,"passed_time":10.50360107,"remaining_time":60.70725362,"test":[0.6712192006]},
{"learn":[0.665344141],"iteration":295,"passed_time":10.53460819,"remaining_time":60.64517686,"test":[0.6712074061]},
{"learn":[0.6653140817],"iteration":296,"passed_time":10.57659448,"remaining_time":60.64626395,"test":[0.6711953324]},
{"learn":[0.665295365],"iteration":297,"passed_time":10.61260262,"remaining_time":60.61291829,"test":[0.6711891001]},
{"learn":[0.6652787488],"iteration":298,"passed_time":10.63910358,"remaining_time":60.52546889,"test":[0.6711870526]},
{"learn":[0.6652502991],"iteration":299,"passed_time":10.6681867,"remaining_time":60.45305797,"test":[0.6711812809]},
{"learn":[0.665231168],"iteration":300,"passed_time":10.70260503,"remaining_time":60.41104967,"test":[0.6711768946]},
{"learn":[0.6652136682],"iteration":301,"passed_time":10.72952096,"remaining_time":60.32690925,"test":[0.6711845012]},
{"learn":[0.6651903001],"iteration":302,"passed_time":10.76489952,"remaining_time":60.29054288,"test":[0.6711869636]},
{"learn":[0.6651697153],"iteration":303,"passed_time":10.80197155,"remaining_time":60.26363073,"test":[0.671186884]},
{"learn":[0.6651525958],"iteration":304,"passed_time":10.82922271,"remaining_time":60.18207375,"test":[0.6711890401]},
{"learn":[0.6651322685],"iteration":305,"passed_time":10.8578399,"remaining_time":60.10843394,"test":[0.6711868603]},
{"learn":[0.6651113828],"iteration":306,"passed_time":10.89228879,"remaining_time":60.06724727,"test":[0.6711900892]},
{"learn":[0.6650886807],"iteration":307,"passed_time":10.93056436,"remaining_time":60.04712628,"test":[0.6711884242]},
{"learn":[0.6650622251],"iteration":308,"passed_time":10.97231236,"remaining_time":60.04589061,"test":[0.6711837119]},
{"learn":[0.6650429987],"iteration":309,"passed_time":11.00296848,"remaining_time":59.98392494,"test":[0.6711766645]},
{"learn":[0.665015513],"iteration":310,"passed_time":11.03002276,"remaining_time":59.90259947,"test":[0.671172959]},
{"learn":[0.6650019022],"iteration":311,"passed_time":11.05828865,"remaining_time":59.82817707,"test":[0.6711740433]},
{"learn":[0.664979951],"iteration":312,"passed_time":11.09287745,"remaining_time":59.78812863,"test":[0.6711715069]},
{"learn":[0.6649549638],"iteration":313,"passed_time":11.1177757,"remaining_time":59.69608229,"test":[0.6711589843]},
{"learn":[0.6649340455],"iteration":314,"passed_time":11.14959087,"remaining_time":59.64146228,"test":[0.6711446402]},
{"learn":[0.6649162445],"iteration":315,"passed_time":11.18718772,"remaining_time":59.61779784,"test":[0.6711415366]},
{"learn":[0.6649048119],"iteration":316,"passed_time":11.21179073,"remaining_time":59.52505932,"test":[0.6711359351]},
{"learn":[0.6648796463],"iteration":317,"passed_time":11.24311165,"remaining_time":59.46828238,"test":[0.671143361]},
{"learn":[0.6648605481],"iteration":318,"passed_time":11.27486028,"remaining_time":59.41391889,"test":[0.6711353638]},
{"learn":[0.6648429084],"iteration":319,"passed_time":11.30400807,"remaining_time":59.34604237,"test":[0.6711444387]},
{"learn":[0.6648238121],"iteration":320,"passed_time":11.33488419,"remaining_time":59.28744721,"test":[0.6711487352]},
{"learn":[0.6647969527],"iteration":321,"passed_time":11.36208838,"remaining_time":59.20988915,"test":[0.67114436]},
{"learn":[0.6647854723],"iteration":322,"passed_time":11.39429642,"remaining_time":59.15862259,"test":[0.6711444722]},
{"learn":[0.6647589304],"iteration":323,"passed_time":11.4363998,"remaining_time":59.15866068,"test":[0.6711325635]},
{"learn":[0.6647429024],"iteration":324,"passed_time":11.47751019,"remaining_time":59.15332173,"test":[0.6711269403]},
{"learn":[0.6647237508],"iteration":325,"passed_time":11.5136833,"remaining_time":59.12241054,"test":[0.6711154078]},
{"learn":[0.6647059396],"iteration":326,"passed_time":11.54795566,"remaining_time":59.08174257,"test":[0.6711203043]},
{"learn":[0.664686288],"iteration":327,"passed_time":11.57245915,"remaining_time":58.99131613,"test":[0.6711241333]},
{"learn":[0.6646532527],"iteration":328,"passed_time":11.60790333,"remaining_time":58.95685857,"test":[0.6711213497]},
{"learn":[0.6646306438],"iteration":329,"passed_time":11.63787346,"remaining_time":58.89469298,"test":[0.6711231641]},
{"learn":[0.6646098516],"iteration":330,"passed_time":11.66805718,"remaining_time":58.83379887,"test":[0.6711049215]},
{"learn":[0.6645858284],"iteration":331,"passed_time":11.70070223,"remaining_time":58.78545579,"test":[0.6711031963]},
{"learn":[0.6645707188],"iteration":332,"passed_time":11.724753,"remaining_time":58.69418391,"test":[0.6710996314]},
{"learn":[0.6645485788],"iteration":333,"passed_time":11.75795297,"remaining_time":58.64895104,"test":[0.6710867309]},
{"learn":[0.6645305696],"iteration":334,"passed_time":11.78053066,"remaining_time":58.55099567,"test":[0.6710914578]},
{"learn":[0.6645108881],"iteration":335,"passed_time":11.81570271,"remaining_time":58.51586106,"test":[0.6710929585]},
{"learn":[0.6644923286],"iteration":336,"passed_time":11.8448851,"remaining_time":58.45116888,"test":[0.6710984779]},
{"learn":[0.6644805222],"iteration":337,"passed_time":11.86964023,"remaining_time":58.36491734,"test":[0.6710923199]},
{"learn":[0.6644572776],"iteration":338,"passed_time":11.90591446,"remaining_time":58.33546879,"test":[0.6710893917]},
{"learn":[0.6644320741],"iteration":339,"passed_time":11.94145444,"remaining_time":58.30239521,"test":[0.6710923306]},
{"learn":[0.6644115048],"iteration":340,"passed_time":11.98658051,"remaining_time":58.31594449,"test":[0.6710927901]},
{"learn":[0.6643949013],"iteration":341,"passed_time":12.02038848,"remaining_time":58.27428098,"test":[0.6711092802]},
{"learn":[0.6643619789],"iteration":342,"passed_time":12.06653941,"remaining_time":58.29229096,"test":[0.6711012995]},
{"learn":[0.6643389502],"iteration":343,"passed_time":12.12283646,"remaining_time":58.35877087,"test":[0.6711015305]},
{"learn":[0.6643088915],"iteration":344,"passed_time":12.17733618,"remaining_time":58.41591705,"test":[0.6710975574]},
{"learn":[0.664286972],"iteration":345,"passed_time":12.22133732,"remaining_time":58.42223099,"test":[0.6710899474]},
{"learn":[0.664274149],"iteration":346,"passed_time":12.2642467,"remaining_time":58.42305415,"test":[0.671085152]},
{"learn":[0.6642536926],"iteration":347,"passed_time":12.30091895,"remaining_time":58.39401755,"test":[0.6710814533]},
{"learn":[0.6642357634],"iteration":348,"passed_time":12.32484094,"remaining_time":58.30462002,"test":[0.6710701892]},
{"learn":[0.664207914],"iteration":349,"passed_time":12.35469303,"remaining_time":58.24355287,"test":[0.67105503]},
{"learn":[0.6641853097],"iteration":350,"passed_time":12.40148755,"remaining_time":58.26225919,"test":[0.6710527861]},
{"learn":[0.6641654917],"iteration":351,"passed_time":12.43803877,"remaining_time":58.23263605,"test":[0.6710508715]},
{"learn":[0.664143804],"iteration":352,"passed_time":12.47995438,"remaining_time":58.22800245,"test":[0.6710560803]},
{"learn":[0.6641290647],"iteration":353,"passed_time":12.51241326,"remaining_time":58.17918707,"test":[0.6710465693]},
{"learn":[0.6641117244],"iteration":354,"passed_time":12.5417829,"remaining_time":58.11614893,"test":[0.6710440741]},
{"learn":[0.6640880219],"iteration":355,"passed_time":12.5692936,"remaining_time":58.0447154,"test":[0.6710496913]},
{"learn":[0.6640669415],"iteration":356,"passed_time":12.5976392,"remaining_time":57.97737034,"test":[0.6710404659]},
{"learn":[0.6640462999],"iteration":357,"passed_time":12.62815847,"remaining_time":57.92021287,"test":[0.6710293986]},
{"learn":[0.664030296],"iteration":358,"passed_time":12.65342509,"remaining_time":57.8391938,"test":[0.6710353817]},
{"learn":[0.6640028542],"iteration":359,"passed_time":12.68233453,"remaining_time":57.77507954,"test":[0.6710271815]},
{"learn":[0.6639813347],"iteration":360,"passed_time":12.72037964,"remaining_time":57.75263774,"test":[0.6710288077]},
{"learn":[0.6639597941],"iteration":361,"passed_time":12.744473,"remaining_time":57.66698004,"test":[0.6710169894]},
{"learn":[0.6639429832],"iteration":362,"passed_time":12.77086568,"remaining_time":57.59203063,"test":[0.6710119848]},
{"learn":[0.6639222708],"iteration":363,"passed_time":12.81194554,"remaining_time":57.58335961,"test":[0.6710114775]},
{"learn":[0.6639065546],"iteration":364,"passed_time":12.84133287,"remaining_time":57.52213492,"test":[0.6710013614]},
{"learn":[0.6638823236],"iteration":365,"passed_time":12.87057337,"remaining_time":57.46042866,"test":[0.6709985657]},
{"learn":[0.6638648195],"iteration":366,"passed_time":12.8971183,"remaining_time":57.38690512,"test":[0.6709948954]},
{"learn":[0.6638436235],"iteration":367,"passed_time":12.93825161,"remaining_time":57.37833324,"test":[0.6709970591]},
{"learn":[0.6638208732],"iteration":368,"passed_time":12.97444296,"remaining_time":57.3477411,"test":[0.6709739289]},
{"learn":[0.6637956357],"iteration":369,"passed_time":13.00974924,"remaining_time":57.31321963,"test":[0.6709754911]},
{"learn":[0.6637718453],"iteration":370,"passed_time":13.03832239,"remaining_time":57.24912984,"test":[0.6709717066]},
{"learn":[0.663756918],"iteration":371,"passed_time":13.07843077,"remaining_time":57.23571316,"test":[0.67096845]},
{"learn":[0.6637353525],"iteration":372,"passed_time":13.11729124,"remaining_time":57.21671005,"test":[0.6709739445]},
{"learn":[0.6637143112],"iteration":373,"passed_time":13.14745329,"remaining_time":57.15978354,"test":[0.6709728881]},
{"learn":[0.6636956547],"iteration":374,"passed_time":13.18118022,"remaining_time":57.11844761,"test":[0.6709694284]},
{"learn":[0.663680995],"iteration":375,"passed_time":13.20539229,"remaining_time":57.03605604,"test":[0.6709604166]},
{"learn":[0.66366728],"iteration":376,"passed_time":13.23563977,"remaining_time":56.97995583,"test":[0.6709605025]},
{"learn":[0.6636487567],"iteration":377,"passed_time":13.27428255,"remaining_time":56.96001665,"test":[0.6709603727]},
{"learn":[0.6636266904],"iteration":378,"passed_time":13.30625754,"remaining_time":56.91146033,"test":[0.670944339]},
{"learn":[0.6636116064],"iteration":379,"passed_time":13.33327871,"remaining_time":56.84187241,"test":[0.6709447187]},
{"learn":[0.6635902746],"iteration":380,"passed_time":13.36632239,"remaining_time":56.79809961,"test":[0.6709538679]},
{"learn":[0.6635654896],"iteration":381,"passed_time":13.39639051,"remaining_time":56.74177969,"test":[0.6709640912]},
{"learn":[0.6635393029],"iteration":382,"passed_time":13.42189438,"remaining_time":56.66632694,"test":[0.6709534847]},
{"learn":[0.6635171734],"iteration":383,"passed_time":13.46730432,"remaining_time":56.6749057,"test":[0.6709471555]},
{"learn":[0.663500789],"iteration":384,"passed_time":13.50832777,"remaining_time":56.66480351,"test":[0.6709506783]},
{"learn":[0.663477743],"iteration":385,"passed_time":13.54029627,"remaining_time":56.61667921,"test":[0.6709546729]},
{"learn":[0.6634584806],"iteration":386,"passed_time":13.56996301,"remaining_time":56.5590448,"test":[0.670930774]},
{"learn":[0.6634337499],"iteration":387,"passed_time":13.59835745,"remaining_time":56.4962686,"test":[0.6709287322]},
{"learn":[0.6634135584],"iteration":388,"passed_time":13.6279617,"remaining_time":56.43867943,"test":[0.6709198643]},
{"learn":[0.6633868455],"iteration":389,"passed_time":13.65633448,"remaining_time":56.37615005,"test":[0.6709220389]},
{"learn":[0.6633755323],"iteration":390,"passed_time":13.68565529,"remaining_time":56.31769658,"test":[0.6709230923]},
{"learn":[0.663356103],"iteration":391,"passed_time":13.71789303,"remaining_time":56.27135714,"test":[0.670930414]},
{"learn":[0.6633337631],"iteration":392,"passed_time":13.75060752,"remaining_time":56.2270389,"test":[0.6709354296]},
{"learn":[0.663319422],"iteration":393,"passed_time":13.77167974,"remaining_time":56.13532403,"test":[0.6709351544]},
{"learn":[0.6632911566],"iteration":394,"passed_time":13.80416242,"remaining_time":56.09033084,"test":[0.6709414935]},
{"learn":[0.6632687875],"iteration":395,"passed_time":13.82525369,"remaining_time":55.9992599,"test":[0.6709445943]},
{"learn":[0.6632431997],"iteration":396,"passed_time":13.85836516,"remaining_time":55.95707646,"test":[0.6709475685]},
{"learn":[0.6632189331],"iteration":397,"passed_time":13.88898168,"remaining_time":55.90489613,"test":[0.6709533591]},
{"learn":[0.663201035],"iteration":398,"passed_time":13.91726355,"remaining_time":55.84345598,"test":[0.6709592222]},
{"learn":[0.6631898553],"iteration":399,"passed_time":13.95316828,"remaining_time":55.81267311,"test":[0.6709508704]},
{"learn":[0.6631712482],"iteration":400,"passed_time":13.99418497,"remaining_time":55.80224881,"test":[0.6709479912]},
{"learn":[0.663143025],"iteration":401,"passed_time":14.0253575,"remaining_time":55.75254052,"test":[0.6709417519]},
{"learn":[0.663121538],"iteration":402,"passed_time":14.04844239,"remaining_time":55.67087467,"test":[0.6709476082]},
{"learn":[0.6631087792],"iteration":403,"passed_time":14.0761289,"remaining_time":55.60767753,"test":[0.6709480979]},
{"learn":[0.6630859067],"iteration":404,"passed_time":14.10555105,"remaining_time":55.55149118,"test":[0.6709448724]},
{"learn":[0.663066483],"iteration":405,"passed_time":14.1427661,"remaining_time":55.52603242,"test":[0.6709421934]},
{"learn":[0.6630443652],"iteration":406,"passed_time":14.18285552,"remaining_time":55.51176619,"test":[0.6709386261]},
{"learn":[0.6630250376],"iteration":407,"passed_time":14.21458769,"remaining_time":55.46476372,"test":[0.6709461564]},
{"learn":[0.6630007822],"iteration":408,"passed_time":14.24035708,"remaining_time":55.39464088,"test":[0.670934384]},
{"learn":[0.6629768728],"iteration":409,"passed_time":14.26711915,"remaining_time":55.32858403,"test":[0.6709312987]},
{"learn":[0.6629528093],"iteration":410,"passed_time":14.29943785,"remaining_time":55.28420133,"test":[0.670931806]},
{"learn":[0.6629260936],"iteration":411,"passed_time":14.32489173,"remaining_time":55.21341763,"test":[0.6709286111]},
{"learn":[0.6629102182],"iteration":412,"passed_time":14.35119075,"remaining_time":55.14610101,"test":[0.6709224729]},
{"learn":[0.6628863488],"iteration":413,"passed_time":14.37946054,"remaining_time":55.08653242,"test":[0.6709236504]},
{"learn":[0.6628648972],"iteration":414,"passed_time":14.41005914,"remaining_time":55.03600899,"test":[0.6709245901]},
{"learn":[0.6628454339],"iteration":415,"passed_time":14.45103793,"remaining_time":55.02510598,"test":[0.6709463437]},
{"learn":[0.6628200274],"iteration":416,"passed_time":14.48428995,"remaining_time":54.98472661,"test":[0.6709567049]},
{"learn":[0.6627942591],"iteration":417,"passed_time":14.5135184,"remaining_time":54.92915339,"test":[0.670945606]},
{"learn":[0.6627744647],"iteration":418,"passed_time":14.53698524,"remaining_time":54.85196578,"test":[0.6709479298]},
{"learn":[0.662765485],"iteration":419,"passed_time":14.56542473,"remaining_time":54.79374067,"test":[0.6709464351]},
{"learn":[0.6627503257],"iteration":420,"passed_time":14.58728594,"remaining_time":54.71098455,"test":[0.6709414048]},
{"learn":[0.6627323029],"iteration":421,"passed_time":14.61501375,"remaining_time":54.65045425,"test":[0.6709414427]},
{"learn":[0.6627111509],"iteration":422,"passed_time":14.64231614,"remaining_time":54.58849302,"test":[0.6709296343]},
{"learn":[0.6626785863],"iteration":423,"passed_time":14.66665432,"remaining_time":54.51567739,"test":[0.670924721]},
{"learn":[0.6626576561],"iteration":424,"passed_time":14.69050441,"remaining_time":54.44128104,"test":[0.670906284]},
{"learn":[0.6626363113],"iteration":425,"passed_time":14.71910475,"remaining_time":54.38467341,"test":[0.6708996826]},
{"learn":[0.6626181065],"iteration":426,"passed_time":14.73941058,"remaining_time":54.2976413,"test":[0.6708987677]},
{"learn":[0.66259794],"iteration":427,"passed_time":14.77242451,"remaining_time":54.25759657,"test":[0.670909526]},
{"learn":[0.6625765658],"iteration":428,"passed_time":14.79088688,"remaining_time":54.1642967,"test":[0.6709033226]},
{"learn":[0.6625526572],"iteration":429,"passed_time":14.82430966,"remaining_time":54.12596783,"test":[0.6708750209]},
{"learn":[0.66253135],"iteration":430,"passed_time":14.84439175,"remaining_time":54.03909666,"test":[0.6708752079]},
{"learn":[0.6625035695],"iteration":431,"passed_time":14.8764415,"remaining_time":53.99597284,"test":[0.6708776566]},
{"learn":[0.662480212],"iteration":432,"passed_time":14.90666075,"remaining_time":53.94627573,"test":[0.6708736133]},
{"learn":[0.6624611632],"iteration":433,"passed_time":14.93845927,"remaining_time":53.90236684,"test":[0.6708754298]},
{"learn":[0.6624332625],"iteration":434,"passed_time":14.98024104,"remaining_time":53.89443041,"test":[0.6708751084]},
{"learn":[0.6624120584],"iteration":435,"passed_time":15.00605075,"remaining_time":53.82904442,"test":[0.6708642042]},
{"learn":[0.6623941719],"iteration":436,"passed_time":15.03384083,"remaining_time":53.77092268,"test":[0.6708610465]},
{"learn":[0.6623766304],"iteration":437,"passed_time":15.05972545,"remaining_time":53.70614417,"test":[0.6708574768]},
{"learn":[0.6623623329],"iteration":438,"passed_time":15.08505889,"remaining_time":53.63958297,"test":[0.6708557953]},
{"learn":[0.6623442925],"iteration":439,"passed_time":15.11080547,"remaining_time":53.57467393,"test":[0.670871378]},
{"learn":[0.6623212715],"iteration":440,"passed_time":15.13466304,"remaining_time":53.50326458,"test":[0.6708640187]},
{"learn":[0.6623025941],"iteration":441,"passed_time":15.16037021,"remaining_time":53.43859001,"test":[0.6708700565]},
{"learn":[0.6622749791],"iteration":442,"passed_time":15.18471062,"remaining_time":53.36928767,"test":[0.6708667534]},
{"learn":[0.6622534499],"iteration":443,"passed_time":15.21140556,"remaining_time":53.30843931,"test":[0.6708675383]},
{"learn":[0.6622305473],"iteration":444,"passed_time":15.23498219,"remaining_time":53.23684787,"test":[0.6708740175]},
{"learn":[0.6622059333],"iteration":445,"passed_time":15.26647355,"remaining_time":53.19304911,"test":[0.6708774523]},
{"learn":[0.6621871707],"iteration":446,"passed_time":15.28793136,"remaining_time":53.11444609,"test":[0.6708697231]},
{"learn":[0.6621638454],"iteration":447,"passed_time":15.31613827,"remaining_time":53.05947899,"test":[0.6708614971]},
{"learn":[0.6621511296],"iteration":448,"passed_time":15.33689091,"remaining_time":52.9788815,"test":[0.6708607946]},
{"learn":[0.6621349978],"iteration":449,"passed_time":15.36674634,"remaining_time":52.92990406,"test":[0.6708740865]},
{"learn":[0.6621120424],"iteration":450,"passed_time":15.393642,"remaining_time":52.87084582,"test":[0.6708729562]},
{"learn":[0.6620958271],"iteration":451,"passed_time":15.42984657,"remaining_time":52.84381082,"test":[0.6708674017]},
{"learn":[0.6620793528],"iteration":452,"passed_time":15.46956188,"remaining_time":52.82872456,"test":[0.6708693088]},
{"learn":[0.6620572713],"iteration":453,"passed_time":15.49032259,"remaining_time":52.74898396,"test":[0.6708712037]},
{"learn":[0.6620395025],"iteration":454,"passed_time":15.52379393,"remaining_time":52.71266289,"test":[0.6708703905]},
{"learn":[0.6620188044],"iteration":455,"passed_time":15.55053135,"remaining_time":52.65355352,"test":[0.6708577595]},
{"learn":[0.6620017347],"iteration":456,"passed_time":15.57735398,"remaining_time":52.59487352,"test":[0.6708493546]},
{"learn":[0.6619811454],"iteration":457,"passed_time":15.60434803,"remaining_time":52.53690973,"test":[0.6708523777]},
{"learn":[0.6619695569],"iteration":458,"passed_time":15.63056555,"remaining_time":52.47647387,"test":[0.6708454134]},
{"learn":[0.661952377],"iteration":459,"passed_time":15.656355,"remaining_time":52.41475368,"test":[0.6708404483]},
{"learn":[0.6619237442],"iteration":460,"passed_time":15.68232112,"remaining_time":52.35377918,"test":[0.6708274771]},
{"learn":[0.6619089407],"iteration":461,"passed_time":15.71164945,"remaining_time":52.30414904,"test":[0.6708244992]},
{"learn":[0.6618886168],"iteration":462,"passed_time":15.7361944,"remaining_time":52.23872743,"test":[0.6708344314]},
{"learn":[0.6618831383],"iteration":463,"passed_time":15.76527735,"remaining_time":52.18850433,"test":[0.6708279081]},
{"learn":[0.6618690774],"iteration":464,"passed_time":15.78652262,"remaining_time":52.11249942,"test":[0.6708258106]},
{"learn":[0.661845878],"iteration":465,"passed_time":15.81756836,"remaining_time":52.06899113,"test":[0.6708049714]},
{"learn":[0.6618290213],"iteration":466,"passed_time":15.83979966,"remaining_time":51.99660146,"test":[0.670810989]},
{"learn":[0.6618050064],"iteration":467,"passed_time":15.87342473,"remaining_time":51.9617237,"test":[0.6708212237]},
{"learn":[0.6617832833],"iteration":468,"passed_time":15.90381555,"remaining_time":51.9162934,"test":[0.6708221741]},
{"learn":[0.6617652311],"iteration":469,"passed_time":15.93502938,"remaining_time":51.87360627,"test":[0.6708259658]},
{"learn":[0.6617443144],"iteration":470,"passed_time":15.96919221,"remaining_time":51.84054117,"test":[0.6708159692]},
{"learn":[0.6617202619],"iteration":471,"passed_time":15.99477329,"remaining_time":51.77968981,"test":[0.6708136212]},
{"learn":[0.6617005831],"iteration":472,"passed_time":16.02279091,"remaining_time":51.72685354,"test":[0.6708224942]},
{"learn":[0.6616824419],"iteration":473,"passed_time":16.04763422,"remaining_time":51.66390258,"test":[0.6708363084]},
{"learn":[0.6616538226],"iteration":474,"passed_time":16.07374645,"remaining_time":51.60518598,"test":[0.670850875]},
{"learn":[0.6616314155],"iteration":475,"passed_time":16.09993591,"remaining_time":51.54685363,"test":[0.6708527236]},
{"learn":[0.6616127861],"iteration":476,"passed_time":16.12811357,"remaining_time":51.49500411,"test":[0.6708453401]},
{"learn":[0.6616029072],"iteration":477,"passed_time":16.15264086,"remaining_time":51.43163051,"test":[0.6708413844]},
{"learn":[0.6615843751],"iteration":478,"passed_time":16.17696751,"remaining_time":51.36778201,"test":[0.6708364569]},
{"learn":[0.661563216],"iteration":479,"passed_time":16.20551145,"remaining_time":51.31745293,"test":[0.6708251774]},
{"learn":[0.6615432257],"iteration":480,"passed_time":16.22860577,"remaining_time":51.2500045,"test":[0.6708154393]},
{"learn":[0.6615263324],"iteration":481,"passed_time":16.25544093,"remaining_time":51.19452144,"test":[0.6708111613]},
{"learn":[0.6615033259],"iteration":482,"passed_time":16.27729221,"remaining_time":51.12350369,"test":[0.6708102339]},
{"learn":[0.661484293],"iteration":483,"passed_time":16.30502335,"remaining_time":51.07110619,"test":[0.6707929623]},
{"learn":[0.6614678231],"iteration":484,"passed_time":16.32842702,"remaining_time":51.00529266,"test":[0.6707900226]},
{"learn":[0.6614463024],"iteration":485,"passed_time":16.36272839,"remaining_time":50.97360242,"test":[0.6707832384]},
{"learn":[0.6614155436],"iteration":486,"passed_time":16.39272506,"remaining_time":50.92852776,"test":[0.6707739118]},
{"learn":[0.6613958945],"iteration":487,"passed_time":16.42636604,"remaining_time":50.89480625,"test":[0.6707737538]},
{"learn":[0.661380611],"iteration":488,"passed_time":16.4597142,"remaining_time":50.86018027,"test":[0.6707730234]},
{"learn":[0.6613677802],"iteration":489,"passed_time":16.48056007,"remaining_time":50.78703206,"test":[0.6707796291]},
{"learn":[0.6613530086],"iteration":490,"passed_time":16.51091177,"remaining_time":50.74331132,"test":[0.670791408]},
{"learn":[0.6613248211],"iteration":491,"passed_time":16.53097438,"remaining_time":50.66810846,"test":[0.6707944906]},
{"learn":[0.6613059359],"iteration":492,"passed_time":16.56161402,"remaining_time":50.62546112,"test":[0.6707835635]},
{"learn":[0.6612729965],"iteration":493,"passed_time":16.5854633,"remaining_time":50.56216139,"test":[0.6707908928]},
{"learn":[0.6612624948],"iteration":494,"passed_time":16.61302735,"remaining_time":50.51031547,"test":[0.670796262]},
{"learn":[0.6612401679],"iteration":495,"passed_time":16.63896978,"remaining_time":50.45365029,"test":[0.6707877825]},
{"learn":[0.6612191637],"iteration":496,"passed_time":16.663707,"remaining_time":50.39346403,"test":[0.6707854132]},
{"learn":[0.6611912219],"iteration":497,"passed_time":16.69040179,"remaining_time":50.33932428,"test":[0.6707756206]},
{"learn":[0.6611773017],"iteration":498,"passed_time":16.71612789,"remaining_time":50.28238068,"test":[0.6707707899]},
{"learn":[0.6611638216],"iteration":499,"passed_time":16.74072553,"remaining_time":50.2221766,"test":[0.6707704386]},
{"learn":[0.6611450533],"iteration":500,"passed_time":16.77346538,"remaining_time":50.18647626,"test":[0.6707621465]},
{"learn":[0.6611179111],"iteration":501,"passed_time":16.80230735,"remaining_time":50.13915621,"test":[0.6707661931]},
{"learn":[0.6610959069],"iteration":502,"passed_time":16.83637769,"remaining_time":50.10747,"test":[0.6707651988]},
{"learn":[0.6610728788],"iteration":503,"passed_time":16.87382128,"remaining_time":50.08578697,"test":[0.6707607827]},
{"learn":[0.6610436668],"iteration":504,"passed_time":16.92151611,"remaining_time":50.09438927,"test":[0.670760242]},
{"learn":[0.6610188976],"iteration":505,"passed_time":16.9898618,"remaining_time":50.16374216,"test":[0.6707506008]},
{"learn":[0.6610030555],"iteration":506,"passed_time":17.03818668,"remaining_time":50.17359509,"test":[0.6707452886]},
{"learn":[0.6609831174],"iteration":507,"passed_time":17.06933058,"remaining_time":50.13275833,"test":[0.6707355189]},
{"learn":[0.6609586562],"iteration":508,"passed_time":17.1106164,"remaining_time":50.12166807,"test":[0.6707312551]},
{"learn":[0.660935882],"iteration":509,"passed_time":17.14537899,"remaining_time":50.09140137,"test":[0.6707199485]},
{"learn":[0.6609202024],"iteration":510,"passed_time":17.19066307,"remaining_time":50.09177556,"test":[0.6707131947]},
{"learn":[0.6609011137],"iteration":511,"passed_time":17.21958034,"remaining_time":50.04440537,"test":[0.6707154112]},
{"learn":[0.6608726737],"iteration":512,"passed_time":17.24756917,"remaining_time":49.99441591,"test":[0.6706982346]},
{"learn":[0.6608608849],"iteration":513,"passed_time":17.27150822,"remaining_time":49.93280391,"test":[0.6706988941]},
{"learn":[0.6608387256],"iteration":514,"passed_time":17.29800365,"remaining_time":49.87870957,"test":[0.6706989098]},
{"learn":[0.6608136063],"iteration":515,"passed_time":17.34332283,"remaining_time":49.87885868,"test":[0.670693306]},
{"learn":[0.6607946343],"iteration":516,"passed_time":17.37393636,"remaining_time":49.83664916,"test":[0.6706944515]},
{"learn":[0.6607703935],"iteration":517,"passed_time":17.4173655,"remaining_time":49.83114994,"test":[0.6706899688]},
{"learn":[0.6607509625],"iteration":518,"passed_time":17.46008645,"remaining_time":49.82348368,"test":[0.6706909374]},
{"learn":[0.6607238109],"iteration":519,"passed_time":17.4906988,"remaining_time":49.78121967,"test":[0.6706855074]},
{"learn":[0.6606999858],"iteration":520,"passed_time":17.5186435,"remaining_time":49.7314275,"test":[0.6706787779]},
{"learn":[0.6606813873],"iteration":521,"passed_time":17.54613056,"remaining_time":49.6804233,"test":[0.6706737082]},
{"learn":[0.6606610372],"iteration":522,"passed_time":17.57100039,"remaining_time":49.62211774,"test":[0.6706761225]},
{"learn":[0.660638456],"iteration":523,"passed_time":17.60084283,"remaining_time":49.5779466,"test":[0.670685455]},
{"learn":[0.6606156483],"iteration":524,"passed_time":17.62599925,"remaining_time":49.52066456,"test":[0.6706693855]},
{"learn":[0.6605968623],"iteration":525,"passed_time":17.65519625,"remaining_time":49.47482751,"test":[0.6706647216]},
{"learn":[0.6605735776],"iteration":526,"passed_time":17.67910836,"remaining_time":49.41428199,"test":[0.6706569188]},
{"learn":[0.6605517294],"iteration":527,"passed_time":17.70744827,"remaining_time":49.36621942,"test":[0.6706549134]},
{"learn":[0.6605309239],"iteration":528,"passed_time":17.72943083,"remaining_time":49.3005534,"test":[0.6706547978]},
{"learn":[0.6605086434],"iteration":529,"passed_time":17.75830336,"remaining_time":49.25416215,"test":[0.6706564214]},
{"learn":[0.6604803349],"iteration":530,"passed_time":17.78141858,"remaining_time":49.19190939,"test":[0.6706559196]},
{"learn":[0.6604566326],"iteration":531,"passed_time":17.80870208,"remaining_time":49.14130574,"test":[0.6706515072]},
{"learn":[0.6604430839],"iteration":532,"passed_time":17.82904188,"remaining_time":49.07167811,"test":[0.6706474616]},
{"learn":[0.6604273738],"iteration":533,"passed_time":17.86246645,"remaining_time":49.03815696,"test":[0.6706424204]},
{"learn":[0.6604048016],"iteration":534,"passed_time":17.90552779,"remaining_time":49.03102469,"test":[0.6706520008]},
{"learn":[0.6603845173],"iteration":535,"passed_time":18.02843143,"remaining_time":49.24183511,"test":[0.6706448306]},
{"learn":[0.6603669212],"iteration":536,"passed_time":18.07245966,"remaining_time":49.23651485,"test":[0.6706415789]},
{"learn":[0.6603488983],"iteration":537,"passed_time":18.10631942,"remaining_time":49.20341819,"test":[0.6706305359]},
{"learn":[0.6603176881],"iteration":538,"passed_time":18.13531438,"remaining_time":49.1571323,"test":[0.6706152774]},
{"learn":[0.6602953862],"iteration":539,"passed_time":18.16575265,"remaining_time":49.11481272,"test":[0.670616585]},
{"learn":[0.6602672025],"iteration":540,"passed_time":18.20025584,"remaining_time":49.08349958,"test":[0.6705963243]},
{"learn":[0.6602568636],"iteration":541,"passed_time":18.22381751,"remaining_time":49.02274158,"test":[0.6706027368]},
{"learn":[0.660235705],"iteration":542,"passed_time":18.25438575,"remaining_time":48.98092088,"test":[0.6706003522]},
{"learn":[0.6602152295],"iteration":543,"passed_time":18.28070524,"remaining_time":48.9277699,"test":[0.6706044301]},
{"learn":[0.6601897709],"iteration":544,"passed_time":18.30768805,"remaining_time":48.87648827,"test":[0.6706047241]},
{"learn":[0.6601683731],"iteration":545,"passed_time":18.33807201,"remaining_time":48.83435294,"test":[0.6706038235]},
{"learn":[0.6601472267],"iteration":546,"passed_time":18.36776304,"remaining_time":48.79041993,"test":[0.6706026913]},
{"learn":[0.6601262337],"iteration":547,"passed_time":18.41134623,"remaining_time":48.78334803,"test":[0.6705845786]},
{"learn":[0.6601119991],"iteration":548,"passed_time":18.44405381,"remaining_time":48.74739905,"test":[0.6705873967]},
{"learn":[0.6600869973],"iteration":549,"passed_time":18.47010718,"remaining_time":48.69391893,"test":[0.6705755426]},
{"learn":[0.6600667497],"iteration":550,"passed_time":18.5036553,"remaining_time":48.66024779,"test":[0.6705715731]},
{"learn":[0.6600397508],"iteration":551,"passed_time":18.53164471,"remaining_time":48.61199556,"test":[0.6705757153]},
{"learn":[0.660016863],"iteration":552,"passed_time":18.5577607,"remaining_time":48.55891452,"test":[0.6705516814]},
{"learn":[0.6599933158],"iteration":553,"passed_time":18.58492994,"remaining_time":48.50867995,"test":[0.6705530864]},
{"learn":[0.6599632649],"iteration":554,"passed_time":18.62562092,"remaining_time":48.49373376,"test":[0.6705552479]},
{"learn":[0.6599446007],"iteration":555,"passed_time":18.65010209,"remaining_time":48.43659608,"test":[0.6705563336]},
{"learn":[0.6599138126],"iteration":556,"passed_time":18.67796421,"remaining_time":48.38833458,"test":[0.6705718544]},
{"learn":[0.6598965504],"iteration":557,"passed_time":18.70319381,"remaining_time":48.33334314,"test":[0.6705688384]},
{"learn":[0.6598785723],"iteration":558,"passed_time":18.72995694,"remaining_time":48.28241136,"test":[0.6705641528]},
{"learn":[0.659860838],"iteration":559,"passed_time":18.75657945,"remaining_time":48.23120429,"test":[0.6705628467]},
{"learn":[0.6598408724],"iteration":560,"passed_time":18.78181322,"remaining_time":48.17652269,"test":[0.670558488]},
{"learn":[0.6598244857],"iteration":561,"passed_time":18.80867415,"remaining_time":48.12610931,"test":[0.6705544404]},
{"learn":[0.6598082469],"iteration":562,"passed_time":18.83488797,"remaining_time":48.0741279,"test":[0.6705617451]},
{"learn":[0.6597851673],"iteration":563,"passed_time":18.86939449,"remaining_time":48.04335193,"test":[0.6705631717]},
{"learn":[0.6597683521],"iteration":564,"passed_time":18.90235988,"remaining_time":48.00864854,"test":[0.6705636201]},
{"learn":[0.6597479006],"iteration":565,"passed_time":18.93001053,"remaining_time":47.96048604,"test":[0.6705537522]},
{"learn":[0.6597310938],"iteration":566,"passed_time":18.95858079,"remaining_time":47.91472006,"test":[0.670555083]},
{"learn":[0.6597096581],"iteration":567,"passed_time":18.9833487,"remaining_time":47.85942842,"test":[0.6705524541]},
{"learn":[0.6596862311],"iteration":568,"passed_time":19.0162481,"remaining_time":47.82469425,"test":[0.6705503132]},
{"learn":[0.6596574779],"iteration":569,"passed_time":19.03781666,"remaining_time":47.76154004,"test":[0.6705354602]},
{"learn":[0.6596385418],"iteration":570,"passed_time":19.0681355,"remaining_time":47.72043018,"test":[0.6705387012]},
{"learn":[0.6596189903],"iteration":571,"passed_time":19.09073714,"remaining_time":47.66009201,"test":[0.6705411923]},
{"learn":[0.65959275],"iteration":572,"passed_time":19.11146842,"remaining_time":47.59522765,"test":[0.6705390018]},
{"learn":[0.6595730662],"iteration":573,"passed_time":19.141368,"remaining_time":47.55329403,"test":[0.6705354939]},
{"learn":[0.6595566809],"iteration":574,"passed_time":19.16428373,"remaining_time":47.49409447,"test":[0.670531296]},
{"learn":[0.6595365076],"iteration":575,"passed_time":19.19652276,"remaining_time":47.45807015,"test":[0.6705377163]},
{"learn":[0.6595163446],"iteration":576,"passed_time":19.21727405,"remaining_time":47.39372785,"test":[0.6705248875]},
{"learn":[0.6594816637],"iteration":577,"passed_time":19.24969594,"remaining_time":47.35824848,"test":[0.6705252902]},
{"learn":[0.6594570142],"iteration":578,"passed_time":19.27445137,"remaining_time":47.30396442,"test":[0.6705181562]},
{"learn":[0.6594353055],"iteration":579,"passed_time":19.29822455,"remaining_time":47.24737734,"test":[0.6705123446]},
{"learn":[0.6594162362],"iteration":580,"passed_time":19.32403522,"remaining_time":47.19587948,"test":[0.6705128345]},
{"learn":[0.659395036],"iteration":581,"passed_time":19.35739555,"remaining_time":47.16286408,"test":[0.6705173712]},
{"learn":[0.6593798831],"iteration":582,"passed_time":19.39112791,"remaining_time":47.13075172,"test":[0.670541941]},
{"learn":[0.6593556719],"iteration":583,"passed_time":19.42704318,"remaining_time":47.1039266,"test":[0.6705463243]},
{"learn":[0.6593292627],"iteration":584,"passed_time":19.46022169,"remaining_time":47.07045077,"test":[0.6705513215]},
{"learn":[0.6592976737],"iteration":585,"passed_time":19.48332075,"remaining_time":47.01265452,"test":[0.6705455889]},
{"learn":[0.6592754841],"iteration":586,"passed_time":19.5115578,"remaining_time":46.9673444,"test":[0.6705408087]},
{"learn":[0.6592510441],"iteration":587,"passed_time":19.54275193,"remaining_time":46.92919341,"test":[0.6705510193]},
{"learn":[0.6592290326],"iteration":588,"passed_time":19.56411389,"remaining_time":46.86751222,"test":[0.6705456751]},
{"learn":[0.6592097404],"iteration":589,"passed_time":19.59700884,"remaining_time":46.8335296,"test":[0.6705402427]},
{"learn":[0.6591876204],"iteration":590,"passed_time":19.62169623,"remaining_time":46.77998306,"test":[0.6705443402]},
{"learn":[0.6591705995],"iteration":591,"passed_time":19.64747626,"remaining_time":46.72913272,"test":[0.67054441]},
{"learn":[0.6591456195],"iteration":592,"passed_time":19.67090184,"remaining_time":46.67278059,"test":[0.6705441955]},
{"learn":[0.6591107122],"iteration":593,"passed_time":19.69910949,"remaining_time":46.62785848,"test":[0.6705319356]},
{"learn":[0.6590819533],"iteration":594,"passed_time":19.72694709,"remaining_time":46.58211876,"test":[0.6705358843]},
{"learn":[0.6590551327],"iteration":595,"passed_time":19.7530808,"remaining_time":46.53242523,"test":[0.6705334396]},
{"learn":[0.6590373916],"iteration":596,"passed_time":19.77835609,"remaining_time":46.48079328,"test":[0.6705320462]},
{"learn":[0.6590177149],"iteration":597,"passed_time":19.80378809,"remaining_time":46.4296169,"test":[0.6705332043]},
{"learn":[0.6589946095],"iteration":598,"passed_time":19.83052585,"remaining_time":46.38158048,"test":[0.6705328363]},
{"learn":[0.6589697628],"iteration":599,"passed_time":19.8579153,"remaining_time":46.33513569,"test":[0.6705315638]},
{"learn":[0.6589442269],"iteration":600,"passed_time":19.89600309,"remaining_time":46.31365777,"test":[0.6705274435]},
{"learn":[0.6589182437],"iteration":601,"passed_time":19.92518872,"remaining_time":46.27145155,"test":[0.670509808]},
{"learn":[0.6588837179],"iteration":602,"passed_time":19.95754179,"remaining_time":46.23662666,"test":[0.6705077789]},
{"learn":[0.6588674101],"iteration":603,"passed_time":19.99116426,"remaining_time":46.20474388,"test":[0.6705212132]},
{"learn":[0.6588406916],"iteration":604,"passed_time":20.01900069,"remaining_time":46.15951398,"test":[0.6705098442]},
{"learn":[0.6588149945],"iteration":605,"passed_time":20.04735837,"remaining_time":46.11554053,"test":[0.6705061509]},
{"learn":[0.6587866031],"iteration":606,"passed_time":20.07232044,"remaining_time":46.06382599,"test":[0.6705003071]},
{"learn":[0.6587636648],"iteration":607,"passed_time":20.09871086,"remaining_time":46.01546959,"test":[0.6705045031]},
{"learn":[0.6587502469],"iteration":608,"passed_time":20.12348304,"remaining_time":45.96348917,"test":[0.6705083194]},
{"learn":[0.6587292784],"iteration":609,"passed_time":20.14920752,"remaining_time":45.91376797,"test":[0.6705329997]},
{"learn":[0.6587104112],"iteration":610,"passed_time":20.17662353,"remaining_time":45.86797068,"test":[0.6705269987]},
{"learn":[0.6586953782],"iteration":611,"passed_time":20.20202219,"remaining_time":45.81765818,"test":[0.6705315607]},
{"learn":[0.6586641191],"iteration":612,"passed_time":20.23050051,"remaining_time":45.77439512,"test":[0.6705142835]},
{"learn":[0.6586450136],"iteration":613,"passed_time":20.25381994,"remaining_time":45.71953492,"test":[0.6705165015]},
{"learn":[0.6586136263],"iteration":614,"passed_time":20.28518384,"remaining_time":45.68289369,"test":[0.6705001061]},
{"learn":[0.6585862768],"iteration":615,"passed_time":20.3078175,"remaining_time":45.62665489,"test":[0.6705013916]},
{"learn":[0.6585585235],"iteration":616,"passed_time":20.33878033,"remaining_time":45.5891948,"test":[0.6705037253]},
{"learn":[0.6585371631],"iteration":617,"passed_time":20.36122842,"remaining_time":45.53271469,"test":[0.67049647]},
{"learn":[0.6585092632],"iteration":618,"passed_time":20.3943397,"remaining_time":45.50013429,"test":[0.6705005632]},
{"learn":[0.6584914317],"iteration":619,"passed_time":20.42384285,"remaining_time":45.45952119,"test":[0.6704957943]},
{"learn":[0.6584662432],"iteration":620,"passed_time":20.45411533,"remaining_time":45.42065225,"test":[0.6704955333]},
{"learn":[0.6584454668],"iteration":621,"passed_time":20.488223,"remaining_time":45.39030754,"test":[0.6704961207]},
{"learn":[0.6584249408],"iteration":622,"passed_time":20.51043528,"remaining_time":45.33365872,"test":[0.6704921459]},
{"learn":[0.6583931228],"iteration":623,"passed_time":20.54384208,"remaining_time":45.30180561,"test":[0.6704751713]},
{"learn":[0.6583660767],"iteration":624,"passed_time":20.56912557,"remaining_time":45.25207624,"test":[0.6704753101]},
{"learn":[0.658354264],"iteration":625,"passed_time":20.59414123,"remaining_time":45.20183714,"test":[0.6704620888]},
{"learn":[0.6583253625],"iteration":626,"passed_time":20.61901142,"remaining_time":45.15135993,"test":[0.6704604282]},
{"learn":[0.6582968632],"iteration":627,"passed_time":20.6468542,"remaining_time":45.10745855,"test":[0.6704663192]},
{"learn":[0.6582687399],"iteration":628,"passed_time":20.67583093,"remaining_time":45.06607981,"test":[0.6704680085]},
{"learn":[0.658242535],"iteration":629,"passed_time":20.7010198,"remaining_time":45.01650336,"test":[0.670453228]},
{"learn":[0.6582199874],"iteration":630,"passed_time":20.72783977,"remaining_time":44.97054302,"test":[0.6704577785]},
{"learn":[0.6581918101],"iteration":631,"passed_time":20.75222724,"remaining_time":44.91937795,"test":[0.67046675]},
{"learn":[0.6581735218],"iteration":632,"passed_time":20.78264004,"remaining_time":44.88130954,"test":[0.6704731863]},
{"learn":[0.6581445869],"iteration":633,"passed_time":20.80459182,"remaining_time":44.82503538,"test":[0.6704811116]},
{"learn":[0.6581202427],"iteration":634,"passed_time":20.83717209,"remaining_time":44.79171637,"test":[0.6704839644]},
{"learn":[0.6580977862],"iteration":635,"passed_time":20.86231353,"remaining_time":44.74244599,"test":[0.6704854798]},
{"learn":[0.6580724179],"iteration":636,"passed_time":20.89269601,"remaining_time":44.70446572,"test":[0.6704835837]},
{"learn":[0.6580426322],"iteration":637,"passed_time":20.93117347,"remaining_time":44.68379039,"test":[0.6704736198]},
{"learn":[0.6580111256],"iteration":638,"passed_time":20.96066949,"remaining_time":44.64392985,"test":[0.6704640242]},
{"learn":[0.6579834747],"iteration":639,"passed_time":20.9941179,"remaining_time":44.61250055,"test":[0.670465663]},
{"learn":[0.6579541367],"iteration":640,"passed_time":21.0224519,"remaining_time":44.57022174,"test":[0.6704646829]},
{"learn":[0.6579254503],"iteration":641,"passed_time":21.0522529,"remaining_time":44.53108946,"test":[0.6704600961]},
{"learn":[0.657898555],"iteration":642,"passed_time":21.08260618,"remaining_time":44.49315178,"test":[0.6704643207]},
{"learn":[0.6578676875],"iteration":643,"passed_time":21.10716702,"remaining_time":44.44304112,"test":[0.6704600533]},
{"learn":[0.6578324163],"iteration":644,"passed_time":21.13594828,"remaining_time":44.40187584,"test":[0.6704614691]},
{"learn":[0.6578062223],"iteration":645,"passed_time":21.1601277,"remaining_time":44.35110357,"test":[0.6704728212]},
{"learn":[0.6577760631],"iteration":646,"passed_time":21.18552999,"remaining_time":44.30297075,"test":[0.6704758731]},
{"learn":[0.6577483474],"iteration":647,"passed_time":21.21048648,"remaining_time":44.25397797,"test":[0.6704833026]},
{"learn":[0.6577249642],"iteration":648,"passed_time":21.23686209,"remaining_time":44.20801337,"test":[0.6704767664]},
{"learn":[0.6576974966],"iteration":649,"passed_time":21.26287585,"remaining_time":44.16135753,"test":[0.6704702727]},
{"learn":[0.657675114],"iteration":650,"passed_time":21.28806218,"remaining_time":44.11305051,"test":[0.6704671372]},
{"learn":[0.6576447891],"iteration":651,"passed_time":21.31506267,"remaining_time":44.06856515,"test":[0.6704699936]},
{"learn":[0.6576102356],"iteration":652,"passed_time":21.3435081,"remaining_time":44.02711394,"test":[0.6704587989]},
{"learn":[0.6575793887],"iteration":653,"passed_time":21.37776713,"remaining_time":43.99766753,"test":[0.6704637668]},
{"learn":[0.6575543309],"iteration":654,"passed_time":21.40301154,"remaining_time":43.94969545,"test":[0.6704653717]},
{"learn":[0.6575340787],"iteration":655,"passed_time":21.44023109,"remaining_time":43.92632711,"test":[0.6704598273]},
{"learn":[0.6575061464],"iteration":656,"passed_time":21.4778965,"remaining_time":43.903828,"test":[0.6704522865]},
{"learn":[0.657476113],"iteration":657,"passed_time":21.50245582,"remaining_time":43.85455275,"test":[0.6704558586]},
{"learn":[0.6574447014],"iteration":658,"passed_time":21.53379663,"remaining_time":43.81915217,"test":[0.6704466331]},
{"learn":[0.6574247361],"iteration":659,"passed_time":21.55955041,"remaining_time":43.77242053,"test":[0.6704405886]},
{"learn":[0.6574034983],"iteration":660,"passed_time":21.58626671,"remaining_time":43.72770215,"test":[0.6704463767]},
{"learn":[0.6573783832],"iteration":661,"passed_time":21.61183918,"remaining_time":43.68072633,"test":[0.6704475216]},
{"learn":[0.657357694],"iteration":662,"passed_time":21.6373217,"remaining_time":43.63363366,"test":[0.6704572386]},
{"learn":[0.6573411592],"iteration":663,"passed_time":21.66283476,"remaining_time":43.58666753,"test":[0.6704658153]},
{"learn":[0.6573118559],"iteration":664,"passed_time":21.68841321,"remaining_time":43.5398972,"test":[0.6704600945]},
{"learn":[0.6572819076],"iteration":665,"passed_time":21.71420973,"remaining_time":43.4936273,"test":[0.6704561998]},
{"learn":[0.6572430097],"iteration":666,"passed_time":21.74213421,"remaining_time":43.45167151,"test":[0.6704535154]},
{"learn":[0.6572160391],"iteration":667,"passed_time":21.77174463,"remaining_time":43.41311953,"test":[0.6704413781]},
{"learn":[0.6571931413],"iteration":668,"passed_time":21.81895309,"remaining_time":43.40960622,"test":[0.6704450013]},
{"learn":[0.6571737099],"iteration":669,"passed_time":21.84627583,"remaining_time":43.36648784,"test":[0.6704422199]},
{"learn":[0.6571532872],"iteration":670,"passed_time":21.88834724,"remaining_time":43.35262814,"test":[0.67044342]},
{"learn":[0.6571208939],"iteration":671,"passed_time":21.93403139,"remaining_time":43.34582395,"test":[0.6704415341]},
{"learn":[0.6570887673],"iteration":672,"passed_time":21.9714274,"remaining_time":43.32256191,"test":[0.6704439539]},
{"learn":[0.6570633692],"iteration":673,"passed_time":22.01942449,"remaining_time":43.32011406,"test":[0.6704498197]},
{"learn":[0.6570454361],"iteration":674,"passed_time":22.05319867,"remaining_time":43.2896122,"test":[0.6704452194]},
{"learn":[0.6570231031],"iteration":675,"passed_time":22.09079747,"remaining_time":43.26659149,"test":[0.6704366524]},
{"learn":[0.6570052089],"iteration":676,"passed_time":22.14192346,"remaining_time":43.26996269,"test":[0.6704427124]},
{"learn":[0.6569855794],"iteration":677,"passed_time":22.17624471,"remaining_time":43.24040635,"test":[0.6704395579]},
{"learn":[0.6569579709],"iteration":678,"passed_time":22.213192,"remaining_time":43.21594497,"test":[0.6704401246]},
{"learn":[0.6569333354],"iteration":679,"passed_time":22.23966403,"remaining_time":43.17111253,"test":[0.6704415621]},
{"learn":[0.6569069617],"iteration":680,"passed_time":22.27051241,"remaining_time":43.13481039,"test":[0.6704341343]},
{"learn":[0.6568931857],"iteration":681,"passed_time":22.29625075,"remaining_time":43.08864881,"test":[0.6704369615]},
{"learn":[0.6568734532],"iteration":682,"passed_time":22.32160622,"remaining_time":43.04180877,"test":[0.6704357425]},
{"learn":[0.6568435196],"iteration":683,"passed_time":22.35059872,"remaining_time":43.00202911,"test":[0.6704294622]},
{"learn":[0.6568108038],"iteration":684,"passed_time":22.37956576,"remaining_time":42.96223208,"test":[0.6704289794]},
{"learn":[0.6567811374],"iteration":685,"passed_time":22.41993338,"remaining_time":42.94430389,"test":[0.6704272409]},
{"learn":[0.6567467284],"iteration":686,"passed_time":22.45285267,"remaining_time":42.91207504,"test":[0.6704101162]},
{"learn":[0.6567172734],"iteration":687,"passed_time":22.4848431,"remaining_time":42.8780729,"test":[0.6704069439]},
{"learn":[0.6566967606],"iteration":688,"passed_time":22.51193834,"remaining_time":42.83476221,"test":[0.6704100747]},
{"learn":[0.6566720128],"iteration":689,"passed_time":22.53798671,"remaining_time":42.78951101,"test":[0.6704122261]},
{"learn":[0.6566441608],"iteration":690,"passed_time":22.57108439,"remaining_time":42.75766928,"test":[0.6704137826]},
{"learn":[0.6566172287],"iteration":691,"passed_time":22.59836588,"remaining_time":42.7148303,"test":[0.6704207952]},
{"learn":[0.6565952549],"iteration":692,"passed_time":22.62447507,"remaining_time":42.66982528,"test":[0.6704154834]},
{"learn":[0.6565702687],"iteration":693,"passed_time":22.65349415,"remaining_time":42.63035067,"test":[0.6704253514]},
{"learn":[0.6565392213],"iteration":694,"passed_time":22.68028991,"remaining_time":42.58673141,"test":[0.6704155636]},
{"learn":[0.6565157938],"iteration":695,"passed_time":22.70844406,"remaining_time":42.54570555,"test":[0.6704141298]},
{"learn":[0.6564902789],"iteration":696,"passed_time":22.73944116,"remaining_time":42.51003133,"test":[0.6704207635]},
{"learn":[0.6564644734],"iteration":697,"passed_time":22.7613976,"remaining_time":42.45750671,"test":[0.6704268341]},
{"learn":[0.6564349549],"iteration":698,"passed_time":22.79216825,"remaining_time":42.42147482,"test":[0.6704243126]},
{"learn":[0.6564046572],"iteration":699,"passed_time":22.8167121,"remaining_time":42.37389389,"test":[0.6704235165]},
{"learn":[0.6563744107],"iteration":700,"passed_time":22.84507296,"remaining_time":42.33345189,"test":[0.6704257736]},
{"learn":[0.6563525063],"iteration":701,"passed_time":22.87088832,"remaining_time":42.28833766,"test":[0.6704247758]},
{"learn":[0.6563189867],"iteration":702,"passed_time":22.90238907,"remaining_time":42.25376759,"test":[0.6704331799]},
{"learn":[0.6562939062],"iteration":703,"passed_time":22.94246813,"remaining_time":42.23499815,"test":[0.6704252722]},
{"learn":[0.6562739297],"iteration":704,"passed_time":22.97441688,"remaining_time":42.20123385,"test":[0.6704146644]},
{"learn":[0.656256438],"iteration":705,"passed_time":23.00262167,"remaining_time":42.16061253,"test":[0.6704164122]},
{"learn":[0.6562366475],"iteration":706,"passed_time":23.033437,"remaining_time":42.12480062,"test":[0.6704118954]},
{"learn":[0.6562073096],"iteration":707,"passed_time":23.0545813,"remaining_time":42.07135458,"test":[0.6704043129]},
{"learn":[0.6561864222],"iteration":708,"passed_time":23.08699831,"remaining_time":42.03852584,"test":[0.6703978198]},
{"learn":[0.6561578826],"iteration":709,"passed_time":23.11590694,"remaining_time":41.99932387,"test":[0.6703935976]},
{"learn":[0.6561208567],"iteration":710,"passed_time":23.14362702,"remaining_time":41.9579961,"test":[0.6703839683]},
{"learn":[0.6560924703],"iteration":711,"passed_time":23.16985155,"remaining_time":41.91400112,"test":[0.6703843723]},
{"learn":[0.6560656907],"iteration":712,"passed_time":23.19510285,"remaining_time":41.86829925,"test":[0.6703879502]},
{"learn":[0.6560362588],"iteration":713,"passed_time":23.23034771,"remaining_time":41.84065429,"test":[0.6703895978]},
{"learn":[0.6560124527],"iteration":714,"passed_time":23.25923754,"remaining_time":41.80156678,"test":[0.6703894359]},
{"learn":[0.6559875055],"iteration":715,"passed_time":23.28703452,"remaining_time":41.76054794,"test":[0.6703928777]},
{"learn":[0.6559547281],"iteration":716,"passed_time":23.31161175,"remaining_time":41.71380457,"test":[0.6703933128]},
{"learn":[0.6559230866],"iteration":717,"passed_time":23.34170355,"remaining_time":41.67696929,"test":[0.6703844355]},
{"learn":[0.6558924823],"iteration":718,"passed_time":23.37263658,"remaining_time":41.64165155,"test":[0.6703825151]},
{"learn":[0.6558676469],"iteration":719,"passed_time":23.40571088,"remaining_time":41.61015268,"test":[0.6703983542]},
{"learn":[0.6558459277],"iteration":720,"passed_time":23.4389719,"remaining_time":41.57898067,"test":[0.670399556]},
{"learn":[0.6558149638],"iteration":721,"passed_time":23.48304084,"remaining_time":41.56693379,"test":[0.6703931808]},
{"learn":[0.6557812248],"iteration":722,"passed_time":23.50734531,"remaining_time":41.5198893,"test":[0.6703886918]},
{"learn":[0.6557546502],"iteration":723,"passed_time":23.54055835,"remaining_time":41.48860836,"test":[0.6703847574]},
{"learn":[0.6557274948],"iteration":724,"passed_time":23.56652491,"remaining_time":41.44457829,"test":[0.6703885941]},
{"learn":[0.6557044723],"iteration":725,"passed_time":23.59580183,"remaining_time":41.40640708,"test":[0.6703788615]},
{"learn":[0.6556751811],"iteration":726,"passed_time":23.62334313,"remaining_time":41.36522119,"test":[0.6703799906]},
{"learn":[0.6556539158],"iteration":727,"passed_time":23.64879831,"remaining_time":41.32042782,"test":[0.6703774518]},
{"learn":[0.6556182915],"iteration":728,"passed_time":23.67755213,"remaining_time":41.28143862,"test":[0.6703783496]},
{"learn":[0.6555977079],"iteration":729,"passed_time":23.70012944,"remaining_time":41.23173204,"test":[0.6703648854]},
{"learn":[0.6555667903],"iteration":730,"passed_time":23.72866102,"remaining_time":41.19243615,"test":[0.6703716654]},
{"learn":[0.6555394075],"iteration":731,"passed_time":23.75226732,"remaining_time":41.14463793,"test":[0.6703550938]},
{"learn":[0.6555122742],"iteration":732,"passed_time":23.7844108,"remaining_time":41.11166233,"test":[0.6703467057]},
{"learn":[0.6554814941],"iteration":733,"passed_time":23.80747563,"remaining_time":41.06303017,"test":[0.6703484503]},
{"learn":[0.6554517373],"iteration":734,"passed_time":23.84023587,"remaining_time":41.03115425,"test":[0.6703549183]},
{"learn":[0.655429552],"iteration":735,"passed_time":23.87042124,"remaining_time":40.99485387,"test":[0.6703501504]},
{"learn":[0.655396579],"iteration":736,"passed_time":23.9087808,"remaining_time":40.97257823,"test":[0.6703672622]},
{"learn":[0.6553735864],"iteration":737,"passed_time":23.94161529,"remaining_time":40.94081097,"test":[0.6703560249]},
{"learn":[0.6553472597],"iteration":738,"passed_time":23.97478791,"remaining_time":40.90961779,"test":[0.6703547155]},
{"learn":[0.6553252832],"iteration":739,"passed_time":24.00628859,"remaining_time":40.87557247,"test":[0.6703593236]},
{"learn":[0.6552971659],"iteration":740,"passed_time":24.03623034,"remaining_time":40.83888528,"test":[0.6703606827]},
{"learn":[0.6552763852],"iteration":741,"passed_time":24.06404686,"remaining_time":40.79861313,"test":[0.6703511404]},
{"learn":[0.6552488203],"iteration":742,"passed_time":24.09270947,"remaining_time":40.75980593,"test":[0.6703431646]},
{"learn":[0.65521229],"iteration":743,"passed_time":24.12724624,"remaining_time":40.73094258,"test":[0.6703475116]},
{"learn":[0.6551949744],"iteration":744,"passed_time":24.15397955,"remaining_time":40.68891857,"test":[0.6703483634]},
{"learn":[0.6551673797],"iteration":745,"passed_time":24.17955779,"remaining_time":40.64499392,"test":[0.6703475713]},
{"learn":[0.6551421856],"iteration":746,"passed_time":24.20715317,"remaining_time":40.60450191,"test":[0.670360457]},
{"learn":[0.6551255516],"iteration":747,"passed_time":24.23336836,"remaining_time":40.5617342,"test":[0.6703664352]},
{"learn":[0.6551019608],"iteration":748,"passed_time":24.2614437,"remaining_time":40.52211759,"test":[0.6703617612]},
{"learn":[0.6550758728],"iteration":749,"passed_time":24.29512083,"remaining_time":40.49186805,"test":[0.6703669926]},
{"learn":[0.655051966],"iteration":750,"passed_time":24.31839238,"remaining_time":40.44430371,"test":[0.6703670837]},
{"learn":[0.6550351058],"iteration":751,"passed_time":24.34977118,"remaining_time":40.41025856,"test":[0.6703706628]},
{"learn":[0.6549998756],"iteration":752,"passed_time":24.3762114,"remaining_time":40.36804198,"test":[0.670369618]},
{"learn":[0.6549721212],"iteration":753,"passed_time":24.40831154,"remaining_time":40.3352204,"test":[0.6703692351]},
{"learn":[0.6549401744],"iteration":754,"passed_time":24.44267281,"remaining_time":40.30612934,"test":[0.6703624433]},
{"learn":[0.6549207325],"iteration":755,"passed_time":24.47460721,"remaining_time":40.27303091,"test":[0.6703686285]},
{"learn":[0.6548900891],"iteration":756,"passed_time":24.50826603,"remaining_time":40.24276708,"test":[0.6703598432]},
{"learn":[0.6548682731],"iteration":757,"passed_time":24.54826542,"remaining_time":40.22288345,"test":[0.6703618766]},
{"learn":[0.6548418938],"iteration":758,"passed_time":24.57546587,"remaining_time":40.18201996,"test":[0.6703694148]},
{"learn":[0.6548234717],"iteration":759,"passed_time":24.60502723,"remaining_time":40.14504442,"test":[0.6703683652]},
{"learn":[0.6547996833],"iteration":760,"passed_time":24.63261096,"remaining_time":40.10486856,"test":[0.6703604855]},
{"learn":[0.6547726174],"iteration":761,"passed_time":24.66001655,"remaining_time":40.06443634,"test":[0.6703758987]},
{"learn":[0.6547509314],"iteration":762,"passed_time":24.68929907,"remaining_time":40.02708119,"test":[0.6703773302]},
{"learn":[0.6547168175],"iteration":763,"passed_time":24.71425118,"remaining_time":39.98274144,"test":[0.6703641028]},
{"learn":[0.6546907846],"iteration":764,"passed_time":24.74589169,"remaining_time":39.94924999,"test":[0.6703649602]},
{"learn":[0.6546671611],"iteration":765,"passed_time":24.76625006,"remaining_time":39.89758822,"test":[0.6703567811]},
{"learn":[0.6546475893],"iteration":766,"passed_time":24.79734832,"remaining_time":39.86327312,"test":[0.6703544688]},
{"learn":[0.6546206223],"iteration":767,"passed_time":24.82531049,"remaining_time":39.82393558,"test":[0.6703611821]},
{"learn":[0.6545874193],"iteration":768,"passed_time":24.85435247,"remaining_time":39.78635616,"test":[0.6703527821]},
{"learn":[0.6545620629],"iteration":769,"passed_time":24.88095966,"remaining_time":39.74490958,"test":[0.6703523616]},
{"learn":[0.6545346297],"iteration":770,"passed_time":24.90935211,"remaining_time":39.70634726,"test":[0.6703616298]},
{"learn":[0.6545172316],"iteration":771,"passed_time":24.94098876,"remaining_time":39.67297175,"test":[0.6703603551]},
{"learn":[0.6544943049],"iteration":772,"passed_time":24.97035098,"remaining_time":39.6359905,"test":[0.6703675655]},
{"learn":[0.6544632323],"iteration":773,"passed_time":25.00434422,"remaining_time":39.60636436,"test":[0.6703582411]},
{"learn":[0.6544384097],"iteration":774,"passed_time":25.03067441,"remaining_time":39.56461439,"test":[0.6703581437]},
{"learn":[0.6544084745],"iteration":775,"passed_time":25.05692652,"remaining_time":39.522781,"test":[0.6703551885]},
{"learn":[0.6543765257],"iteration":776,"passed_time":25.08660163,"remaining_time":39.48637554,"test":[0.6703608491]},
{"learn":[0.6543536123],"iteration":777,"passed_time":25.10764591,"remaining_time":39.43643098,"test":[0.6703674554]},
{"learn":[0.6543303593],"iteration":778,"passed_time":25.13940138,"remaining_time":39.40334928,"test":[0.6703679619]},
{"learn":[0.6543005831],"iteration":779,"passed_time":25.15916899,"remaining_time":39.35152074,"test":[0.6703701757]},
{"learn":[0.6542678123],"iteration":780,"passed_time":25.18841105,"remaining_time":39.31456219,"test":[0.6703603462]},
{"learn":[0.6542439303],"iteration":781,"passed_time":25.21444083,"remaining_time":39.27262012,"test":[0.670359801]},
{"learn":[0.6542100401],"iteration":782,"passed_time":25.24017824,"remaining_time":39.23026426,"test":[0.6703523669]},
{"learn":[0.6541836178],"iteration":783,"passed_time":25.2660091,"remaining_time":39.18809574,"test":[0.6703365674]},
{"learn":[0.654158129],"iteration":784,"passed_time":25.28891553,"remaining_time":39.1414425,"test":[0.6703486118]},
{"learn":[0.6541343464],"iteration":785,"passed_time":25.31589904,"remaining_time":39.10114686,"test":[0.6703450011]},
{"learn":[0.6541092921],"iteration":786,"passed_time":25.34123581,"remaining_time":39.05834694,"test":[0.6703473135]},
{"learn":[0.6540812254],"iteration":787,"passed_time":25.36728606,"remaining_time":39.01668871,"test":[0.670350998]},
{"learn":[0.654060259],"iteration":788,"passed_time":25.39177931,"remaining_time":38.97268028,"test":[0.6703417767]},
{"learn":[0.6540467253],"iteration":789,"passed_time":25.41712461,"remaining_time":38.9300263,"test":[0.6703349821]},
{"learn":[0.6540306837],"iteration":790,"passed_time":25.44804125,"remaining_time":38.89593157,"test":[0.6703457717]},
{"learn":[0.6540103667],"iteration":791,"passed_time":25.48249341,"remaining_time":38.86723743,"test":[0.6703506266]},
{"learn":[0.6539821302],"iteration":792,"passed_time":25.51450657,"remaining_time":38.83481643,"test":[0.6703596395]},
{"learn":[0.6539577914],"iteration":793,"passed_time":25.54216564,"remaining_time":38.79578307,"test":[0.6703799895]},
{"learn":[0.653923724],"iteration":794,"passed_time":25.56982738,"remaining_time":38.75678238,"test":[0.6703687687]},
{"learn":[0.6539086888],"iteration":795,"passed_time":25.59539769,"remaining_time":38.71464675,"test":[0.6703780675]},
{"learn":[0.6538798424],"iteration":796,"passed_time":25.61874122,"remaining_time":38.66919157,"test":[0.670374835]},
{"learn":[0.6538566996],"iteration":797,"passed_time":25.64394874,"remaining_time":38.62659947,"test":[0.6703831387]},
{"learn":[0.6538290752],"iteration":798,"passed_time":25.66776244,"remaining_time":38.58195581,"test":[0.670377656]},
{"learn":[0.6538051255],"iteration":799,"passed_time":25.69593415,"remaining_time":38.54390122,"test":[0.6703689741]},
{"learn":[0.6537917354],"iteration":800,"passed_time":25.71651353,"remaining_time":38.49450652,"test":[0.6703709756]},
{"learn":[0.6537684302],"iteration":801,"passed_time":25.74304126,"remaining_time":38.45406912,"test":[0.6703737517]},
{"learn":[0.6537402991],"iteration":802,"passed_time":25.77084871,"remaining_time":38.41557398,"test":[0.6703818964]},
{"learn":[0.6537165427],"iteration":803,"passed_time":25.79028824,"remaining_time":38.36465763,"test":[0.6703812173]},
{"learn":[0.6536853601],"iteration":804,"passed_time":25.82203653,"remaining_time":38.3320915,"test":[0.6703960068]},
{"learn":[0.6536681479],"iteration":805,"passed_time":25.84395064,"remaining_time":38.28495914,"test":[0.6703976729]},
{"learn":[0.6536409101],"iteration":806,"passed_time":25.87390688,"remaining_time":38.24977808,"test":[0.6704024604]},
{"learn":[0.6536120189],"iteration":807,"passed_time":25.89606204,"remaining_time":38.20310143,"test":[0.6704085008]},
{"learn":[0.6535912493],"iteration":808,"passed_time":25.92585483,"remaining_time":38.16772942,"test":[0.6704076633]},
{"learn":[0.6535617421],"iteration":809,"passed_time":25.95539059,"remaining_time":38.13199358,"test":[0.6704111719]},
{"learn":[0.6535315174],"iteration":810,"passed_time":25.98822968,"remaining_time":38.10111601,"test":[0.6704220803]},
{"learn":[0.6534972927],"iteration":811,"passed_time":26.02835773,"remaining_time":38.08089777,"test":[0.6704265011]},
{"learn":[0.6534818476],"iteration":812,"passed_time":26.0558565,"remaining_time":38.04219146,"test":[0.6704251162]},
{"learn":[0.6534498323],"iteration":813,"passed_time":26.08151817,"remaining_time":38.00083606,"test":[0.6704375472]},
{"learn":[0.6534305025],"iteration":814,"passed_time":26.10848988,"remaining_time":37.96142393,"test":[0.6704319336]},
{"learn":[0.6534081059],"iteration":815,"passed_time":26.13143346,"remaining_time":37.91619757,"test":[0.670437614]},
{"learn":[0.6533765804],"iteration":816,"passed_time":26.15923661,"remaining_time":37.87806231,"test":[0.6704554331]},
{"learn":[0.6533441549],"iteration":817,"passed_time":26.18805523,"remaining_time":37.84141966,"test":[0.6704603317]},
{"learn":[0.6533053405],"iteration":818,"passed_time":26.2140726,"remaining_time":37.8007567,"test":[0.6704548042]},
{"learn":[0.6532838469],"iteration":819,"passed_time":26.24289367,"remaining_time":37.76416405,"test":[0.6704502654]},
{"learn":[0.6532604302],"iteration":820,"passed_time":26.27260776,"remaining_time":37.72887277,"test":[0.6704512072]},
{"learn":[0.6532364412],"iteration":821,"passed_time":26.29880394,"remaining_time":37.68855358,"test":[0.6704433481]},
{"learn":[0.6532100089],"iteration":822,"passed_time":26.32785215,"remaining_time":37.65234749,"test":[0.6704095112]},
{"learn":[0.6531782515],"iteration":823,"passed_time":26.35925682,"remaining_time":37.61952188,"test":[0.6704086019]},
{"learn":[0.6531449701],"iteration":824,"passed_time":26.38596096,"remaining_time":37.580005,"test":[0.6703987131]},
{"learn":[0.653115452],"iteration":825,"passed_time":26.40854839,"remaining_time":37.53466805,"test":[0.6704019708]},
{"learn":[0.6530787602],"iteration":826,"passed_time":26.44419918,"remaining_time":37.50791492,"test":[0.6704046556]},
{"learn":[0.653052397],"iteration":827,"passed_time":26.47784276,"remaining_time":37.47829917,"test":[0.6704091961]},
{"learn":[0.6530313579],"iteration":828,"passed_time":26.51701028,"remaining_time":37.45647652,"test":[0.6704103204]},
{"learn":[0.6530010363],"iteration":829,"passed_time":26.53963123,"remaining_time":37.41128739,"test":[0.6704074257]},
{"learn":[0.6529752146],"iteration":830,"passed_time":26.57362226,"remaining_time":37.38214732,"test":[0.6704115335]},
{"learn":[0.652954801],"iteration":831,"passed_time":26.59767057,"remaining_time":37.33903754,"test":[0.6704041275]},
{"learn":[0.6529330351],"iteration":832,"passed_time":26.62378941,"remaining_time":37.29887425,"test":[0.6704004556]},
{"learn":[0.6528993709],"iteration":833,"passed_time":26.65024746,"remaining_time":37.25921887,"test":[0.6704037097]},
{"learn":[0.6528665883],"iteration":834,"passed_time":26.67774911,"remaining_time":37.22105115,"test":[0.6704035477]},
{"learn":[0.6528413041],"iteration":835,"passed_time":26.70473813,"remaining_time":37.1821952,"test":[0.6704025281]},
{"learn":[0.6528217161],"iteration":836,"passed_time":26.72833235,"remaining_time":37.13865056,"test":[0.6704024549]},
{"learn":[0.6527978782],"iteration":837,"passed_time":26.76384162,"remaining_time":37.11167537,"test":[0.670405721]},
{"learn":[0.6527789461],"iteration":838,"passed_time":26.79137369,"remaining_time":37.07364106,"test":[0.6703983189]},
{"learn":[0.6527432001],"iteration":839,"passed_time":26.82295602,"remaining_time":37.04122498,"test":[0.6704035256]},
{"learn":[0.6527139767],"iteration":840,"passed_time":26.87217031,"remaining_time":37.03310985,"test":[0.6704047613]},
{"learn":[0.6526857244],"iteration":841,"passed_time":26.92488006,"remaining_time":37.0297044,"test":[0.6704139617]},
{"learn":[0.652657086],"iteration":842,"passed_time":26.98258041,"remaining_time":37.03303147,"test":[0.6704066193]},
{"learn":[0.6526355016],"iteration":843,"passed_time":27.05424841,"remaining_time":37.05534497,"test":[0.670402892]},
{"learn":[0.6526054936],"iteration":844,"passed_time":27.09765154,"remaining_time":37.03880181,"test":[0.6704081961]},
{"learn":[0.6525793707],"iteration":845,"passed_time":27.12038959,"remaining_time":36.99400661,"test":[0.6704029862]},
{"learn":[0.6525584692],"iteration":846,"passed_time":27.14691224,"remaining_time":36.95441537,"test":[0.6704014281]},
{"learn":[0.6525279747],"iteration":847,"passed_time":27.18096334,"remaining_time":36.92508227,"test":[0.6704036115]},
{"learn":[0.6525038765],"iteration":848,"passed_time":27.20686017,"remaining_time":36.88468322,"test":[0.6704016777]},
{"learn":[0.6524849104],"iteration":849,"passed_time":27.23465701,"remaining_time":36.8468889,"test":[0.6704085392]},
{"learn":[0.6524610603],"iteration":850,"passed_time":27.26094834,"remaining_time":36.80708536,"test":[0.6704042952]},
{"learn":[0.6524357337],"iteration":851,"passed_time":27.28945577,"remaining_time":36.77029957,"test":[0.670394789]},
{"learn":[0.6524082286],"iteration":852,"passed_time":27.31865398,"remaining_time":36.73446203,"test":[0.6703885644]},
{"learn":[0.65238051],"iteration":853,"passed_time":27.34791322,"remaining_time":36.69872195,"test":[0.6703946813]},
{"learn":[0.6523557826],"iteration":854,"passed_time":27.3865535,"remaining_time":36.67555995,"test":[0.6704042137]},
{"learn":[0.6523391233],"iteration":855,"passed_time":27.41370907,"remaining_time":36.63701306,"test":[0.6704077517]},
{"learn":[0.652325347],"iteration":856,"passed_time":27.43905921,"remaining_time":36.5960848,"test":[0.6704118698]},
{"learn":[0.6522924958],"iteration":857,"passed_time":27.47159295,"remaining_time":36.56475425,"test":[0.6704114259]},
{"learn":[0.6522623584],"iteration":858,"passed_time":27.50124299,"remaining_time":36.52959052,"test":[0.6704157567]},
{"learn":[0.6522343891],"iteration":859,"passed_time":27.53509105,"remaining_time":36.50000442,"test":[0.6703837005]},
{"learn":[0.6522094424],"iteration":860,"passed_time":27.57211091,"remaining_time":36.47460432,"test":[0.6703829482]},
{"learn":[0.6521841478],"iteration":861,"passed_time":27.59555719,"remaining_time":36.43125764,"test":[0.6703818491]},
{"learn":[0.6521657946],"iteration":862,"passed_time":27.6272049,"remaining_time":36.39876242,"test":[0.6703826129]},
{"learn":[0.6521304278],"iteration":863,"passed_time":27.65462267,"remaining_time":36.36070759,"test":[0.6703834487]},
{"learn":[0.6521045712],"iteration":864,"passed_time":27.68321566,"remaining_time":36.3242194,"test":[0.6703868275]},
{"learn":[0.6520753696],"iteration":865,"passed_time":27.71151671,"remaining_time":36.28736714,"test":[0.6703853357]},
{"learn":[0.6520519528],"iteration":866,"passed_time":27.73884016,"remaining_time":36.2492571,"test":[0.670450644]},
{"learn":[0.6520216555],"iteration":867,"passed_time":27.76583897,"remaining_time":36.21074851,"test":[0.6704556991]},
{"learn":[0.6519926935],"iteration":868,"passed_time":27.79498714,"remaining_time":36.17506382,"test":[0.6704535742]},
{"learn":[0.6519734186],"iteration":869,"passed_time":27.82082723,"remaining_time":36.13509744,"test":[0.6704495915]}
]}
Binary file not shown.
+871
View File
@@ -0,0 +1,871 @@
iter Logloss
0 0.692389481
1 0.6916338586
2 0.6910159214
3 0.6903417151
4 0.6896961461
5 0.6890979366
6 0.6884946167
7 0.6879503686
8 0.6874528094
9 0.6869036785
10 0.6863761921
11 0.6859038678
12 0.685410175
13 0.6849483392
14 0.6845417792
15 0.6841038875
16 0.6836957422
17 0.6832947461
18 0.6829014105
19 0.6825264546
20 0.6822106577
21 0.6818649349
22 0.6815467855
23 0.6812293319
24 0.6808837443
25 0.6805816494
26 0.6803209634
27 0.6800350862
28 0.6797703947
29 0.6794926675
30 0.6792251865
31 0.6789670166
32 0.678722402
33 0.678476935
34 0.6782297335
35 0.6780226701
36 0.6778291026
37 0.6776045324
38 0.6773969079
39 0.6771819602
40 0.6769816736
41 0.6767984027
42 0.6766201184
43 0.6764394377
44 0.6762698797
45 0.6760974263
46 0.6759245179
47 0.6757673909
48 0.6756172628
49 0.675474531
50 0.6753286933
51 0.6751900513
52 0.6750574835
53 0.6749329567
54 0.6748033265
55 0.6746797823
56 0.674535525
57 0.6744256514
58 0.674310819
59 0.6741967947
60 0.6740879654
61 0.6739772476
62 0.67388281
63 0.6737789726
64 0.6736812332
65 0.6735930009
66 0.6734947116
67 0.6733961481
68 0.6732990195
69 0.6732133575
70 0.673111539
71 0.6730080451
72 0.6729157861
73 0.6728347949
74 0.6727640693
75 0.6726808811
76 0.6726029645
77 0.6725356026
78 0.6724606887
79 0.6723849561
80 0.6723050519
81 0.6722508802
82 0.6721773904
83 0.6721007598
84 0.6720353564
85 0.6719790902
86 0.6719140024
87 0.6718573633
88 0.671795602
89 0.6717369134
90 0.6716711079
91 0.6716070843
92 0.6715517232
93 0.6714957378
94 0.6714364567
95 0.6713881758
96 0.6713336502
97 0.6712700267
98 0.6712154424
99 0.6711600413
100 0.6711060533
101 0.6710494943
102 0.6709936897
103 0.6709472183
104 0.6708914508
105 0.6708388195
106 0.6707885854
107 0.6707454167
108 0.6706973013
109 0.6706577031
110 0.67061108
111 0.6705625485
112 0.6705146484
113 0.6704704423
114 0.6704155922
115 0.6703687117
116 0.6703324232
117 0.6702884624
118 0.670253478
119 0.6702140804
120 0.6701682529
121 0.6701320588
122 0.6700939824
123 0.6700655902
124 0.6700190743
125 0.6699792296
126 0.6699379404
127 0.669895454
128 0.6698563938
129 0.6698215571
130 0.6697857067
131 0.6697449303
132 0.6697052425
133 0.6696695553
134 0.6696269265
135 0.6695969271
136 0.6695489786
137 0.6695173859
138 0.6694811164
139 0.6694477439
140 0.6694082161
141 0.6693679185
142 0.6693341916
143 0.6692933159
144 0.6692619696
145 0.6692229289
146 0.6691840164
147 0.6691581406
148 0.6691177196
149 0.6690851126
150 0.6690518144
151 0.6690149711
152 0.668993877
153 0.6689596579
154 0.6689372651
155 0.6689003045
156 0.6688680182
157 0.6688348164
158 0.6687947046
159 0.6687605251
160 0.668726253
161 0.6686862718
162 0.668663478
163 0.6686399521
164 0.6686058279
165 0.6685761282
166 0.6685469327
167 0.6685157003
168 0.6684805143
169 0.6684485765
170 0.6684144429
171 0.6683849752
172 0.6683568537
173 0.6683266628
174 0.6682937842
175 0.6682657097
176 0.6682301443
177 0.6681995916
178 0.6681658267
179 0.6681422687
180 0.6681216601
181 0.6680899019
182 0.6680676394
183 0.6680413672
184 0.6680088406
185 0.6679873982
186 0.6679663544
187 0.6679417375
188 0.6679100197
189 0.667881208
190 0.6678475427
191 0.6678310341
192 0.6678060257
193 0.6677789336
194 0.6677478773
195 0.6677212408
196 0.667704316
197 0.6676819639
198 0.6676554448
199 0.6676318346
200 0.6676074705
201 0.6675849784
202 0.6675631744
203 0.6675397619
204 0.6675169086
205 0.6674864762
206 0.6674670714
207 0.6674375599
208 0.6674148457
209 0.6673974446
210 0.6673812139
211 0.6673515687
212 0.6673197956
213 0.6672900754
214 0.6672550009
215 0.6672271563
216 0.667204521
217 0.667181968
218 0.6671640023
219 0.66714351
220 0.6671167156
221 0.6670915937
222 0.6670595279
223 0.667033994
224 0.6670008246
225 0.6669858319
226 0.6669553964
227 0.6669274683
228 0.666896348
229 0.6668698686
230 0.6668513411
231 0.6668309985
232 0.6668058585
233 0.6667845908
234 0.6667582863
235 0.6667332943
236 0.6667070085
237 0.6666907315
238 0.6666633028
239 0.6666406707
240 0.6666134624
241 0.6665850522
242 0.6665631193
243 0.6665412643
244 0.6665168385
245 0.6664904845
246 0.6664678274
247 0.6664539777
248 0.6664334121
249 0.6664121724
250 0.666392034
251 0.666366899
252 0.6663414098
253 0.6663157816
254 0.6662989799
255 0.6662696102
256 0.6662479711
257 0.6662231874
258 0.6661947927
259 0.6661669951
260 0.6661426137
261 0.6661216749
262 0.6660983123
263 0.6660803402
264 0.6660617842
265 0.6660443878
266 0.6660176079
267 0.6659967546
268 0.6659751467
269 0.6659539329
270 0.6659263951
271 0.6659038921
272 0.6658767418
273 0.6658510507
274 0.6658210119
275 0.6657963011
276 0.6657748552
277 0.6657490013
278 0.665732402
279 0.6657118786
280 0.665684467
281 0.6656584634
282 0.6656309991
283 0.6656073482
284 0.6655890957
285 0.6655665563
286 0.6655452454
287 0.6655255286
288 0.6655053548
289 0.6654893396
290 0.6654648912
291 0.6654442759
292 0.6654173127
293 0.6653914518
294 0.6653648946
295 0.665344141
296 0.6653140817
297 0.665295365
298 0.6652787488
299 0.6652502991
300 0.665231168
301 0.6652136682
302 0.6651903001
303 0.6651697153
304 0.6651525958
305 0.6651322685
306 0.6651113828
307 0.6650886807
308 0.6650622251
309 0.6650429987
310 0.665015513
311 0.6650019022
312 0.664979951
313 0.6649549638
314 0.6649340455
315 0.6649162445
316 0.6649048119
317 0.6648796463
318 0.6648605481
319 0.6648429084
320 0.6648238121
321 0.6647969527
322 0.6647854723
323 0.6647589304
324 0.6647429024
325 0.6647237508
326 0.6647059396
327 0.664686288
328 0.6646532527
329 0.6646306438
330 0.6646098516
331 0.6645858284
332 0.6645707188
333 0.6645485788
334 0.6645305696
335 0.6645108881
336 0.6644923286
337 0.6644805222
338 0.6644572776
339 0.6644320741
340 0.6644115048
341 0.6643949013
342 0.6643619789
343 0.6643389502
344 0.6643088915
345 0.664286972
346 0.664274149
347 0.6642536926
348 0.6642357634
349 0.664207914
350 0.6641853097
351 0.6641654917
352 0.664143804
353 0.6641290647
354 0.6641117244
355 0.6640880219
356 0.6640669415
357 0.6640462999
358 0.664030296
359 0.6640028542
360 0.6639813347
361 0.6639597941
362 0.6639429832
363 0.6639222708
364 0.6639065546
365 0.6638823236
366 0.6638648195
367 0.6638436235
368 0.6638208732
369 0.6637956357
370 0.6637718453
371 0.663756918
372 0.6637353525
373 0.6637143112
374 0.6636956547
375 0.663680995
376 0.66366728
377 0.6636487567
378 0.6636266904
379 0.6636116064
380 0.6635902746
381 0.6635654896
382 0.6635393029
383 0.6635171734
384 0.663500789
385 0.663477743
386 0.6634584806
387 0.6634337499
388 0.6634135584
389 0.6633868455
390 0.6633755323
391 0.663356103
392 0.6633337631
393 0.663319422
394 0.6632911566
395 0.6632687875
396 0.6632431997
397 0.6632189331
398 0.663201035
399 0.6631898553
400 0.6631712482
401 0.663143025
402 0.663121538
403 0.6631087792
404 0.6630859067
405 0.663066483
406 0.6630443652
407 0.6630250376
408 0.6630007822
409 0.6629768728
410 0.6629528093
411 0.6629260936
412 0.6629102182
413 0.6628863488
414 0.6628648972
415 0.6628454339
416 0.6628200274
417 0.6627942591
418 0.6627744647
419 0.662765485
420 0.6627503257
421 0.6627323029
422 0.6627111509
423 0.6626785863
424 0.6626576561
425 0.6626363113
426 0.6626181065
427 0.66259794
428 0.6625765658
429 0.6625526572
430 0.66253135
431 0.6625035695
432 0.662480212
433 0.6624611632
434 0.6624332625
435 0.6624120584
436 0.6623941719
437 0.6623766304
438 0.6623623329
439 0.6623442925
440 0.6623212715
441 0.6623025941
442 0.6622749791
443 0.6622534499
444 0.6622305473
445 0.6622059333
446 0.6621871707
447 0.6621638454
448 0.6621511296
449 0.6621349978
450 0.6621120424
451 0.6620958271
452 0.6620793528
453 0.6620572713
454 0.6620395025
455 0.6620188044
456 0.6620017347
457 0.6619811454
458 0.6619695569
459 0.661952377
460 0.6619237442
461 0.6619089407
462 0.6618886168
463 0.6618831383
464 0.6618690774
465 0.661845878
466 0.6618290213
467 0.6618050064
468 0.6617832833
469 0.6617652311
470 0.6617443144
471 0.6617202619
472 0.6617005831
473 0.6616824419
474 0.6616538226
475 0.6616314155
476 0.6616127861
477 0.6616029072
478 0.6615843751
479 0.661563216
480 0.6615432257
481 0.6615263324
482 0.6615033259
483 0.661484293
484 0.6614678231
485 0.6614463024
486 0.6614155436
487 0.6613958945
488 0.661380611
489 0.6613677802
490 0.6613530086
491 0.6613248211
492 0.6613059359
493 0.6612729965
494 0.6612624948
495 0.6612401679
496 0.6612191637
497 0.6611912219
498 0.6611773017
499 0.6611638216
500 0.6611450533
501 0.6611179111
502 0.6610959069
503 0.6610728788
504 0.6610436668
505 0.6610188976
506 0.6610030555
507 0.6609831174
508 0.6609586562
509 0.660935882
510 0.6609202024
511 0.6609011137
512 0.6608726737
513 0.6608608849
514 0.6608387256
515 0.6608136063
516 0.6607946343
517 0.6607703935
518 0.6607509625
519 0.6607238109
520 0.6606999858
521 0.6606813873
522 0.6606610372
523 0.660638456
524 0.6606156483
525 0.6605968623
526 0.6605735776
527 0.6605517294
528 0.6605309239
529 0.6605086434
530 0.6604803349
531 0.6604566326
532 0.6604430839
533 0.6604273738
534 0.6604048016
535 0.6603845173
536 0.6603669212
537 0.6603488983
538 0.6603176881
539 0.6602953862
540 0.6602672025
541 0.6602568636
542 0.660235705
543 0.6602152295
544 0.6601897709
545 0.6601683731
546 0.6601472267
547 0.6601262337
548 0.6601119991
549 0.6600869973
550 0.6600667497
551 0.6600397508
552 0.660016863
553 0.6599933158
554 0.6599632649
555 0.6599446007
556 0.6599138126
557 0.6598965504
558 0.6598785723
559 0.659860838
560 0.6598408724
561 0.6598244857
562 0.6598082469
563 0.6597851673
564 0.6597683521
565 0.6597479006
566 0.6597310938
567 0.6597096581
568 0.6596862311
569 0.6596574779
570 0.6596385418
571 0.6596189903
572 0.65959275
573 0.6595730662
574 0.6595566809
575 0.6595365076
576 0.6595163446
577 0.6594816637
578 0.6594570142
579 0.6594353055
580 0.6594162362
581 0.659395036
582 0.6593798831
583 0.6593556719
584 0.6593292627
585 0.6592976737
586 0.6592754841
587 0.6592510441
588 0.6592290326
589 0.6592097404
590 0.6591876204
591 0.6591705995
592 0.6591456195
593 0.6591107122
594 0.6590819533
595 0.6590551327
596 0.6590373916
597 0.6590177149
598 0.6589946095
599 0.6589697628
600 0.6589442269
601 0.6589182437
602 0.6588837179
603 0.6588674101
604 0.6588406916
605 0.6588149945
606 0.6587866031
607 0.6587636648
608 0.6587502469
609 0.6587292784
610 0.6587104112
611 0.6586953782
612 0.6586641191
613 0.6586450136
614 0.6586136263
615 0.6585862768
616 0.6585585235
617 0.6585371631
618 0.6585092632
619 0.6584914317
620 0.6584662432
621 0.6584454668
622 0.6584249408
623 0.6583931228
624 0.6583660767
625 0.658354264
626 0.6583253625
627 0.6582968632
628 0.6582687399
629 0.658242535
630 0.6582199874
631 0.6581918101
632 0.6581735218
633 0.6581445869
634 0.6581202427
635 0.6580977862
636 0.6580724179
637 0.6580426322
638 0.6580111256
639 0.6579834747
640 0.6579541367
641 0.6579254503
642 0.657898555
643 0.6578676875
644 0.6578324163
645 0.6578062223
646 0.6577760631
647 0.6577483474
648 0.6577249642
649 0.6576974966
650 0.657675114
651 0.6576447891
652 0.6576102356
653 0.6575793887
654 0.6575543309
655 0.6575340787
656 0.6575061464
657 0.657476113
658 0.6574447014
659 0.6574247361
660 0.6574034983
661 0.6573783832
662 0.657357694
663 0.6573411592
664 0.6573118559
665 0.6572819076
666 0.6572430097
667 0.6572160391
668 0.6571931413
669 0.6571737099
670 0.6571532872
671 0.6571208939
672 0.6570887673
673 0.6570633692
674 0.6570454361
675 0.6570231031
676 0.6570052089
677 0.6569855794
678 0.6569579709
679 0.6569333354
680 0.6569069617
681 0.6568931857
682 0.6568734532
683 0.6568435196
684 0.6568108038
685 0.6567811374
686 0.6567467284
687 0.6567172734
688 0.6566967606
689 0.6566720128
690 0.6566441608
691 0.6566172287
692 0.6565952549
693 0.6565702687
694 0.6565392213
695 0.6565157938
696 0.6564902789
697 0.6564644734
698 0.6564349549
699 0.6564046572
700 0.6563744107
701 0.6563525063
702 0.6563189867
703 0.6562939062
704 0.6562739297
705 0.656256438
706 0.6562366475
707 0.6562073096
708 0.6561864222
709 0.6561578826
710 0.6561208567
711 0.6560924703
712 0.6560656907
713 0.6560362588
714 0.6560124527
715 0.6559875055
716 0.6559547281
717 0.6559230866
718 0.6558924823
719 0.6558676469
720 0.6558459277
721 0.6558149638
722 0.6557812248
723 0.6557546502
724 0.6557274948
725 0.6557044723
726 0.6556751811
727 0.6556539158
728 0.6556182915
729 0.6555977079
730 0.6555667903
731 0.6555394075
732 0.6555122742
733 0.6554814941
734 0.6554517373
735 0.655429552
736 0.655396579
737 0.6553735864
738 0.6553472597
739 0.6553252832
740 0.6552971659
741 0.6552763852
742 0.6552488203
743 0.65521229
744 0.6551949744
745 0.6551673797
746 0.6551421856
747 0.6551255516
748 0.6551019608
749 0.6550758728
750 0.655051966
751 0.6550351058
752 0.6549998756
753 0.6549721212
754 0.6549401744
755 0.6549207325
756 0.6548900891
757 0.6548682731
758 0.6548418938
759 0.6548234717
760 0.6547996833
761 0.6547726174
762 0.6547509314
763 0.6547168175
764 0.6546907846
765 0.6546671611
766 0.6546475893
767 0.6546206223
768 0.6545874193
769 0.6545620629
770 0.6545346297
771 0.6545172316
772 0.6544943049
773 0.6544632323
774 0.6544384097
775 0.6544084745
776 0.6543765257
777 0.6543536123
778 0.6543303593
779 0.6543005831
780 0.6542678123
781 0.6542439303
782 0.6542100401
783 0.6541836178
784 0.654158129
785 0.6541343464
786 0.6541092921
787 0.6540812254
788 0.654060259
789 0.6540467253
790 0.6540306837
791 0.6540103667
792 0.6539821302
793 0.6539577914
794 0.653923724
795 0.6539086888
796 0.6538798424
797 0.6538566996
798 0.6538290752
799 0.6538051255
800 0.6537917354
801 0.6537684302
802 0.6537402991
803 0.6537165427
804 0.6536853601
805 0.6536681479
806 0.6536409101
807 0.6536120189
808 0.6535912493
809 0.6535617421
810 0.6535315174
811 0.6534972927
812 0.6534818476
813 0.6534498323
814 0.6534305025
815 0.6534081059
816 0.6533765804
817 0.6533441549
818 0.6533053405
819 0.6532838469
820 0.6532604302
821 0.6532364412
822 0.6532100089
823 0.6531782515
824 0.6531449701
825 0.653115452
826 0.6530787602
827 0.653052397
828 0.6530313579
829 0.6530010363
830 0.6529752146
831 0.652954801
832 0.6529330351
833 0.6528993709
834 0.6528665883
835 0.6528413041
836 0.6528217161
837 0.6527978782
838 0.6527789461
839 0.6527432001
840 0.6527139767
841 0.6526857244
842 0.652657086
843 0.6526355016
844 0.6526054936
845 0.6525793707
846 0.6525584692
847 0.6525279747
848 0.6525038765
849 0.6524849104
850 0.6524610603
851 0.6524357337
852 0.6524082286
853 0.65238051
854 0.6523557826
855 0.6523391233
856 0.652325347
857 0.6522924958
858 0.6522623584
859 0.6522343891
860 0.6522094424
861 0.6521841478
862 0.6521657946
863 0.6521304278
864 0.6521045712
865 0.6520753696
866 0.6520519528
867 0.6520216555
868 0.6519926935
869 0.6519734186
1 iter Logloss
2 0 0.692389481
3 1 0.6916338586
4 2 0.6910159214
5 3 0.6903417151
6 4 0.6896961461
7 5 0.6890979366
8 6 0.6884946167
9 7 0.6879503686
10 8 0.6874528094
11 9 0.6869036785
12 10 0.6863761921
13 11 0.6859038678
14 12 0.685410175
15 13 0.6849483392
16 14 0.6845417792
17 15 0.6841038875
18 16 0.6836957422
19 17 0.6832947461
20 18 0.6829014105
21 19 0.6825264546
22 20 0.6822106577
23 21 0.6818649349
24 22 0.6815467855
25 23 0.6812293319
26 24 0.6808837443
27 25 0.6805816494
28 26 0.6803209634
29 27 0.6800350862
30 28 0.6797703947
31 29 0.6794926675
32 30 0.6792251865
33 31 0.6789670166
34 32 0.678722402
35 33 0.678476935
36 34 0.6782297335
37 35 0.6780226701
38 36 0.6778291026
39 37 0.6776045324
40 38 0.6773969079
41 39 0.6771819602
42 40 0.6769816736
43 41 0.6767984027
44 42 0.6766201184
45 43 0.6764394377
46 44 0.6762698797
47 45 0.6760974263
48 46 0.6759245179
49 47 0.6757673909
50 48 0.6756172628
51 49 0.675474531
52 50 0.6753286933
53 51 0.6751900513
54 52 0.6750574835
55 53 0.6749329567
56 54 0.6748033265
57 55 0.6746797823
58 56 0.674535525
59 57 0.6744256514
60 58 0.674310819
61 59 0.6741967947
62 60 0.6740879654
63 61 0.6739772476
64 62 0.67388281
65 63 0.6737789726
66 64 0.6736812332
67 65 0.6735930009
68 66 0.6734947116
69 67 0.6733961481
70 68 0.6732990195
71 69 0.6732133575
72 70 0.673111539
73 71 0.6730080451
74 72 0.6729157861
75 73 0.6728347949
76 74 0.6727640693
77 75 0.6726808811
78 76 0.6726029645
79 77 0.6725356026
80 78 0.6724606887
81 79 0.6723849561
82 80 0.6723050519
83 81 0.6722508802
84 82 0.6721773904
85 83 0.6721007598
86 84 0.6720353564
87 85 0.6719790902
88 86 0.6719140024
89 87 0.6718573633
90 88 0.671795602
91 89 0.6717369134
92 90 0.6716711079
93 91 0.6716070843
94 92 0.6715517232
95 93 0.6714957378
96 94 0.6714364567
97 95 0.6713881758
98 96 0.6713336502
99 97 0.6712700267
100 98 0.6712154424
101 99 0.6711600413
102 100 0.6711060533
103 101 0.6710494943
104 102 0.6709936897
105 103 0.6709472183
106 104 0.6708914508
107 105 0.6708388195
108 106 0.6707885854
109 107 0.6707454167
110 108 0.6706973013
111 109 0.6706577031
112 110 0.67061108
113 111 0.6705625485
114 112 0.6705146484
115 113 0.6704704423
116 114 0.6704155922
117 115 0.6703687117
118 116 0.6703324232
119 117 0.6702884624
120 118 0.670253478
121 119 0.6702140804
122 120 0.6701682529
123 121 0.6701320588
124 122 0.6700939824
125 123 0.6700655902
126 124 0.6700190743
127 125 0.6699792296
128 126 0.6699379404
129 127 0.669895454
130 128 0.6698563938
131 129 0.6698215571
132 130 0.6697857067
133 131 0.6697449303
134 132 0.6697052425
135 133 0.6696695553
136 134 0.6696269265
137 135 0.6695969271
138 136 0.6695489786
139 137 0.6695173859
140 138 0.6694811164
141 139 0.6694477439
142 140 0.6694082161
143 141 0.6693679185
144 142 0.6693341916
145 143 0.6692933159
146 144 0.6692619696
147 145 0.6692229289
148 146 0.6691840164
149 147 0.6691581406
150 148 0.6691177196
151 149 0.6690851126
152 150 0.6690518144
153 151 0.6690149711
154 152 0.668993877
155 153 0.6689596579
156 154 0.6689372651
157 155 0.6689003045
158 156 0.6688680182
159 157 0.6688348164
160 158 0.6687947046
161 159 0.6687605251
162 160 0.668726253
163 161 0.6686862718
164 162 0.668663478
165 163 0.6686399521
166 164 0.6686058279
167 165 0.6685761282
168 166 0.6685469327
169 167 0.6685157003
170 168 0.6684805143
171 169 0.6684485765
172 170 0.6684144429
173 171 0.6683849752
174 172 0.6683568537
175 173 0.6683266628
176 174 0.6682937842
177 175 0.6682657097
178 176 0.6682301443
179 177 0.6681995916
180 178 0.6681658267
181 179 0.6681422687
182 180 0.6681216601
183 181 0.6680899019
184 182 0.6680676394
185 183 0.6680413672
186 184 0.6680088406
187 185 0.6679873982
188 186 0.6679663544
189 187 0.6679417375
190 188 0.6679100197
191 189 0.667881208
192 190 0.6678475427
193 191 0.6678310341
194 192 0.6678060257
195 193 0.6677789336
196 194 0.6677478773
197 195 0.6677212408
198 196 0.667704316
199 197 0.6676819639
200 198 0.6676554448
201 199 0.6676318346
202 200 0.6676074705
203 201 0.6675849784
204 202 0.6675631744
205 203 0.6675397619
206 204 0.6675169086
207 205 0.6674864762
208 206 0.6674670714
209 207 0.6674375599
210 208 0.6674148457
211 209 0.6673974446
212 210 0.6673812139
213 211 0.6673515687
214 212 0.6673197956
215 213 0.6672900754
216 214 0.6672550009
217 215 0.6672271563
218 216 0.667204521
219 217 0.667181968
220 218 0.6671640023
221 219 0.66714351
222 220 0.6671167156
223 221 0.6670915937
224 222 0.6670595279
225 223 0.667033994
226 224 0.6670008246
227 225 0.6669858319
228 226 0.6669553964
229 227 0.6669274683
230 228 0.666896348
231 229 0.6668698686
232 230 0.6668513411
233 231 0.6668309985
234 232 0.6668058585
235 233 0.6667845908
236 234 0.6667582863
237 235 0.6667332943
238 236 0.6667070085
239 237 0.6666907315
240 238 0.6666633028
241 239 0.6666406707
242 240 0.6666134624
243 241 0.6665850522
244 242 0.6665631193
245 243 0.6665412643
246 244 0.6665168385
247 245 0.6664904845
248 246 0.6664678274
249 247 0.6664539777
250 248 0.6664334121
251 249 0.6664121724
252 250 0.666392034
253 251 0.666366899
254 252 0.6663414098
255 253 0.6663157816
256 254 0.6662989799
257 255 0.6662696102
258 256 0.6662479711
259 257 0.6662231874
260 258 0.6661947927
261 259 0.6661669951
262 260 0.6661426137
263 261 0.6661216749
264 262 0.6660983123
265 263 0.6660803402
266 264 0.6660617842
267 265 0.6660443878
268 266 0.6660176079
269 267 0.6659967546
270 268 0.6659751467
271 269 0.6659539329
272 270 0.6659263951
273 271 0.6659038921
274 272 0.6658767418
275 273 0.6658510507
276 274 0.6658210119
277 275 0.6657963011
278 276 0.6657748552
279 277 0.6657490013
280 278 0.665732402
281 279 0.6657118786
282 280 0.665684467
283 281 0.6656584634
284 282 0.6656309991
285 283 0.6656073482
286 284 0.6655890957
287 285 0.6655665563
288 286 0.6655452454
289 287 0.6655255286
290 288 0.6655053548
291 289 0.6654893396
292 290 0.6654648912
293 291 0.6654442759
294 292 0.6654173127
295 293 0.6653914518
296 294 0.6653648946
297 295 0.665344141
298 296 0.6653140817
299 297 0.665295365
300 298 0.6652787488
301 299 0.6652502991
302 300 0.665231168
303 301 0.6652136682
304 302 0.6651903001
305 303 0.6651697153
306 304 0.6651525958
307 305 0.6651322685
308 306 0.6651113828
309 307 0.6650886807
310 308 0.6650622251
311 309 0.6650429987
312 310 0.665015513
313 311 0.6650019022
314 312 0.664979951
315 313 0.6649549638
316 314 0.6649340455
317 315 0.6649162445
318 316 0.6649048119
319 317 0.6648796463
320 318 0.6648605481
321 319 0.6648429084
322 320 0.6648238121
323 321 0.6647969527
324 322 0.6647854723
325 323 0.6647589304
326 324 0.6647429024
327 325 0.6647237508
328 326 0.6647059396
329 327 0.664686288
330 328 0.6646532527
331 329 0.6646306438
332 330 0.6646098516
333 331 0.6645858284
334 332 0.6645707188
335 333 0.6645485788
336 334 0.6645305696
337 335 0.6645108881
338 336 0.6644923286
339 337 0.6644805222
340 338 0.6644572776
341 339 0.6644320741
342 340 0.6644115048
343 341 0.6643949013
344 342 0.6643619789
345 343 0.6643389502
346 344 0.6643088915
347 345 0.664286972
348 346 0.664274149
349 347 0.6642536926
350 348 0.6642357634
351 349 0.664207914
352 350 0.6641853097
353 351 0.6641654917
354 352 0.664143804
355 353 0.6641290647
356 354 0.6641117244
357 355 0.6640880219
358 356 0.6640669415
359 357 0.6640462999
360 358 0.664030296
361 359 0.6640028542
362 360 0.6639813347
363 361 0.6639597941
364 362 0.6639429832
365 363 0.6639222708
366 364 0.6639065546
367 365 0.6638823236
368 366 0.6638648195
369 367 0.6638436235
370 368 0.6638208732
371 369 0.6637956357
372 370 0.6637718453
373 371 0.663756918
374 372 0.6637353525
375 373 0.6637143112
376 374 0.6636956547
377 375 0.663680995
378 376 0.66366728
379 377 0.6636487567
380 378 0.6636266904
381 379 0.6636116064
382 380 0.6635902746
383 381 0.6635654896
384 382 0.6635393029
385 383 0.6635171734
386 384 0.663500789
387 385 0.663477743
388 386 0.6634584806
389 387 0.6634337499
390 388 0.6634135584
391 389 0.6633868455
392 390 0.6633755323
393 391 0.663356103
394 392 0.6633337631
395 393 0.663319422
396 394 0.6632911566
397 395 0.6632687875
398 396 0.6632431997
399 397 0.6632189331
400 398 0.663201035
401 399 0.6631898553
402 400 0.6631712482
403 401 0.663143025
404 402 0.663121538
405 403 0.6631087792
406 404 0.6630859067
407 405 0.663066483
408 406 0.6630443652
409 407 0.6630250376
410 408 0.6630007822
411 409 0.6629768728
412 410 0.6629528093
413 411 0.6629260936
414 412 0.6629102182
415 413 0.6628863488
416 414 0.6628648972
417 415 0.6628454339
418 416 0.6628200274
419 417 0.6627942591
420 418 0.6627744647
421 419 0.662765485
422 420 0.6627503257
423 421 0.6627323029
424 422 0.6627111509
425 423 0.6626785863
426 424 0.6626576561
427 425 0.6626363113
428 426 0.6626181065
429 427 0.66259794
430 428 0.6625765658
431 429 0.6625526572
432 430 0.66253135
433 431 0.6625035695
434 432 0.662480212
435 433 0.6624611632
436 434 0.6624332625
437 435 0.6624120584
438 436 0.6623941719
439 437 0.6623766304
440 438 0.6623623329
441 439 0.6623442925
442 440 0.6623212715
443 441 0.6623025941
444 442 0.6622749791
445 443 0.6622534499
446 444 0.6622305473
447 445 0.6622059333
448 446 0.6621871707
449 447 0.6621638454
450 448 0.6621511296
451 449 0.6621349978
452 450 0.6621120424
453 451 0.6620958271
454 452 0.6620793528
455 453 0.6620572713
456 454 0.6620395025
457 455 0.6620188044
458 456 0.6620017347
459 457 0.6619811454
460 458 0.6619695569
461 459 0.661952377
462 460 0.6619237442
463 461 0.6619089407
464 462 0.6618886168
465 463 0.6618831383
466 464 0.6618690774
467 465 0.661845878
468 466 0.6618290213
469 467 0.6618050064
470 468 0.6617832833
471 469 0.6617652311
472 470 0.6617443144
473 471 0.6617202619
474 472 0.6617005831
475 473 0.6616824419
476 474 0.6616538226
477 475 0.6616314155
478 476 0.6616127861
479 477 0.6616029072
480 478 0.6615843751
481 479 0.661563216
482 480 0.6615432257
483 481 0.6615263324
484 482 0.6615033259
485 483 0.661484293
486 484 0.6614678231
487 485 0.6614463024
488 486 0.6614155436
489 487 0.6613958945
490 488 0.661380611
491 489 0.6613677802
492 490 0.6613530086
493 491 0.6613248211
494 492 0.6613059359
495 493 0.6612729965
496 494 0.6612624948
497 495 0.6612401679
498 496 0.6612191637
499 497 0.6611912219
500 498 0.6611773017
501 499 0.6611638216
502 500 0.6611450533
503 501 0.6611179111
504 502 0.6610959069
505 503 0.6610728788
506 504 0.6610436668
507 505 0.6610188976
508 506 0.6610030555
509 507 0.6609831174
510 508 0.6609586562
511 509 0.660935882
512 510 0.6609202024
513 511 0.6609011137
514 512 0.6608726737
515 513 0.6608608849
516 514 0.6608387256
517 515 0.6608136063
518 516 0.6607946343
519 517 0.6607703935
520 518 0.6607509625
521 519 0.6607238109
522 520 0.6606999858
523 521 0.6606813873
524 522 0.6606610372
525 523 0.660638456
526 524 0.6606156483
527 525 0.6605968623
528 526 0.6605735776
529 527 0.6605517294
530 528 0.6605309239
531 529 0.6605086434
532 530 0.6604803349
533 531 0.6604566326
534 532 0.6604430839
535 533 0.6604273738
536 534 0.6604048016
537 535 0.6603845173
538 536 0.6603669212
539 537 0.6603488983
540 538 0.6603176881
541 539 0.6602953862
542 540 0.6602672025
543 541 0.6602568636
544 542 0.660235705
545 543 0.6602152295
546 544 0.6601897709
547 545 0.6601683731
548 546 0.6601472267
549 547 0.6601262337
550 548 0.6601119991
551 549 0.6600869973
552 550 0.6600667497
553 551 0.6600397508
554 552 0.660016863
555 553 0.6599933158
556 554 0.6599632649
557 555 0.6599446007
558 556 0.6599138126
559 557 0.6598965504
560 558 0.6598785723
561 559 0.659860838
562 560 0.6598408724
563 561 0.6598244857
564 562 0.6598082469
565 563 0.6597851673
566 564 0.6597683521
567 565 0.6597479006
568 566 0.6597310938
569 567 0.6597096581
570 568 0.6596862311
571 569 0.6596574779
572 570 0.6596385418
573 571 0.6596189903
574 572 0.65959275
575 573 0.6595730662
576 574 0.6595566809
577 575 0.6595365076
578 576 0.6595163446
579 577 0.6594816637
580 578 0.6594570142
581 579 0.6594353055
582 580 0.6594162362
583 581 0.659395036
584 582 0.6593798831
585 583 0.6593556719
586 584 0.6593292627
587 585 0.6592976737
588 586 0.6592754841
589 587 0.6592510441
590 588 0.6592290326
591 589 0.6592097404
592 590 0.6591876204
593 591 0.6591705995
594 592 0.6591456195
595 593 0.6591107122
596 594 0.6590819533
597 595 0.6590551327
598 596 0.6590373916
599 597 0.6590177149
600 598 0.6589946095
601 599 0.6589697628
602 600 0.6589442269
603 601 0.6589182437
604 602 0.6588837179
605 603 0.6588674101
606 604 0.6588406916
607 605 0.6588149945
608 606 0.6587866031
609 607 0.6587636648
610 608 0.6587502469
611 609 0.6587292784
612 610 0.6587104112
613 611 0.6586953782
614 612 0.6586641191
615 613 0.6586450136
616 614 0.6586136263
617 615 0.6585862768
618 616 0.6585585235
619 617 0.6585371631
620 618 0.6585092632
621 619 0.6584914317
622 620 0.6584662432
623 621 0.6584454668
624 622 0.6584249408
625 623 0.6583931228
626 624 0.6583660767
627 625 0.658354264
628 626 0.6583253625
629 627 0.6582968632
630 628 0.6582687399
631 629 0.658242535
632 630 0.6582199874
633 631 0.6581918101
634 632 0.6581735218
635 633 0.6581445869
636 634 0.6581202427
637 635 0.6580977862
638 636 0.6580724179
639 637 0.6580426322
640 638 0.6580111256
641 639 0.6579834747
642 640 0.6579541367
643 641 0.6579254503
644 642 0.657898555
645 643 0.6578676875
646 644 0.6578324163
647 645 0.6578062223
648 646 0.6577760631
649 647 0.6577483474
650 648 0.6577249642
651 649 0.6576974966
652 650 0.657675114
653 651 0.6576447891
654 652 0.6576102356
655 653 0.6575793887
656 654 0.6575543309
657 655 0.6575340787
658 656 0.6575061464
659 657 0.657476113
660 658 0.6574447014
661 659 0.6574247361
662 660 0.6574034983
663 661 0.6573783832
664 662 0.657357694
665 663 0.6573411592
666 664 0.6573118559
667 665 0.6572819076
668 666 0.6572430097
669 667 0.6572160391
670 668 0.6571931413
671 669 0.6571737099
672 670 0.6571532872
673 671 0.6571208939
674 672 0.6570887673
675 673 0.6570633692
676 674 0.6570454361
677 675 0.6570231031
678 676 0.6570052089
679 677 0.6569855794
680 678 0.6569579709
681 679 0.6569333354
682 680 0.6569069617
683 681 0.6568931857
684 682 0.6568734532
685 683 0.6568435196
686 684 0.6568108038
687 685 0.6567811374
688 686 0.6567467284
689 687 0.6567172734
690 688 0.6566967606
691 689 0.6566720128
692 690 0.6566441608
693 691 0.6566172287
694 692 0.6565952549
695 693 0.6565702687
696 694 0.6565392213
697 695 0.6565157938
698 696 0.6564902789
699 697 0.6564644734
700 698 0.6564349549
701 699 0.6564046572
702 700 0.6563744107
703 701 0.6563525063
704 702 0.6563189867
705 703 0.6562939062
706 704 0.6562739297
707 705 0.656256438
708 706 0.6562366475
709 707 0.6562073096
710 708 0.6561864222
711 709 0.6561578826
712 710 0.6561208567
713 711 0.6560924703
714 712 0.6560656907
715 713 0.6560362588
716 714 0.6560124527
717 715 0.6559875055
718 716 0.6559547281
719 717 0.6559230866
720 718 0.6558924823
721 719 0.6558676469
722 720 0.6558459277
723 721 0.6558149638
724 722 0.6557812248
725 723 0.6557546502
726 724 0.6557274948
727 725 0.6557044723
728 726 0.6556751811
729 727 0.6556539158
730 728 0.6556182915
731 729 0.6555977079
732 730 0.6555667903
733 731 0.6555394075
734 732 0.6555122742
735 733 0.6554814941
736 734 0.6554517373
737 735 0.655429552
738 736 0.655396579
739 737 0.6553735864
740 738 0.6553472597
741 739 0.6553252832
742 740 0.6552971659
743 741 0.6552763852
744 742 0.6552488203
745 743 0.65521229
746 744 0.6551949744
747 745 0.6551673797
748 746 0.6551421856
749 747 0.6551255516
750 748 0.6551019608
751 749 0.6550758728
752 750 0.655051966
753 751 0.6550351058
754 752 0.6549998756
755 753 0.6549721212
756 754 0.6549401744
757 755 0.6549207325
758 756 0.6548900891
759 757 0.6548682731
760 758 0.6548418938
761 759 0.6548234717
762 760 0.6547996833
763 761 0.6547726174
764 762 0.6547509314
765 763 0.6547168175
766 764 0.6546907846
767 765 0.6546671611
768 766 0.6546475893
769 767 0.6546206223
770 768 0.6545874193
771 769 0.6545620629
772 770 0.6545346297
773 771 0.6545172316
774 772 0.6544943049
775 773 0.6544632323
776 774 0.6544384097
777 775 0.6544084745
778 776 0.6543765257
779 777 0.6543536123
780 778 0.6543303593
781 779 0.6543005831
782 780 0.6542678123
783 781 0.6542439303
784 782 0.6542100401
785 783 0.6541836178
786 784 0.654158129
787 785 0.6541343464
788 786 0.6541092921
789 787 0.6540812254
790 788 0.654060259
791 789 0.6540467253
792 790 0.6540306837
793 791 0.6540103667
794 792 0.6539821302
795 793 0.6539577914
796 794 0.653923724
797 795 0.6539086888
798 796 0.6538798424
799 797 0.6538566996
800 798 0.6538290752
801 799 0.6538051255
802 800 0.6537917354
803 801 0.6537684302
804 802 0.6537402991
805 803 0.6537165427
806 804 0.6536853601
807 805 0.6536681479
808 806 0.6536409101
809 807 0.6536120189
810 808 0.6535912493
811 809 0.6535617421
812 810 0.6535315174
813 811 0.6534972927
814 812 0.6534818476
815 813 0.6534498323
816 814 0.6534305025
817 815 0.6534081059
818 816 0.6533765804
819 817 0.6533441549
820 818 0.6533053405
821 819 0.6532838469
822 820 0.6532604302
823 821 0.6532364412
824 822 0.6532100089
825 823 0.6531782515
826 824 0.6531449701
827 825 0.653115452
828 826 0.6530787602
829 827 0.653052397
830 828 0.6530313579
831 829 0.6530010363
832 830 0.6529752146
833 831 0.652954801
834 832 0.6529330351
835 833 0.6528993709
836 834 0.6528665883
837 835 0.6528413041
838 836 0.6528217161
839 837 0.6527978782
840 838 0.6527789461
841 839 0.6527432001
842 840 0.6527139767
843 841 0.6526857244
844 842 0.652657086
845 843 0.6526355016
846 844 0.6526054936
847 845 0.6525793707
848 846 0.6525584692
849 847 0.6525279747
850 848 0.6525038765
851 849 0.6524849104
852 850 0.6524610603
853 851 0.6524357337
854 852 0.6524082286
855 853 0.65238051
856 854 0.6523557826
857 855 0.6523391233
858 856 0.652325347
859 857 0.6522924958
860 858 0.6522623584
861 859 0.6522343891
862 860 0.6522094424
863 861 0.6521841478
864 862 0.6521657946
865 863 0.6521304278
866 864 0.6521045712
867 865 0.6520753696
868 866 0.6520519528
869 867 0.6520216555
870 868 0.6519926935
871 869 0.6519734186
+871
View File
@@ -0,0 +1,871 @@
iter Passed Remaining
0 46 93548
1 83 83419
2 132 88415
3 162 81250
4 196 78573
5 230 76747
6 269 76701
7 319 79674
8 364 80653
9 411 81918
10 456 82497
11 491 81432
12 522 79809
13 555 78774
14 595 78777
15 630 78123
16 662 77290
17 700 77124
18 730 76120
19 764 75651
20 804 75774
21 835 75128
22 886 76169
23 920 75764
24 960 75853
25 989 75130
26 1025 74941
27 1060 74714
28 1104 75079
29 1141 74976
30 1180 74975
31 1213 74640
32 1245 74260
33 1287 74434
34 1327 74528
35 1376 75071
36 1427 75741
37 1468 75804
38 1508 75857
39 1549 75922
40 1586 75781
41 1621 75590
42 1663 75705
43 1701 75621
44 1739 75591
45 1776 75460
46 1819 75616
47 1869 76025
48 1916 76288
49 1953 76191
50 1993 76197
51 2038 76381
52 2080 76420
53 2158 77788
54 2220 78529
55 2286 79390
56 2328 79372
57 2367 79254
58 2409 79257
59 2444 79049
60 2484 78985
61 2521 78820
62 2554 78528
63 2593 78466
64 2623 78111
65 2660 77969
66 2695 77776
67 2725 77446
68 2761 77291
69 2791 76975
70 2824 76739
71 2861 76611
72 2897 76476
73 2935 76408
74 3040 78027
75 3097 78411
76 3152 78741
77 3216 79248
78 3256 79195
79 3305 79336
80 3348 79320
81 3381 79089
82 3416 78911
83 3480 79399
84 3535 79649
85 3581 79716
86 3612 79428
87 3644 79185
88 3678 78975
89 3712 78785
90 3743 78531
91 3775 78297
92 3806 78047
93 3837 77821
94 3871 77629
95 3913 77618
96 3945 77403
97 3989 77433
98 4020 77204
99 4053 77020
100 4084 76789
101 4116 76597
102 4148 76401
103 4176 76141
104 4202 75845
105 4232 75634
106 4261 75390
107 4290 75168
108 4324 75018
109 4351 74766
110 4386 74648
111 4424 74577
112 4458 74455
113 4497 74400
114 4533 74307
115 4564 74136
116 4596 73981
117 4628 73818
118 4668 73786
119 4692 73509
120 4723 73354
121 4756 73220
122 4788 73065
123 4815 72854
124 4843 72647
125 4875 72514
126 4916 72515
127 4952 72436
128 4991 72397
129 5028 72327
130 5059 72180
131 5096 72116
132 5125 71946
133 5156 71804
134 5190 71704
135 5221 71564
136 5251 71407
137 5274 71165
138 5309 71084
139 5344 71008
140 5377 70902
141 5416 70866
142 5452 70803
143 5490 70760
144 5521 70641
145 5553 70522
146 5582 70365
147 5611 70217
148 5636 70026
149 5673 69975
150 5706 69874
151 5738 69764
152 5765 69605
153 5795 69471
154 5817 69246
155 5853 69191
156 5888 69122
157 5924 69070
158 5964 69061
159 5996 68963
160 6022 68789
161 6050 68650
162 6079 68510
163 6108 68385
164 6140 68292
165 6169 68162
166 6202 68074
167 6231 67953
168 6263 67858
169 6295 67764
170 6325 67656
171 6356 67561
172 6395 67545
173 6437 67554
174 6472 67495
175 6503 67395
176 6533 67291
177 6562 67174
178 6590 67049
179 6624 66982
180 6655 66882
181 6687 66804
182 6718 66703
183 6751 66632
184 6784 66559
185 6810 66424
186 6832 66246
187 6867 66187
188 6918 66294
189 6969 66393
190 7018 66470
191 7074 66614
192 7117 66635
193 7191 66943
194 7242 67036
195 7282 67027
196 7317 66967
197 7351 66903
198 7389 66879
199 7432 66896
200 7471 66869
201 7506 66814
202 7540 66752
203 7568 66628
204 7605 66596
205 7638 66519
206 7665 66397
207 7700 66340
208 7734 66276
209 7766 66197
210 7796 66106
211 7831 66053
212 7871 66037
213 7910 66016
214 7951 66014
215 7989 65983
216 8025 65946
217 8058 65872
218 8087 65768
219 8112 65638
220 8148 65594
221 8197 65655
222 8239 65655
223 8268 65556
224 8298 65466
225 8327 65366
226 8357 65278
227 8384 65167
228 8418 65103
229 8453 65058
230 8490 65020
231 8523 64958
232 8550 64848
233 8575 64718
234 8607 64648
235 8635 64545
236 8660 64426
237 8691 64345
238 8719 64250
239 8746 64137
240 8773 64038
241 8803 63951
242 8833 63873
243 8862 63779
244 8892 63698
245 8932 63688
246 8962 63611
247 8991 63521
248 9021 63442
249 9051 63358
250 9085 63306
251 9110 63193
252 9137 63093
253 9174 63066
254 9196 62935
255 9238 62934
256 9267 62855
257 9297 62776
258 9324 62681
259 9357 62625
260 9388 62552
261 9427 62536
262 9461 62491
263 9496 62443
264 9524 62356
265 9553 62278
266 9590 62247
267 9620 62172
268 9645 62071
269 9682 62040
270 9711 61962
271 9739 61872
272 9768 61797
273 9804 61761
274 9848 61777
275 9886 61755
276 9925 61740
277 9965 61728
278 9995 61656
279 10022 61564
280 10055 61516
281 10080 61410
282 10111 61344
283 10147 61311
284 10175 61230
285 10202 61141
286 10234 61084
287 10264 61018
288 10299 60977
289 10323 60874
290 10353 60804
291 10394 60803
292 10431 60773
293 10471 60763
294 10503 60707
295 10534 60645
296 10576 60646
297 10612 60612
298 10639 60525
299 10668 60453
300 10702 60411
301 10729 60326
302 10764 60290
303 10801 60263
304 10829 60182
305 10857 60108
306 10892 60067
307 10930 60047
308 10972 60045
309 11002 59983
310 11030 59902
311 11058 59828
312 11092 59788
313 11117 59696
314 11149 59641
315 11187 59617
316 11211 59525
317 11243 59468
318 11274 59413
319 11304 59346
320 11334 59287
321 11362 59209
322 11394 59158
323 11436 59158
324 11477 59153
325 11513 59122
326 11547 59081
327 11572 58991
328 11607 58956
329 11637 58894
330 11668 58833
331 11700 58785
332 11724 58694
333 11757 58648
334 11780 58550
335 11815 58515
336 11844 58451
337 11869 58364
338 11905 58335
339 11941 58302
340 11986 58315
341 12020 58274
342 12066 58292
343 12122 58358
344 12177 58415
345 12221 58422
346 12264 58423
347 12300 58394
348 12324 58304
349 12354 58243
350 12401 58262
351 12438 58232
352 12479 58228
353 12512 58179
354 12541 58116
355 12569 58044
356 12597 57977
357 12628 57920
358 12653 57839
359 12682 57775
360 12720 57752
361 12744 57666
362 12770 57592
363 12811 57583
364 12841 57522
365 12870 57460
366 12897 57386
367 12938 57378
368 12974 57347
369 13009 57313
370 13038 57249
371 13078 57235
372 13117 57216
373 13147 57159
374 13181 57118
375 13205 57036
376 13235 56979
377 13274 56960
378 13306 56911
379 13333 56841
380 13366 56798
381 13396 56741
382 13421 56666
383 13467 56674
384 13508 56664
385 13540 56616
386 13569 56559
387 13598 56496
388 13627 56438
389 13656 56376
390 13685 56317
391 13717 56271
392 13750 56227
393 13771 56135
394 13804 56090
395 13825 55999
396 13858 55957
397 13888 55904
398 13917 55843
399 13953 55812
400 13994 55802
401 14025 55752
402 14048 55670
403 14076 55607
404 14105 55551
405 14142 55526
406 14182 55511
407 14214 55464
408 14240 55394
409 14267 55328
410 14299 55284
411 14324 55213
412 14351 55146
413 14379 55086
414 14410 55036
415 14451 55025
416 14484 54984
417 14513 54929
418 14536 54851
419 14565 54793
420 14587 54710
421 14615 54650
422 14642 54588
423 14666 54515
424 14690 54441
425 14719 54384
426 14739 54297
427 14772 54257
428 14790 54164
429 14824 54125
430 14844 54039
431 14876 53995
432 14906 53946
433 14938 53902
434 14980 53894
435 15006 53829
436 15033 53770
437 15059 53706
438 15085 53639
439 15110 53574
440 15134 53503
441 15160 53438
442 15184 53369
443 15211 53308
444 15234 53236
445 15266 53193
446 15287 53114
447 15316 53059
448 15336 52978
449 15366 52929
450 15393 52870
451 15429 52843
452 15469 52828
453 15490 52748
454 15523 52712
455 15550 52653
456 15577 52594
457 15604 52536
458 15630 52476
459 15656 52414
460 15682 52353
461 15711 52304
462 15736 52238
463 15765 52188
464 15786 52112
465 15817 52068
466 15839 51996
467 15873 51961
468 15903 51916
469 15935 51873
470 15969 51840
471 15994 51779
472 16022 51726
473 16047 51663
474 16073 51605
475 16099 51546
476 16128 51495
477 16152 51431
478 16176 51367
479 16205 51317
480 16228 51250
481 16255 51194
482 16277 51123
483 16305 51071
484 16328 51005
485 16362 50973
486 16392 50928
487 16426 50894
488 16459 50860
489 16480 50787
490 16510 50743
491 16530 50668
492 16561 50625
493 16585 50562
494 16613 50510
495 16638 50453
496 16663 50393
497 16690 50339
498 16716 50282
499 16740 50222
500 16773 50186
501 16802 50139
502 16836 50107
503 16873 50085
504 16921 50094
505 16989 50163
506 17038 50173
507 17069 50132
508 17110 50121
509 17145 50091
510 17190 50091
511 17219 50044
512 17247 49994
513 17271 49932
514 17298 49878
515 17343 49878
516 17373 49836
517 17417 49831
518 17460 49823
519 17490 49781
520 17518 49731
521 17546 49680
522 17571 49622
523 17600 49577
524 17625 49520
525 17655 49474
526 17679 49414
527 17707 49366
528 17729 49300
529 17758 49254
530 17781 49191
531 17808 49141
532 17829 49071
533 17862 49038
534 17905 49031
535 18028 49241
536 18072 49236
537 18106 49203
538 18135 49157
539 18165 49114
540 18200 49083
541 18223 49022
542 18254 48980
543 18280 48927
544 18307 48876
545 18338 48834
546 18367 48790
547 18411 48783
548 18444 48747
549 18470 48693
550 18503 48660
551 18531 48611
552 18557 48558
553 18584 48508
554 18625 48493
555 18650 48436
556 18677 48388
557 18703 48333
558 18729 48282
559 18756 48231
560 18781 48176
561 18808 48126
562 18834 48074
563 18869 48043
564 18902 48008
565 18930 47960
566 18958 47914
567 18983 47859
568 19016 47824
569 19037 47761
570 19068 47720
571 19090 47660
572 19111 47595
573 19141 47553
574 19164 47494
575 19196 47458
576 19217 47393
577 19249 47358
578 19274 47303
579 19298 47247
580 19324 47195
581 19357 47162
582 19391 47130
583 19427 47103
584 19460 47070
585 19483 47012
586 19511 46967
587 19542 46929
588 19564 46867
589 19597 46833
590 19621 46779
591 19647 46729
592 19670 46672
593 19699 46627
594 19726 46582
595 19753 46532
596 19778 46480
597 19803 46429
598 19830 46381
599 19857 46335
600 19896 46313
601 19925 46271
602 19957 46236
603 19991 46204
604 20019 46159
605 20047 46115
606 20072 46063
607 20098 46015
608 20123 45963
609 20149 45913
610 20176 45867
611 20202 45817
612 20230 45774
613 20253 45719
614 20285 45682
615 20307 45626
616 20338 45589
617 20361 45532
618 20394 45500
619 20423 45459
620 20454 45420
621 20488 45390
622 20510 45333
623 20543 45301
624 20569 45252
625 20594 45201
626 20619 45151
627 20646 45107
628 20675 45066
629 20701 45016
630 20727 44970
631 20752 44919
632 20782 44881
633 20804 44825
634 20837 44791
635 20862 44742
636 20892 44704
637 20931 44683
638 20960 44643
639 20994 44612
640 21022 44570
641 21052 44531
642 21082 44493
643 21107 44443
644 21135 44401
645 21160 44351
646 21185 44302
647 21210 44253
648 21236 44208
649 21262 44161
650 21288 44113
651 21315 44068
652 21343 44027
653 21377 43997
654 21403 43949
655 21440 43926
656 21477 43903
657 21502 43854
658 21533 43819
659 21559 43772
660 21586 43727
661 21611 43680
662 21637 43633
663 21662 43586
664 21688 43539
665 21714 43493
666 21742 43451
667 21771 43413
668 21818 43409
669 21846 43366
670 21888 43352
671 21934 43345
672 21971 43322
673 22019 43320
674 22053 43289
675 22090 43266
676 22141 43269
677 22176 43240
678 22213 43215
679 22239 43171
680 22270 43134
681 22296 43088
682 22321 43041
683 22350 43002
684 22379 42962
685 22419 42944
686 22452 42912
687 22484 42878
688 22511 42834
689 22537 42789
690 22571 42757
691 22598 42714
692 22624 42669
693 22653 42630
694 22680 42586
695 22708 42545
696 22739 42510
697 22761 42457
698 22792 42421
699 22816 42373
700 22845 42333
701 22870 42288
702 22902 42253
703 22942 42234
704 22974 42201
705 23002 42160
706 23033 42124
707 23054 42071
708 23086 42038
709 23115 41999
710 23143 41957
711 23169 41914
712 23195 41868
713 23230 41840
714 23259 41801
715 23287 41760
716 23311 41713
717 23341 41676
718 23372 41641
719 23405 41610
720 23438 41578
721 23483 41566
722 23507 41519
723 23540 41488
724 23566 41444
725 23595 41406
726 23623 41365
727 23648 41320
728 23677 41281
729 23700 41231
730 23728 41192
731 23752 41144
732 23784 41111
733 23807 41063
734 23840 41031
735 23870 40994
736 23908 40972
737 23941 40940
738 23974 40909
739 24006 40875
740 24036 40838
741 24064 40798
742 24092 40759
743 24127 40730
744 24153 40688
745 24179 40644
746 24207 40604
747 24233 40561
748 24261 40522
749 24295 40491
750 24318 40444
751 24349 40410
752 24376 40368
753 24408 40335
754 24442 40306
755 24474 40273
756 24508 40242
757 24548 40222
758 24575 40182
759 24605 40145
760 24632 40104
761 24660 40064
762 24689 40027
763 24714 39982
764 24745 39949
765 24766 39897
766 24797 39863
767 24825 39823
768 24854 39786
769 24880 39744
770 24909 39706
771 24940 39672
772 24970 39635
773 25004 39606
774 25030 39564
775 25056 39522
776 25086 39486
777 25107 39436
778 25139 39403
779 25159 39351
780 25188 39314
781 25214 39272
782 25240 39230
783 25266 39188
784 25288 39141
785 25315 39101
786 25341 39058
787 25367 39016
788 25391 38972
789 25417 38930
790 25448 38895
791 25482 38867
792 25514 38834
793 25542 38795
794 25569 38756
795 25595 38714
796 25618 38669
797 25643 38626
798 25667 38581
799 25695 38543
800 25716 38494
801 25743 38454
802 25770 38415
803 25790 38364
804 25822 38332
805 25843 38284
806 25873 38249
807 25896 38203
808 25925 38167
809 25955 38131
810 25988 38101
811 26028 38080
812 26055 38042
813 26081 38000
814 26108 37961
815 26131 37916
816 26159 37878
817 26188 37841
818 26214 37800
819 26242 37764
820 26272 37728
821 26298 37688
822 26327 37652
823 26359 37619
824 26385 37580
825 26408 37534
826 26444 37507
827 26477 37478
828 26517 37456
829 26539 37411
830 26573 37382
831 26597 37339
832 26623 37298
833 26650 37259
834 26677 37221
835 26704 37182
836 26728 37138
837 26763 37111
838 26791 37073
839 26822 37041
840 26872 37033
841 26924 37029
842 26982 37033
843 27054 37055
844 27097 37038
845 27120 36994
846 27146 36954
847 27180 36925
848 27206 36884
849 27234 36846
850 27260 36807
851 27289 36770
852 27318 36734
853 27347 36698
854 27386 36675
855 27413 36637
856 27439 36596
857 27471 36564
858 27501 36529
859 27535 36500
860 27572 36474
861 27595 36431
862 27627 36398
863 27654 36360
864 27683 36324
865 27711 36287
866 27738 36249
867 27765 36210
868 27794 36175
869 27820 36135
1 iter Passed Remaining
2 0 46 93548
3 1 83 83419
4 2 132 88415
5 3 162 81250
6 4 196 78573
7 5 230 76747
8 6 269 76701
9 7 319 79674
10 8 364 80653
11 9 411 81918
12 10 456 82497
13 11 491 81432
14 12 522 79809
15 13 555 78774
16 14 595 78777
17 15 630 78123
18 16 662 77290
19 17 700 77124
20 18 730 76120
21 19 764 75651
22 20 804 75774
23 21 835 75128
24 22 886 76169
25 23 920 75764
26 24 960 75853
27 25 989 75130
28 26 1025 74941
29 27 1060 74714
30 28 1104 75079
31 29 1141 74976
32 30 1180 74975
33 31 1213 74640
34 32 1245 74260
35 33 1287 74434
36 34 1327 74528
37 35 1376 75071
38 36 1427 75741
39 37 1468 75804
40 38 1508 75857
41 39 1549 75922
42 40 1586 75781
43 41 1621 75590
44 42 1663 75705
45 43 1701 75621
46 44 1739 75591
47 45 1776 75460
48 46 1819 75616
49 47 1869 76025
50 48 1916 76288
51 49 1953 76191
52 50 1993 76197
53 51 2038 76381
54 52 2080 76420
55 53 2158 77788
56 54 2220 78529
57 55 2286 79390
58 56 2328 79372
59 57 2367 79254
60 58 2409 79257
61 59 2444 79049
62 60 2484 78985
63 61 2521 78820
64 62 2554 78528
65 63 2593 78466
66 64 2623 78111
67 65 2660 77969
68 66 2695 77776
69 67 2725 77446
70 68 2761 77291
71 69 2791 76975
72 70 2824 76739
73 71 2861 76611
74 72 2897 76476
75 73 2935 76408
76 74 3040 78027
77 75 3097 78411
78 76 3152 78741
79 77 3216 79248
80 78 3256 79195
81 79 3305 79336
82 80 3348 79320
83 81 3381 79089
84 82 3416 78911
85 83 3480 79399
86 84 3535 79649
87 85 3581 79716
88 86 3612 79428
89 87 3644 79185
90 88 3678 78975
91 89 3712 78785
92 90 3743 78531
93 91 3775 78297
94 92 3806 78047
95 93 3837 77821
96 94 3871 77629
97 95 3913 77618
98 96 3945 77403
99 97 3989 77433
100 98 4020 77204
101 99 4053 77020
102 100 4084 76789
103 101 4116 76597
104 102 4148 76401
105 103 4176 76141
106 104 4202 75845
107 105 4232 75634
108 106 4261 75390
109 107 4290 75168
110 108 4324 75018
111 109 4351 74766
112 110 4386 74648
113 111 4424 74577
114 112 4458 74455
115 113 4497 74400
116 114 4533 74307
117 115 4564 74136
118 116 4596 73981
119 117 4628 73818
120 118 4668 73786
121 119 4692 73509
122 120 4723 73354
123 121 4756 73220
124 122 4788 73065
125 123 4815 72854
126 124 4843 72647
127 125 4875 72514
128 126 4916 72515
129 127 4952 72436
130 128 4991 72397
131 129 5028 72327
132 130 5059 72180
133 131 5096 72116
134 132 5125 71946
135 133 5156 71804
136 134 5190 71704
137 135 5221 71564
138 136 5251 71407
139 137 5274 71165
140 138 5309 71084
141 139 5344 71008
142 140 5377 70902
143 141 5416 70866
144 142 5452 70803
145 143 5490 70760
146 144 5521 70641
147 145 5553 70522
148 146 5582 70365
149 147 5611 70217
150 148 5636 70026
151 149 5673 69975
152 150 5706 69874
153 151 5738 69764
154 152 5765 69605
155 153 5795 69471
156 154 5817 69246
157 155 5853 69191
158 156 5888 69122
159 157 5924 69070
160 158 5964 69061
161 159 5996 68963
162 160 6022 68789
163 161 6050 68650
164 162 6079 68510
165 163 6108 68385
166 164 6140 68292
167 165 6169 68162
168 166 6202 68074
169 167 6231 67953
170 168 6263 67858
171 169 6295 67764
172 170 6325 67656
173 171 6356 67561
174 172 6395 67545
175 173 6437 67554
176 174 6472 67495
177 175 6503 67395
178 176 6533 67291
179 177 6562 67174
180 178 6590 67049
181 179 6624 66982
182 180 6655 66882
183 181 6687 66804
184 182 6718 66703
185 183 6751 66632
186 184 6784 66559
187 185 6810 66424
188 186 6832 66246
189 187 6867 66187
190 188 6918 66294
191 189 6969 66393
192 190 7018 66470
193 191 7074 66614
194 192 7117 66635
195 193 7191 66943
196 194 7242 67036
197 195 7282 67027
198 196 7317 66967
199 197 7351 66903
200 198 7389 66879
201 199 7432 66896
202 200 7471 66869
203 201 7506 66814
204 202 7540 66752
205 203 7568 66628
206 204 7605 66596
207 205 7638 66519
208 206 7665 66397
209 207 7700 66340
210 208 7734 66276
211 209 7766 66197
212 210 7796 66106
213 211 7831 66053
214 212 7871 66037
215 213 7910 66016
216 214 7951 66014
217 215 7989 65983
218 216 8025 65946
219 217 8058 65872
220 218 8087 65768
221 219 8112 65638
222 220 8148 65594
223 221 8197 65655
224 222 8239 65655
225 223 8268 65556
226 224 8298 65466
227 225 8327 65366
228 226 8357 65278
229 227 8384 65167
230 228 8418 65103
231 229 8453 65058
232 230 8490 65020
233 231 8523 64958
234 232 8550 64848
235 233 8575 64718
236 234 8607 64648
237 235 8635 64545
238 236 8660 64426
239 237 8691 64345
240 238 8719 64250
241 239 8746 64137
242 240 8773 64038
243 241 8803 63951
244 242 8833 63873
245 243 8862 63779
246 244 8892 63698
247 245 8932 63688
248 246 8962 63611
249 247 8991 63521
250 248 9021 63442
251 249 9051 63358
252 250 9085 63306
253 251 9110 63193
254 252 9137 63093
255 253 9174 63066
256 254 9196 62935
257 255 9238 62934
258 256 9267 62855
259 257 9297 62776
260 258 9324 62681
261 259 9357 62625
262 260 9388 62552
263 261 9427 62536
264 262 9461 62491
265 263 9496 62443
266 264 9524 62356
267 265 9553 62278
268 266 9590 62247
269 267 9620 62172
270 268 9645 62071
271 269 9682 62040
272 270 9711 61962
273 271 9739 61872
274 272 9768 61797
275 273 9804 61761
276 274 9848 61777
277 275 9886 61755
278 276 9925 61740
279 277 9965 61728
280 278 9995 61656
281 279 10022 61564
282 280 10055 61516
283 281 10080 61410
284 282 10111 61344
285 283 10147 61311
286 284 10175 61230
287 285 10202 61141
288 286 10234 61084
289 287 10264 61018
290 288 10299 60977
291 289 10323 60874
292 290 10353 60804
293 291 10394 60803
294 292 10431 60773
295 293 10471 60763
296 294 10503 60707
297 295 10534 60645
298 296 10576 60646
299 297 10612 60612
300 298 10639 60525
301 299 10668 60453
302 300 10702 60411
303 301 10729 60326
304 302 10764 60290
305 303 10801 60263
306 304 10829 60182
307 305 10857 60108
308 306 10892 60067
309 307 10930 60047
310 308 10972 60045
311 309 11002 59983
312 310 11030 59902
313 311 11058 59828
314 312 11092 59788
315 313 11117 59696
316 314 11149 59641
317 315 11187 59617
318 316 11211 59525
319 317 11243 59468
320 318 11274 59413
321 319 11304 59346
322 320 11334 59287
323 321 11362 59209
324 322 11394 59158
325 323 11436 59158
326 324 11477 59153
327 325 11513 59122
328 326 11547 59081
329 327 11572 58991
330 328 11607 58956
331 329 11637 58894
332 330 11668 58833
333 331 11700 58785
334 332 11724 58694
335 333 11757 58648
336 334 11780 58550
337 335 11815 58515
338 336 11844 58451
339 337 11869 58364
340 338 11905 58335
341 339 11941 58302
342 340 11986 58315
343 341 12020 58274
344 342 12066 58292
345 343 12122 58358
346 344 12177 58415
347 345 12221 58422
348 346 12264 58423
349 347 12300 58394
350 348 12324 58304
351 349 12354 58243
352 350 12401 58262
353 351 12438 58232
354 352 12479 58228
355 353 12512 58179
356 354 12541 58116
357 355 12569 58044
358 356 12597 57977
359 357 12628 57920
360 358 12653 57839
361 359 12682 57775
362 360 12720 57752
363 361 12744 57666
364 362 12770 57592
365 363 12811 57583
366 364 12841 57522
367 365 12870 57460
368 366 12897 57386
369 367 12938 57378
370 368 12974 57347
371 369 13009 57313
372 370 13038 57249
373 371 13078 57235
374 372 13117 57216
375 373 13147 57159
376 374 13181 57118
377 375 13205 57036
378 376 13235 56979
379 377 13274 56960
380 378 13306 56911
381 379 13333 56841
382 380 13366 56798
383 381 13396 56741
384 382 13421 56666
385 383 13467 56674
386 384 13508 56664
387 385 13540 56616
388 386 13569 56559
389 387 13598 56496
390 388 13627 56438
391 389 13656 56376
392 390 13685 56317
393 391 13717 56271
394 392 13750 56227
395 393 13771 56135
396 394 13804 56090
397 395 13825 55999
398 396 13858 55957
399 397 13888 55904
400 398 13917 55843
401 399 13953 55812
402 400 13994 55802
403 401 14025 55752
404 402 14048 55670
405 403 14076 55607
406 404 14105 55551
407 405 14142 55526
408 406 14182 55511
409 407 14214 55464
410 408 14240 55394
411 409 14267 55328
412 410 14299 55284
413 411 14324 55213
414 412 14351 55146
415 413 14379 55086
416 414 14410 55036
417 415 14451 55025
418 416 14484 54984
419 417 14513 54929
420 418 14536 54851
421 419 14565 54793
422 420 14587 54710
423 421 14615 54650
424 422 14642 54588
425 423 14666 54515
426 424 14690 54441
427 425 14719 54384
428 426 14739 54297
429 427 14772 54257
430 428 14790 54164
431 429 14824 54125
432 430 14844 54039
433 431 14876 53995
434 432 14906 53946
435 433 14938 53902
436 434 14980 53894
437 435 15006 53829
438 436 15033 53770
439 437 15059 53706
440 438 15085 53639
441 439 15110 53574
442 440 15134 53503
443 441 15160 53438
444 442 15184 53369
445 443 15211 53308
446 444 15234 53236
447 445 15266 53193
448 446 15287 53114
449 447 15316 53059
450 448 15336 52978
451 449 15366 52929
452 450 15393 52870
453 451 15429 52843
454 452 15469 52828
455 453 15490 52748
456 454 15523 52712
457 455 15550 52653
458 456 15577 52594
459 457 15604 52536
460 458 15630 52476
461 459 15656 52414
462 460 15682 52353
463 461 15711 52304
464 462 15736 52238
465 463 15765 52188
466 464 15786 52112
467 465 15817 52068
468 466 15839 51996
469 467 15873 51961
470 468 15903 51916
471 469 15935 51873
472 470 15969 51840
473 471 15994 51779
474 472 16022 51726
475 473 16047 51663
476 474 16073 51605
477 475 16099 51546
478 476 16128 51495
479 477 16152 51431
480 478 16176 51367
481 479 16205 51317
482 480 16228 51250
483 481 16255 51194
484 482 16277 51123
485 483 16305 51071
486 484 16328 51005
487 485 16362 50973
488 486 16392 50928
489 487 16426 50894
490 488 16459 50860
491 489 16480 50787
492 490 16510 50743
493 491 16530 50668
494 492 16561 50625
495 493 16585 50562
496 494 16613 50510
497 495 16638 50453
498 496 16663 50393
499 497 16690 50339
500 498 16716 50282
501 499 16740 50222
502 500 16773 50186
503 501 16802 50139
504 502 16836 50107
505 503 16873 50085
506 504 16921 50094
507 505 16989 50163
508 506 17038 50173
509 507 17069 50132
510 508 17110 50121
511 509 17145 50091
512 510 17190 50091
513 511 17219 50044
514 512 17247 49994
515 513 17271 49932
516 514 17298 49878
517 515 17343 49878
518 516 17373 49836
519 517 17417 49831
520 518 17460 49823
521 519 17490 49781
522 520 17518 49731
523 521 17546 49680
524 522 17571 49622
525 523 17600 49577
526 524 17625 49520
527 525 17655 49474
528 526 17679 49414
529 527 17707 49366
530 528 17729 49300
531 529 17758 49254
532 530 17781 49191
533 531 17808 49141
534 532 17829 49071
535 533 17862 49038
536 534 17905 49031
537 535 18028 49241
538 536 18072 49236
539 537 18106 49203
540 538 18135 49157
541 539 18165 49114
542 540 18200 49083
543 541 18223 49022
544 542 18254 48980
545 543 18280 48927
546 544 18307 48876
547 545 18338 48834
548 546 18367 48790
549 547 18411 48783
550 548 18444 48747
551 549 18470 48693
552 550 18503 48660
553 551 18531 48611
554 552 18557 48558
555 553 18584 48508
556 554 18625 48493
557 555 18650 48436
558 556 18677 48388
559 557 18703 48333
560 558 18729 48282
561 559 18756 48231
562 560 18781 48176
563 561 18808 48126
564 562 18834 48074
565 563 18869 48043
566 564 18902 48008
567 565 18930 47960
568 566 18958 47914
569 567 18983 47859
570 568 19016 47824
571 569 19037 47761
572 570 19068 47720
573 571 19090 47660
574 572 19111 47595
575 573 19141 47553
576 574 19164 47494
577 575 19196 47458
578 576 19217 47393
579 577 19249 47358
580 578 19274 47303
581 579 19298 47247
582 580 19324 47195
583 581 19357 47162
584 582 19391 47130
585 583 19427 47103
586 584 19460 47070
587 585 19483 47012
588 586 19511 46967
589 587 19542 46929
590 588 19564 46867
591 589 19597 46833
592 590 19621 46779
593 591 19647 46729
594 592 19670 46672
595 593 19699 46627
596 594 19726 46582
597 595 19753 46532
598 596 19778 46480
599 597 19803 46429
600 598 19830 46381
601 599 19857 46335
602 600 19896 46313
603 601 19925 46271
604 602 19957 46236
605 603 19991 46204
606 604 20019 46159
607 605 20047 46115
608 606 20072 46063
609 607 20098 46015
610 608 20123 45963
611 609 20149 45913
612 610 20176 45867
613 611 20202 45817
614 612 20230 45774
615 613 20253 45719
616 614 20285 45682
617 615 20307 45626
618 616 20338 45589
619 617 20361 45532
620 618 20394 45500
621 619 20423 45459
622 620 20454 45420
623 621 20488 45390
624 622 20510 45333
625 623 20543 45301
626 624 20569 45252
627 625 20594 45201
628 626 20619 45151
629 627 20646 45107
630 628 20675 45066
631 629 20701 45016
632 630 20727 44970
633 631 20752 44919
634 632 20782 44881
635 633 20804 44825
636 634 20837 44791
637 635 20862 44742
638 636 20892 44704
639 637 20931 44683
640 638 20960 44643
641 639 20994 44612
642 640 21022 44570
643 641 21052 44531
644 642 21082 44493
645 643 21107 44443
646 644 21135 44401
647 645 21160 44351
648 646 21185 44302
649 647 21210 44253
650 648 21236 44208
651 649 21262 44161
652 650 21288 44113
653 651 21315 44068
654 652 21343 44027
655 653 21377 43997
656 654 21403 43949
657 655 21440 43926
658 656 21477 43903
659 657 21502 43854
660 658 21533 43819
661 659 21559 43772
662 660 21586 43727
663 661 21611 43680
664 662 21637 43633
665 663 21662 43586
666 664 21688 43539
667 665 21714 43493
668 666 21742 43451
669 667 21771 43413
670 668 21818 43409
671 669 21846 43366
672 670 21888 43352
673 671 21934 43345
674 672 21971 43322
675 673 22019 43320
676 674 22053 43289
677 675 22090 43266
678 676 22141 43269
679 677 22176 43240
680 678 22213 43215
681 679 22239 43171
682 680 22270 43134
683 681 22296 43088
684 682 22321 43041
685 683 22350 43002
686 684 22379 42962
687 685 22419 42944
688 686 22452 42912
689 687 22484 42878
690 688 22511 42834
691 689 22537 42789
692 690 22571 42757
693 691 22598 42714
694 692 22624 42669
695 693 22653 42630
696 694 22680 42586
697 695 22708 42545
698 696 22739 42510
699 697 22761 42457
700 698 22792 42421
701 699 22816 42373
702 700 22845 42333
703 701 22870 42288
704 702 22902 42253
705 703 22942 42234
706 704 22974 42201
707 705 23002 42160
708 706 23033 42124
709 707 23054 42071
710 708 23086 42038
711 709 23115 41999
712 710 23143 41957
713 711 23169 41914
714 712 23195 41868
715 713 23230 41840
716 714 23259 41801
717 715 23287 41760
718 716 23311 41713
719 717 23341 41676
720 718 23372 41641
721 719 23405 41610
722 720 23438 41578
723 721 23483 41566
724 722 23507 41519
725 723 23540 41488
726 724 23566 41444
727 725 23595 41406
728 726 23623 41365
729 727 23648 41320
730 728 23677 41281
731 729 23700 41231
732 730 23728 41192
733 731 23752 41144
734 732 23784 41111
735 733 23807 41063
736 734 23840 41031
737 735 23870 40994
738 736 23908 40972
739 737 23941 40940
740 738 23974 40909
741 739 24006 40875
742 740 24036 40838
743 741 24064 40798
744 742 24092 40759
745 743 24127 40730
746 744 24153 40688
747 745 24179 40644
748 746 24207 40604
749 747 24233 40561
750 748 24261 40522
751 749 24295 40491
752 750 24318 40444
753 751 24349 40410
754 752 24376 40368
755 753 24408 40335
756 754 24442 40306
757 755 24474 40273
758 756 24508 40242
759 757 24548 40222
760 758 24575 40182
761 759 24605 40145
762 760 24632 40104
763 761 24660 40064
764 762 24689 40027
765 763 24714 39982
766 764 24745 39949
767 765 24766 39897
768 766 24797 39863
769 767 24825 39823
770 768 24854 39786
771 769 24880 39744
772 770 24909 39706
773 771 24940 39672
774 772 24970 39635
775 773 25004 39606
776 774 25030 39564
777 775 25056 39522
778 776 25086 39486
779 777 25107 39436
780 778 25139 39403
781 779 25159 39351
782 780 25188 39314
783 781 25214 39272
784 782 25240 39230
785 783 25266 39188
786 784 25288 39141
787 785 25315 39101
788 786 25341 39058
789 787 25367 39016
790 788 25391 38972
791 789 25417 38930
792 790 25448 38895
793 791 25482 38867
794 792 25514 38834
795 793 25542 38795
796 794 25569 38756
797 795 25595 38714
798 796 25618 38669
799 797 25643 38626
800 798 25667 38581
801 799 25695 38543
802 800 25716 38494
803 801 25743 38454
804 802 25770 38415
805 803 25790 38364
806 804 25822 38332
807 805 25843 38284
808 806 25873 38249
809 807 25896 38203
810 808 25925 38167
811 809 25955 38131
812 810 25988 38101
813 811 26028 38080
814 812 26055 38042
815 813 26081 38000
816 814 26108 37961
817 815 26131 37916
818 816 26159 37878
819 817 26188 37841
820 818 26214 37800
821 819 26242 37764
822 820 26272 37728
823 821 26298 37688
824 822 26327 37652
825 823 26359 37619
826 824 26385 37580
827 825 26408 37534
828 826 26444 37507
829 827 26477 37478
830 828 26517 37456
831 829 26539 37411
832 830 26573 37382
833 831 26597 37339
834 832 26623 37298
835 833 26650 37259
836 834 26677 37221
837 835 26704 37182
838 836 26728 37138
839 837 26763 37111
840 838 26791 37073
841 839 26822 37041
842 840 26872 37033
843 841 26924 37029
844 842 26982 37033
845 843 27054 37055
846 844 27097 37038
847 845 27120 36994
848 846 27146 36954
849 847 27180 36925
850 848 27206 36884
851 849 27234 36846
852 850 27260 36807
853 851 27289 36770
854 852 27318 36734
855 853 27347 36698
856 854 27386 36675
857 855 27413 36637
858 856 27439 36596
859 857 27471 36564
860 858 27501 36529
861 859 27535 36500
862 860 27572 36474
863 861 27595 36431
864 862 27627 36398
865 863 27654 36360
866 864 27683 36324
867 865 27711 36287
868 866 27738 36249
869 867 27765 36210
870 868 27794 36175
871 869 27820 36135
+73 -4
View File
@@ -1,17 +1,19 @@
import os import os
import json
import yaml import yaml
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
class EnsembleConfig: class EnsembleConfig:
_instance: Optional['EnsembleConfig'] = None _instance: Optional['EnsembleConfig'] = None
_config: Dict[str, Any] = {} _config: Dict[str, Any] = {}
def __new__(cls): def __new__(cls):
if cls._instance is None: if cls._instance is None:
cls._instance = super(EnsembleConfig, cls).__new__(cls) cls._instance = super(EnsembleConfig, cls).__new__(cls)
cls._instance._load_config() cls._instance._load_config()
return cls._instance return cls._instance
def _load_config(self): def _load_config(self):
"""Load configuration from YAML file.""" """Load configuration from YAML file."""
config_path = os.path.join(os.path.dirname(__file__), 'ensemble_config.yaml') config_path = os.path.join(os.path.dirname(__file__), 'ensemble_config.yaml')
@@ -22,12 +24,12 @@ class EnsembleConfig:
except Exception as e: except Exception as e:
print(f"❌ Failed to load ensemble config: {e}") print(f"❌ Failed to load ensemble config: {e}")
self._config = {} self._config = {}
def get(self, key: str, default: Any = None) -> Any: def get(self, key: str, default: Any = None) -> Any:
"""Get configuration value by key (supports dot notation for nested keys).""" """Get configuration value by key (supports dot notation for nested keys)."""
keys = key.split('.') keys = key.split('.')
value = self._config value = self._config
try: try:
for k in keys: for k in keys:
value = value[k] value = value[k]
@@ -35,12 +37,79 @@ class EnsembleConfig:
except (KeyError, TypeError): except (KeyError, TypeError):
return default return default
# Singleton accessor # Singleton accessor
def get_config() -> EnsembleConfig: def get_config() -> EnsembleConfig:
return EnsembleConfig() return EnsembleConfig()
# ── Market Thresholds Loader ────────────────────────────────────────────
_market_thresholds_cache: Optional[Dict[str, Any]] = None
def load_market_thresholds() -> Dict[str, Any]:
"""
Load market thresholds from JSON config file.
Returns the full config dict with 'markets' and 'defaults' keys.
Caches after first load for performance.
"""
global _market_thresholds_cache
if _market_thresholds_cache is not None:
return _market_thresholds_cache
config_path = os.path.join(os.path.dirname(__file__), 'market_thresholds.json')
try:
with open(config_path, 'r', encoding='utf-8') as f:
data = json.load(f)
_market_thresholds_cache = data
print(f"✅ Market thresholds loaded: {len(data.get('markets', {}))} markets (v={data.get('_meta', {}).get('version', '?')})")
return data
except Exception as e:
print(f"❌ Failed to load market thresholds: {e} — using built-in defaults")
_market_thresholds_cache = {"markets": {}, "defaults": {
"calibration": 0.55,
"min_conf": 55.0,
"min_play_score": 68.0,
"min_edge": 0.02,
"odds_band_min_sample": 0.0,
"odds_band_min_edge": 0.0,
}}
return _market_thresholds_cache
def build_threshold_dict(field: str) -> Dict[str, float]:
"""
Build a flat {market: value} dict for a specific threshold field.
Usage:
calibration_map = build_threshold_dict("calibration")
# → {"MS": 0.62, "DC": 0.82, ...}
"""
data = load_market_thresholds()
markets = data.get("markets", {})
result: Dict[str, float] = {}
for market, cfg in markets.items():
if field in cfg:
result[market] = float(cfg[field])
return result
def get_threshold_default(field: str) -> float:
"""Get the default fallback value for a threshold field."""
data = load_market_thresholds()
defaults = data.get("defaults", {})
return float(defaults.get(field, 0.0))
if __name__ == "__main__": if __name__ == "__main__":
# Test # Test
cfg = get_config() cfg = get_config()
print(f"Weights: {cfg.get('engine_weights')}") print(f"Weights: {cfg.get('engine_weights')}")
print(f"Team Weight: {cfg.get('engine_weights.team')}") print(f"Team Weight: {cfg.get('engine_weights.team')}")
print()
print("--- Market Thresholds ---")
for field in ["calibration", "min_conf", "min_play_score", "min_edge"]:
d = build_threshold_dict(field)
print(f"{field}: {d}")
print(f"Default calibration: {get_threshold_default('calibration')}")
+11
View File
@@ -1,3 +1,14 @@
model_ensemble:
xgb_weight: 0.50
lgb_weight: 0.50
temperature: 1.5
default_ms_odds:
home: 2.65
draw: 3.20
away: 2.65
elo_staleness_days: 14
odds_staleness_hours: 48
engine_weights: engine_weights:
team: 0.30 team: 0.30
player: 0.25 player: 0.25
+115
View File
@@ -0,0 +1,115 @@
{
"_meta": {
"version": "v34",
"description": "Market-specific thresholds for the betting engine pipeline — V34 odds-aware gate fix",
"rule": "max_reachable (100 × calibration) MUST be > min_conf + 8",
"updated_at": "2026-05-10",
"changelog": "V34: Reduced min_edge to realistic levels for odds-aware V25 model. Model output ≈ market-implied, so large EV edges are mathematically impossible."
},
"markets": {
"MS": {
"calibration": 0.62,
"min_conf": 20.0,
"min_play_score": 28.0,
"min_edge": 0.005,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"DC": {
"calibration": 0.82,
"min_conf": 40.0,
"min_play_score": 50.0,
"min_edge": 0.003,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"OU15": {
"calibration": 0.84,
"min_conf": 45.0,
"min_play_score": 50.0,
"min_edge": 0.003,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"OU25": {
"calibration": 0.68,
"min_conf": 30.0,
"min_play_score": 40.0,
"min_edge": 0.005,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"OU35": {
"calibration": 0.60,
"min_conf": 20.0,
"min_play_score": 30.0,
"min_edge": 0.008,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.008
},
"BTTS": {
"calibration": 0.65,
"min_conf": 30.0,
"min_play_score": 40.0,
"min_edge": 0.005,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"HT": {
"calibration": 0.58,
"min_conf": 20.0,
"min_play_score": 28.0,
"min_edge": 0.01,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.008
},
"HT_OU05": {
"calibration": 0.68,
"min_conf": 35.0,
"min_play_score": 42.0,
"min_edge": 0.005,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.005
},
"HT_OU15": {
"calibration": 0.60,
"min_conf": 25.0,
"min_play_score": 32.0,
"min_edge": 0.008,
"odds_band_min_sample": 8.0,
"odds_band_min_edge": 0.008
},
"OE": {
"calibration": 0.62,
"min_conf": 35.0,
"min_play_score": 32.0,
"min_edge": 0.005
},
"CARDS": {
"calibration": 0.58,
"min_conf": 30.0,
"min_play_score": 35.0,
"min_edge": 0.008
},
"HCAP": {
"calibration": 0.56,
"min_conf": 25.0,
"min_play_score": 30.0,
"min_edge": 0.015
},
"HTFT": {
"calibration": 0.45,
"min_conf": 10.0,
"min_play_score": 18.0,
"min_edge": 0.02
}
},
"defaults": {
"calibration": 0.55,
"min_conf": 55.0,
"min_play_score": 60.0,
"min_edge": 0.008,
"odds_band_min_sample": 0.0,
"odds_band_min_edge": 0.0
}
}
@@ -40,7 +40,7 @@ class CalculationContext:
is_surprise: bool = False is_surprise: bool = False
# XGBoost Predictions (New) # XGBoost Predictions (New)
xgboost_preds: dict[str, dict[str, Any]] = field(default_factory=dict) xgboost_preds: dict[str, Any] = field(default_factory=dict)
class BaseCalculator: class BaseCalculator:
@@ -28,7 +28,7 @@ class RecommendationResult:
class BetRecommender(BaseCalculator): class BetRecommender(BaseCalculator):
def calculate(self, def calculate(self, # type: ignore[override]
ctx: CalculationContext, ctx: CalculationContext,
ms_res: MatchResultPrediction, ms_res: MatchResultPrediction,
ou_res: OverUnderPrediction, ou_res: OverUnderPrediction,
@@ -36,7 +36,7 @@ class ExpertResult:
class ExpertRecommender(BaseCalculator): class ExpertRecommender(BaseCalculator):
def calculate(self, def calculate(self, # type: ignore[override]
ctx: CalculationContext, ctx: CalculationContext,
ms_res: MatchResultPrediction, ms_res: MatchResultPrediction,
ou_res: OverUnderPrediction, ou_res: OverUnderPrediction,
@@ -31,7 +31,7 @@ class HalfTimeCalculator(BaseCalculator):
return 1.0 if k == 0 else 0.0 return 1.0 if k == 0 else 0.0
return (lam ** k) * math.exp(-lam) / math.factorial(k) return (lam ** k) * math.exp(-lam) / math.factorial(k)
def calculate(self, ctx: CalculationContext) -> HalfTimePrediction: def calculate(self, ctx: CalculationContext) -> HalfTimePrediction: # type: ignore[override]
team_pred = ctx.team_pred team_pred = ctx.team_pred
odds_pred = ctx.odds_pred odds_pred = ctx.odds_pred
@@ -22,9 +22,9 @@ class MatchResultCalculator(BaseCalculator):
def _get_engine_winner(self, home_prob: float, draw_prob: float, away_prob: float) -> str: def _get_engine_winner(self, home_prob: float, draw_prob: float, away_prob: float) -> str:
"""Determine which outcome an engine favors.""" """Determine which outcome an engine favors."""
probs = {"1": home_prob, "X": draw_prob, "2": away_prob} probs = {"1": home_prob, "X": draw_prob, "2": away_prob}
return max(probs, key=probs.get) return max(probs, key=probs.__getitem__)
def calculate(self, ctx: CalculationContext) -> MatchResultPrediction: def calculate(self, ctx: CalculationContext) -> MatchResultPrediction: # type: ignore[override]
# Weights # Weights
w_team = ctx.weights["team"] w_team = ctx.weights["team"]
w_player = ctx.weights["player"] w_player = ctx.weights["player"]
@@ -28,7 +28,7 @@ class OtherMarketsPrediction:
class OtherMarketsCalculator(BaseCalculator): class OtherMarketsCalculator(BaseCalculator):
def calculate( def calculate( # type: ignore[override]
self, self,
ctx: CalculationContext, ctx: CalculationContext,
ms_result: MatchResultPrediction, ms_result: MatchResultPrediction,
@@ -55,7 +55,7 @@ class OverUnderCalculator(BaseCalculator):
return over_15, over_25, over_35, btts_yes return over_15, over_25, over_35, btts_yes
def calculate(self, ctx: CalculationContext) -> OverUnderPrediction: def calculate(self, ctx: CalculationContext) -> OverUnderPrediction: # type: ignore[override]
odds_pred = ctx.odds_pred odds_pred = ctx.odds_pred
referee_mods = ctx.referee_mods referee_mods = ctx.referee_mods
+40 -29
View File
@@ -67,12 +67,14 @@ class RiskAssessor(BaseCalculator):
if sport_key == "basketball": if sport_key == "basketball":
if is_top_league: if is_top_league:
return float( top_val = self.config.get("risk.surprise_threshold_basketball_top")
self.config.get("risk.surprise_threshold_basketball_top", self.config.get("risk.surprise_threshold_basketball", 0.30)), if top_val is not None:
) return float(top_val)
return float( base_val = self.config.get("risk.surprise_threshold_basketball")
self.config.get("risk.surprise_threshold_basketball_non_top", 0.34), return float(base_val) if base_val is not None else 0.30
)
non_top_val = self.config.get("risk.surprise_threshold_basketball_non_top")
return float(non_top_val) if non_top_val is not None else 0.34
if top_label not in ("1/2", "2/1"): if top_label not in ("1/2", "2/1"):
return base_threshold return base_threshold
@@ -81,27 +83,30 @@ class RiskAssessor(BaseCalculator):
favorite_side, gap = self._favorite_profile_from_odds(ctx.odds_data) favorite_side, gap = self._favorite_profile_from_odds(ctx.odds_data)
if is_top_league: if is_top_league:
favorite_winner_threshold = float( top_fav = self.config.get("risk.surprise_threshold_favorite_reversal_top")
self.config.get( if top_fav is not None:
"risk.surprise_threshold_favorite_reversal_top", favorite_winner_threshold = float(top_fav)
self.config.get("risk.surprise_threshold_favorite_reversal", 0.26), else:
), base_fav = self.config.get("risk.surprise_threshold_favorite_reversal")
) favorite_winner_threshold = float(base_fav) if base_fav is not None else 0.26
underdog_winner_threshold = float(
self.config.get( top_ud = self.config.get("risk.surprise_threshold_underdog_reversal_top")
"risk.surprise_threshold_underdog_reversal_top", if top_ud is not None:
self.config.get("risk.surprise_threshold_underdog_reversal", 0.20), underdog_winner_threshold = float(top_ud)
), else:
) base_ud = self.config.get("risk.surprise_threshold_underdog_reversal")
underdog_winner_threshold = float(base_ud) if base_ud is not None else 0.20
else: else:
favorite_winner_threshold = float( nt_fav = self.config.get("risk.surprise_threshold_favorite_reversal_non_top")
self.config.get("risk.surprise_threshold_favorite_reversal_non_top", 0.30), favorite_winner_threshold = float(nt_fav) if nt_fav is not None else 0.30
) nt_ud = self.config.get("risk.surprise_threshold_underdog_reversal_non_top")
underdog_winner_threshold = float( underdog_winner_threshold = float(nt_ud) if nt_ud is not None else 0.24
self.config.get("risk.surprise_threshold_underdog_reversal_non_top", 0.24),
) gm = self.config.get("risk.htft_reversal_gap_medium")
gap_medium = float(self.config.get("risk.htft_reversal_gap_medium", 0.50)) gap_medium = float(gm) if gm is not None else 0.50
gap_strong = float(self.config.get("risk.htft_reversal_gap_strong", 1.00))
gs = self.config.get("risk.htft_reversal_gap_strong")
gap_strong = float(gs) if gs is not None else 1.00
if favorite_side in ("H", "A"): if favorite_side in ("H", "A"):
threshold = ( threshold = (
@@ -117,7 +122,7 @@ class RiskAssessor(BaseCalculator):
return base_threshold return base_threshold
def calculate(self, ctx: CalculationContext, ms_result=None) -> RiskAnalysis: def calculate(self, ctx: CalculationContext, ms_result: Any = None) -> RiskAnalysis: # type: ignore[override]
""" """
Wrapper for assess_risk to match BaseCalculator interface but with extra arg. Wrapper for assess_risk to match BaseCalculator interface but with extra arg.
""" """
@@ -173,9 +178,15 @@ class RiskAssessor(BaseCalculator):
threshold = self._dynamic_reversal_threshold(ctx, top_label) threshold = self._dynamic_reversal_threshold(ctx, top_label)
if getattr(ctx, "is_top_league", False): if getattr(ctx, "is_top_league", False):
min_gap = float(self.config.get("risk.surprise_min_top_gap_top", self.config.get("risk.surprise_min_top_gap", 0.02))) top_gap_val = self.config.get("risk.surprise_min_top_gap_top")
if top_gap_val is not None:
min_gap = float(top_gap_val)
else:
base_gap_val = self.config.get("risk.surprise_min_top_gap")
min_gap = float(base_gap_val) if base_gap_val is not None else 0.02
else: else:
min_gap = float(self.config.get("risk.surprise_min_top_gap_non_top", 0.03)) non_top_gap_val = self.config.get("risk.surprise_min_top_gap_non_top")
min_gap = float(non_top_gap_val) if non_top_gap_val is not None else 0.03
# Trigger surprise only when reversal class is: # Trigger surprise only when reversal class is:
# - top HT/FT outcome # - top HT/FT outcome
@@ -3,7 +3,7 @@ import pickle
import pandas as pd import pandas as pd
import xgboost as xgb import xgboost as xgb
from dataclasses import dataclass from dataclasses import dataclass
from typing import List, Dict, Tuple from typing import List, Dict, Tuple, Optional
import math import math
from .base_calculator import BaseCalculator, CalculationContext from .base_calculator import BaseCalculator, CalculationContext
from .confidence import calc_confidence_3way, calc_confidence_dc from .confidence import calc_confidence_3way, calc_confidence_dc
@@ -16,7 +16,7 @@ class ScorePrediction:
ft_scores_top5: List[Dict] ft_scores_top5: List[Dict]
# Reconciled MS/DC predictions (can be updated here) # Reconciled MS/DC predictions (can be updated here)
reconciled_ms: MatchResultPrediction = None reconciled_ms: Optional[MatchResultPrediction] = None
class ScoreCalculator(BaseCalculator): class ScoreCalculator(BaseCalculator):
@@ -57,7 +57,8 @@ class ScoreCalculator(BaseCalculator):
return 1.0 if k == 0 else 0.0 return 1.0 if k == 0 else 0.0
return (lam ** k) * math.exp(-lam) / math.factorial(k) return (lam ** k) * math.exp(-lam) / math.factorial(k)
def calculate(self, ctx: CalculationContext, ms_result: MatchResultPrediction) -> ScorePrediction: def calculate(self, ctx: CalculationContext, ms_result: MatchResultPrediction) -> ScorePrediction: # type: ignore[override]
predicted_ht = None
# Default Lambdas (fallback) # Default Lambdas (fallback)
lambda_home = max(0.5, ctx.home_xg) lambda_home = max(0.5, ctx.home_xg)
lambda_away = max(0.5, ctx.away_xg) lambda_away = max(0.5, ctx.away_xg)
@@ -199,7 +200,7 @@ class ScoreCalculator(BaseCalculator):
predicted_ft = top_overall_score predicted_ft = top_overall_score
# If we didn't calculate HT via ML (exception case), do it now # If we didn't calculate HT via ML (exception case), do it now
if 'predicted_ht' not in locals(): if predicted_ht is None:
ft_to_ht = self.config.get("half_time.ft_to_ht_ratio", 0.42) ft_to_ht = self.config.get("half_time.ft_to_ht_ratio", 0.42)
ht_h = round(lambda_home * ft_to_ht) ht_h = round(lambda_home * ft_to_ht)
ht_a = round(lambda_away * ft_to_ht) ht_a = round(lambda_away * ft_to_ht)
+1 -7
View File
@@ -1,16 +1,10 @@
# ai-engine/core/engines/__init__.py # ai-engine/core/engines/__init__.py
""" """
V20 Ensemble Prediction Engines Prediction Engines
""" """
from .team_predictor import TeamPredictorEngine, get_team_predictor
from .player_predictor import PlayerPredictorEngine, get_player_predictor from .player_predictor import PlayerPredictorEngine, get_player_predictor
from .odds_predictor import OddsPredictorEngine, get_odds_predictor
from .referee_predictor import RefereePredictorEngine, get_referee_predictor
__all__ = [ __all__ = [
"TeamPredictorEngine", "get_team_predictor",
"PlayerPredictorEngine", "get_player_predictor", "PlayerPredictorEngine", "get_player_predictor",
"OddsPredictorEngine", "get_odds_predictor",
"RefereePredictorEngine", "get_referee_predictor"
] ]
-237
View File
@@ -1,237 +0,0 @@
"""
Odds Predictor Engine - V20 Ensemble Component
Uses market odds and Poisson mathematics for predictions.
Weight: 30% in ensemble
"""
import os
import sys
from typing import Dict, Optional
from dataclasses import dataclass
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from features.poisson_engine import get_poisson_engine
from features.value_calculator import get_value_calculator
@dataclass
class OddsPrediction:
"""Odds engine prediction output."""
# Market-implied probabilities
market_home_prob: float = 0.33
market_draw_prob: float = 0.33
market_away_prob: float = 0.33
# Poisson xG
poisson_home_xg: float = 1.3
poisson_away_xg: float = 1.1
# Over/Under probabilities
over_15_prob: float = 0.75
over_25_prob: float = 0.55
over_35_prob: float = 0.30
# BTTS
btts_yes_prob: float = 0.50
# Most likely scores
most_likely_score: str = "1-1"
second_likely_score: str = "1-0"
third_likely_score: str = "2-1"
# Value bet opportunities
value_bets: list = None
confidence: float = 0.0
def __post_init__(self):
if self.value_bets is None:
self.value_bets = []
def to_dict(self) -> dict:
return {
"market_home_prob": round(self.market_home_prob * 100, 1),
"market_draw_prob": round(self.market_draw_prob * 100, 1),
"market_away_prob": round(self.market_away_prob * 100, 1),
"poisson_home_xg": round(self.poisson_home_xg, 2),
"poisson_away_xg": round(self.poisson_away_xg, 2),
"over_15_prob": round(self.over_15_prob * 100, 1),
"over_25_prob": round(self.over_25_prob * 100, 1),
"over_35_prob": round(self.over_35_prob * 100, 1),
"btts_yes_prob": round(self.btts_yes_prob * 100, 1),
"most_likely_score": self.most_likely_score,
"second_likely_score": self.second_likely_score,
"third_likely_score": self.third_likely_score,
"value_bets": self.value_bets,
"confidence": round(self.confidence, 1)
}
class OddsPredictorEngine:
"""
Odds-based prediction engine.
Uses:
- Market odds to extract implied probabilities
- Poisson distribution for mathematical xG
- Value calculator for EV+ opportunities
"""
def __init__(self):
self.poisson_engine = get_poisson_engine()
try:
self.value_calc = get_value_calculator()
except Exception:
self.value_calc = None
self.default_ms_h = 2.65
self.default_ms_d = 3.20
self.default_ms_a = 2.65
print("✅ OddsPredictorEngine initialized")
def _odds_to_prob(self, odds: float) -> float:
"""Convert decimal odds to probability."""
try:
odds = float(odds)
except (TypeError, ValueError):
return 0.0
if odds <= 1.0:
return 0.0
return 1.0 / odds
def predict(self,
odds_data: Dict[str, float],
home_goals_avg: float = 1.5,
home_conceded_avg: float = 1.2,
away_goals_avg: float = 1.2,
away_conceded_avg: float = 1.4) -> OddsPrediction:
"""
Generate odds-based prediction.
Args:
odds_data: Dict with keys like 'ms_h', 'ms_d', 'ms_a', 'ou25_o', 'btts_y'
home_goals_avg: Home team's average goals scored
home_conceded_avg: Home team's average goals conceded
away_goals_avg: Away team's average goals scored
away_conceded_avg: Away team's average goals conceded
Returns:
OddsPrediction with market and Poisson analysis
"""
# 1. Extract market probabilities from odds
ms_h = odds_data.get("ms_h", self.default_ms_h)
ms_d = odds_data.get("ms_d", self.default_ms_d)
ms_a = odds_data.get("ms_a", self.default_ms_a)
# Remove vig to get fair probabilities
raw_probs = [
self._odds_to_prob(ms_h),
self._odds_to_prob(ms_d),
self._odds_to_prob(ms_a)
]
total = sum(raw_probs) or 1
market_home = raw_probs[0] / total
market_draw = raw_probs[1] / total
market_away = raw_probs[2] / total
# 2. Poisson prediction
poisson_pred = self.poisson_engine.predict(
home_goals_avg, home_conceded_avg,
away_goals_avg, away_conceded_avg
)
# 3. Get most likely scores
likely_scores = poisson_pred.most_likely_scores[:3] if poisson_pred.most_likely_scores else []
score_1 = likely_scores[0]["score"] if len(likely_scores) > 0 else "1-1"
score_2 = likely_scores[1]["score"] if len(likely_scores) > 1 else "1-0"
score_3 = likely_scores[2]["score"] if len(likely_scores) > 2 else "2-1"
# 4. Value bet detection
value_bets = []
# Check if our Poisson model disagrees with market significantly
if abs(poisson_pred.home_win_prob - market_home) > 0.10:
if poisson_pred.home_win_prob > market_home:
value_bets.append({
"market": "MS 1",
"edge": round((poisson_pred.home_win_prob - market_home) * 100, 1),
"confidence": "medium"
})
else:
value_bets.append({
"market": "MS 2",
"edge": round((poisson_pred.away_win_prob - market_away) * 100, 1),
"confidence": "medium"
})
# O/U value check
ou25_o = odds_data.get("ou25_o", 1.9)
market_over25 = self._odds_to_prob(ou25_o)
if abs(poisson_pred.over_25_prob - market_over25) > 0.08:
pick = "2.5 Üst" if poisson_pred.over_25_prob > market_over25 else "2.5 Alt"
edge = abs(poisson_pred.over_25_prob - market_over25) * 100
value_bets.append({
"market": pick,
"edge": round(edge, 1),
"confidence": "high" if edge > 10 else "medium"
})
# Calculate confidence
# Higher when market and Poisson agree
agreement = 1.0 - abs(poisson_pred.home_win_prob - market_home)
confidence = 50.0 + (agreement * 40) + (len(value_bets) * 5)
return OddsPrediction(
market_home_prob=market_home,
market_draw_prob=market_draw,
market_away_prob=market_away,
poisson_home_xg=poisson_pred.home_xg,
poisson_away_xg=poisson_pred.away_xg,
over_15_prob=poisson_pred.over_15_prob,
over_25_prob=poisson_pred.over_25_prob,
over_35_prob=poisson_pred.over_35_prob,
btts_yes_prob=poisson_pred.btts_yes_prob,
most_likely_score=score_1,
second_likely_score=score_2,
third_likely_score=score_3,
value_bets=value_bets,
confidence=min(99.9, confidence)
)
# Singleton
_engine: Optional[OddsPredictorEngine] = None
def get_odds_predictor() -> OddsPredictorEngine:
global _engine
if _engine is None:
_engine = OddsPredictorEngine()
return _engine
if __name__ == "__main__":
engine = get_odds_predictor()
print("\n🧪 Odds Predictor Engine Test")
print("=" * 50)
pred = engine.predict(
odds_data={
"ms_h": 1.85,
"ms_d": 3.40,
"ms_a": 4.20,
"ou25_o": 1.90
},
home_goals_avg=1.8,
home_conceded_avg=1.0,
away_goals_avg=1.2,
away_conceded_avg=1.5
)
print(f"\n📊 Prediction:")
for k, v in pred.to_dict().items():
print(f" {k}: {v}")
+187 -52
View File
@@ -18,33 +18,35 @@ from features.sidelined_analyzer import get_sidelined_analyzer
@dataclass @dataclass
class PlayerPrediction: class PlayerPrediction:
"""Player engine prediction output.""" """Player engine prediction output.
home_squad_quality: float = 50.0 # 0-100
away_squad_quality: float = 50.0 IMPORTANT: squad_quality uses the SAME composite formula as
squad_diff: float = 0.0 # -100 to +100 extract_training_data.py so that inference values match the
distribution the model was trained on (~3-36 range).
"""
home_squad_quality: float = 12.0
away_squad_quality: float = 12.0
squad_diff: float = 0.0
home_key_players: int = 0 home_key_players: int = 0
away_key_players: int = 0 away_key_players: int = 0
home_missing_impact: float = 0.0 # 0-1, how much weaker due to missing players home_missing_impact: float = 0.0
away_missing_impact: float = 0.0 away_missing_impact: float = 0.0
home_goals_form: int = 0 # Goals in last 5 matches home_goals_form: int = 0
away_goals_form: int = 0 away_goals_form: int = 0
home_lineup_goals_per90: float = 0.0
away_lineup_goals_per90: float = 0.0
home_lineup_assists_per90: float = 0.0
away_lineup_assists_per90: float = 0.0
home_squad_continuity: float = 0.5
away_squad_continuity: float = 0.5
home_top_scorer_form: int = 0
away_top_scorer_form: int = 0
home_avg_player_exp: float = 0.0
away_avg_player_exp: float = 0.0
home_goals_diversity: float = 0.0
away_goals_diversity: float = 0.0
lineup_available: bool = False lineup_available: bool = False
confidence: float = 0.0 confidence: float = 0.0
def to_dict(self) -> dict:
return {
"home_squad_quality": round(self.home_squad_quality, 1),
"away_squad_quality": round(self.away_squad_quality, 1),
"squad_diff": round(self.squad_diff, 1),
"home_key_players": self.home_key_players,
"away_key_players": self.away_key_players,
"home_missing_impact": round(self.home_missing_impact, 2),
"away_missing_impact": round(self.away_missing_impact, 2),
"home_goals_form": self.home_goals_form,
"away_goals_form": self.away_goals_form,
"lineup_available": self.lineup_available,
"confidence": round(self.confidence, 1)
}
class PlayerPredictorEngine: class PlayerPredictorEngine:
@@ -67,9 +69,9 @@ class PlayerPredictorEngine:
match_id: str, match_id: str,
home_team_id: str, home_team_id: str,
away_team_id: str, away_team_id: str,
home_lineup: List[str] = None, home_lineup: Optional[List[str]] = None,
away_lineup: List[str] = None, away_lineup: Optional[List[str]] = None,
sidelined_data: Dict = None) -> PlayerPrediction: sidelined_data: Optional[Dict] = None) -> PlayerPrediction:
""" """
Generate player-based prediction. Generate player-based prediction.
@@ -85,8 +87,9 @@ class PlayerPredictorEngine:
""" """
# Get squad features # Get squad features
home_analysis = None
away_analysis = None
if home_lineup and away_lineup: if home_lineup and away_lineup:
# Use provided lineups (for live matches)
home_analysis = self.squad_engine.analyze_squad_from_list( home_analysis = self.squad_engine.analyze_squad_from_list(
home_lineup, home_team_id home_lineup, home_team_id
) )
@@ -94,19 +97,19 @@ class PlayerPredictorEngine:
away_lineup, away_team_id away_lineup, away_team_id
) )
lineup_available = True lineup_available = True
# Build features dict from analysis objects
features = { features = {
"home_starting_11": home_analysis.starting_count or 11, "home_starting_11": home_analysis.starting_count or 11,
"home_goals_last_5": home_analysis.total_goals_last_5, "home_goals_last_5": home_analysis.total_goals_last_5,
"home_assists_last_5": home_analysis.total_assists_last_5, "home_assists_last_5": home_analysis.total_assists_last_5,
"home_key_players": home_analysis.key_players_count, "home_key_players": home_analysis.key_players_count,
"home_forwards": home_analysis.forward_count or 2,
"away_starting_11": away_analysis.starting_count or 11, "away_starting_11": away_analysis.starting_count or 11,
"away_goals_last_5": away_analysis.total_goals_last_5, "away_goals_last_5": away_analysis.total_goals_last_5,
"away_assists_last_5": away_analysis.total_assists_last_5, "away_assists_last_5": away_analysis.total_assists_last_5,
"away_key_players": away_analysis.key_players_count, "away_key_players": away_analysis.key_players_count,
"away_forwards": away_analysis.forward_count or 2,
} }
elif match_id: elif match_id:
# Try to get from database
try: try:
features = self.squad_engine.get_features( features = self.squad_engine.get_features(
match_id, home_team_id, away_team_id match_id, home_team_id, away_team_id
@@ -125,40 +128,42 @@ class PlayerPredictorEngine:
home_team_id, away_team_id home_team_id, away_team_id
) )
lineup_available = False lineup_available = False
# Extract features home_goals = int(features.get("home_goals_last_5", 0))
home_goals = features.get("home_goals_last_5", 0) away_goals = int(features.get("away_goals_last_5", 0))
away_goals = features.get("away_goals_last_5", 0) home_key = int(features.get("home_key_players", 0))
home_key = features.get("home_key_players", 0) away_key = int(features.get("away_key_players", 0))
away_key = features.get("away_key_players", 0) home_starting = features.get("home_starting_11", 11)
away_starting = features.get("away_starting_11", 11)
# Calculate squad quality (0-100) home_fwd = features.get("home_forwards", 2)
# Based on: goals scored, key players, assists away_fwd = features.get("away_forwards", 2)
home_quality = min(100, 50 + (home_goals * 3) + (home_key * 5) +
features.get("home_assists_last_5", 0) * 2) # Squad quality — matches V25 extract_training_data.py:579
away_quality = min(100, 50 + (away_goals * 3) + (away_key * 5) + home_quality = home_starting * 0.3 + home_key * 3.0 + home_fwd * 1.5
features.get("away_assists_last_5", 0) * 2) away_quality = away_starting * 0.3 + away_key * 3.0 + away_fwd * 1.5
# Squad difference
squad_diff = home_quality - away_quality squad_diff = home_quality - away_quality
# Missing player impact # Missing player impact
# Priority: sidelined data (position-weighted) > lineup count (basic)
if sidelined_data: if sidelined_data:
home_impact, away_impact = self.sidelined_analyzer.analyze_match(sidelined_data) home_impact, away_impact = self.sidelined_analyzer.analyze_match(sidelined_data)
home_missing = home_impact.impact_score home_missing = min(1.0, max(0.0, home_impact.impact_score))
away_missing = away_impact.impact_score away_missing = min(1.0, max(0.0, away_impact.impact_score))
sidelined_available = True sidelined_available = True
else: else:
# Fallback: basic lineup count method
expected_xi = 11 expected_xi = 11
actual_home_xi = features.get("home_starting_11", 11) actual_home_xi = features.get("home_starting_11", 11)
actual_away_xi = features.get("away_starting_11", 11) actual_away_xi = features.get("away_starting_11", 11)
home_missing = (expected_xi - actual_home_xi) / expected_xi if actual_home_xi < expected_xi else 0 home_missing = (expected_xi - actual_home_xi) / expected_xi if actual_home_xi < expected_xi else 0
away_missing = (expected_xi - actual_away_xi) / expected_xi if actual_away_xi < expected_xi else 0 away_missing = (expected_xi - actual_away_xi) / expected_xi if actual_away_xi < expected_xi else 0
sidelined_available = False sidelined_available = False
# Confidence: more data sources = higher confidence # Player-level features (matches extract_training_data.py:594-650)
player_feats = self._compute_player_level_features(
home_lineup or [], away_lineup or [],
home_team_id, away_team_id,
home_analysis, away_analysis,
)
confidence = 70.0 if lineup_available else 35.0 confidence = 70.0 if lineup_available else 35.0
if home_goals + away_goals > 10: if home_goals + away_goals > 10:
confidence += 15 confidence += 15
@@ -166,7 +171,7 @@ class PlayerPredictorEngine:
confidence += self.sidelined_analyzer.config.get("sidelined.confidence_boost", 10) confidence += self.sidelined_analyzer.config.get("sidelined.confidence_boost", 10)
if not lineup_available: if not lineup_available:
confidence -= 5.0 confidence -= 5.0
return PlayerPrediction( return PlayerPrediction(
home_squad_quality=home_quality, home_squad_quality=home_quality,
away_squad_quality=away_quality, away_squad_quality=away_quality,
@@ -177,17 +182,147 @@ class PlayerPredictorEngine:
away_missing_impact=away_missing, away_missing_impact=away_missing,
home_goals_form=home_goals, home_goals_form=home_goals,
away_goals_form=away_goals, away_goals_form=away_goals,
home_lineup_goals_per90=player_feats['home_lineup_goals_per90'],
away_lineup_goals_per90=player_feats['away_lineup_goals_per90'],
home_lineup_assists_per90=player_feats['home_lineup_assists_per90'],
away_lineup_assists_per90=player_feats['away_lineup_assists_per90'],
home_squad_continuity=player_feats['home_squad_continuity'],
away_squad_continuity=player_feats['away_squad_continuity'],
home_top_scorer_form=player_feats['home_top_scorer_form'],
away_top_scorer_form=player_feats['away_top_scorer_form'],
home_avg_player_exp=player_feats['home_avg_player_exp'],
away_avg_player_exp=player_feats['away_avg_player_exp'],
home_goals_diversity=player_feats['home_goals_diversity'],
away_goals_diversity=player_feats['away_goals_diversity'],
lineup_available=lineup_available, lineup_available=lineup_available,
confidence=max(5.0, confidence) confidence=max(5.0, confidence)
) )
def _compute_player_level_features(
self,
home_lineup: List[str],
away_lineup: List[str],
home_team_id: str,
away_team_id: str,
home_analysis,
away_analysis,
) -> Dict[str, float]:
defaults = {
'home_lineup_goals_per90': 0.0, 'away_lineup_goals_per90': 0.0,
'home_lineup_assists_per90': 0.0, 'away_lineup_assists_per90': 0.0,
'home_squad_continuity': 0.5, 'away_squad_continuity': 0.5,
'home_top_scorer_form': 0, 'away_top_scorer_form': 0,
'home_avg_player_exp': 0.0, 'away_avg_player_exp': 0.0,
'home_goals_diversity': 0.0, 'away_goals_diversity': 0.0,
}
conn = self.squad_engine.get_conn()
if conn is None:
return defaults
try:
from psycopg2.extras import RealDictCursor
result = {}
for prefix, lineup, team_id in [
('home', home_lineup, home_team_id),
('away', away_lineup, away_team_id),
]:
if not lineup:
for k in ('lineup_goals_per90', 'lineup_assists_per90',
'squad_continuity', 'top_scorer_form',
'avg_player_exp', 'goals_diversity'):
result[f'{prefix}_{k}'] = defaults[f'{prefix}_{k}']
continue
g90, a90, total_exp = 0.0, 0.0, 0
best_scorer_total, best_scorer_id = 0, None
scorers_in_lineup = 0
with conn.cursor(cursor_factory=RealDictCursor) as cur:
for pid in lineup:
cur.execute("""
SELECT
COUNT(*) as starts,
COALESCE(SUM(CASE WHEN e.event_type = 'goal'
AND (e.event_subtype IS NULL OR e.event_subtype NOT ILIKE '%%penaltı kaçırma%%')
THEN 1 ELSE 0 END), 0) as goals,
COALESCE((SELECT COUNT(*) FROM match_player_events
WHERE assist_player_id = %s), 0) as assists
FROM match_player_participation mpp
LEFT JOIN match_player_events e
ON e.match_id = mpp.match_id AND e.player_id = mpp.player_id
WHERE mpp.player_id = %s AND mpp.is_starting = true
""", (pid, pid))
row = cur.fetchone()
if not row or not row['starts']:
continue
starts = row['starts']
goals = row['goals'] or 0
assists = row['assists'] or 0
g90 += goals / starts
a90 += assists / starts
total_exp += starts
if goals > 0:
scorers_in_lineup += 1
if goals > best_scorer_total:
best_scorer_total = goals
best_scorer_id = pid
n_st = len(lineup) or 1
# Top scorer recent form (goals in last 5 starts)
top_scorer_form = 0
if best_scorer_id:
cur.execute("""
SELECT COUNT(*) as goals
FROM match_player_events mpe
WHERE mpe.player_id = %s AND mpe.event_type = 'goal'
AND mpe.match_id IN (
SELECT match_id FROM match_player_participation
WHERE player_id = %s AND is_starting = true
ORDER BY match_id DESC LIMIT 5
)
""", (best_scorer_id, best_scorer_id))
tsf_row = cur.fetchone()
if tsf_row:
top_scorer_form = tsf_row['goals'] or 0
# Squad continuity (overlap with previous match lineup)
squad_continuity = 0.5
cur.execute("""
SELECT mpp.player_id
FROM match_player_participation mpp
JOIN matches m ON mpp.match_id = m.id
WHERE mpp.team_id = %s AND mpp.is_starting = true
AND m.status = 'FT'
ORDER BY m.mst_utc DESC
LIMIT 11
""", (team_id,))
prev_starters = {r['player_id'] for r in cur.fetchall()}
if prev_starters:
overlap = len(set(lineup) & prev_starters)
squad_continuity = overlap / n_st
result[f'{prefix}_lineup_goals_per90'] = round(g90, 3)
result[f'{prefix}_lineup_assists_per90'] = round(a90, 3)
result[f'{prefix}_squad_continuity'] = round(squad_continuity, 3)
result[f'{prefix}_top_scorer_form'] = top_scorer_form
result[f'{prefix}_avg_player_exp'] = round(total_exp / n_st, 1)
result[f'{prefix}_goals_diversity'] = round(scorers_in_lineup / n_st, 3)
return result
except Exception as e:
print(f"[PlayerPredictor] Player-level features failed: {e}")
return defaults
def get_1x2_modifier(self, prediction: PlayerPrediction) -> Dict[str, float]: def get_1x2_modifier(self, prediction: PlayerPrediction) -> Dict[str, float]:
""" """
Calculate 1X2 probability modifiers based on squad analysis. Calculate 1X2 probability modifiers based on squad analysis.
Returns modifiers to apply to base probabilities. Returns modifiers to apply to base probabilities.
squad_diff is in training scale (~-33 to +33), normalize to -1..+1.
""" """
diff = prediction.squad_diff / 100 # -1 to +1 diff = prediction.squad_diff / 33.0 # training-scale normalisation
diff = max(-1.0, min(1.0, diff)) # clamp
return { return {
"home_modifier": 1.0 + (diff * 0.3), # Up to +/-30% "home_modifier": 1.0 + (diff * 0.3), # Up to +/-30%
@@ -214,7 +349,7 @@ if __name__ == "__main__":
print("=" * 50) print("=" * 50)
pred = engine.predict( pred = engine.predict(
match_id=None, match_id="test_match",
home_team_id="test_home", home_team_id="test_home",
away_team_id="test_away" away_team_id="test_away"
) )
-188
View File
@@ -1,188 +0,0 @@
"""
Referee Predictor Engine - V20 Ensemble Component
Analyzes referee patterns for cards, goals, and home bias.
Weight: 15% in ensemble
"""
import os
import sys
from typing import Dict, Optional
from dataclasses import dataclass
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from features.referee_engine import get_referee_engine
@dataclass
class RefereePrediction:
"""Referee engine prediction output."""
referee_name: str = ""
matches_officiated: int = 0
# Card tendencies
avg_yellow_cards: float = 4.0
avg_red_cards: float = 0.2
is_card_heavy: bool = False # Above average cards
# Goal tendencies
avg_goals_per_match: float = 2.5
over_25_rate: float = 0.50
is_high_scoring: bool = False # Above average goals
# Home bias
home_win_rate: float = 0.45
home_bias: float = 0.0 # -1 to +1, positive = favors home
# Penalty tendency
penalty_rate: float = 0.15
confidence: float = 0.0
def to_dict(self) -> dict:
return {
"referee_name": self.referee_name,
"matches_officiated": self.matches_officiated,
"avg_yellow_cards": round(self.avg_yellow_cards, 1),
"avg_red_cards": round(self.avg_red_cards, 2),
"is_card_heavy": self.is_card_heavy,
"avg_goals_per_match": round(self.avg_goals_per_match, 2),
"over_25_rate": round(self.over_25_rate * 100, 1),
"is_high_scoring": self.is_high_scoring,
"home_win_rate": round(self.home_win_rate * 100, 1),
"home_bias": round(self.home_bias, 2),
"penalty_rate": round(self.penalty_rate * 100, 1),
"confidence": round(self.confidence, 1)
}
class RefereePredictorEngine:
"""
Referee-based prediction engine.
Analyzes:
- Card tendency (sarı/kırmızı kart ortalaması)
- Goal tendency (maç başına gol, 2.5 üst oranı)
- Home bias (ev sahibi lehine karar oranı)
- Penalty tendency (penaltı verme oranı)
"""
# League average benchmarks
LEAGUE_AVG_GOALS = 2.65
LEAGUE_AVG_YELLOW = 4.0
LEAGUE_HOME_WIN_RATE = 0.45
def __init__(self):
self.referee_engine = get_referee_engine()
print("✅ RefereePredictorEngine initialized")
def predict(self,
match_id: str = None,
referee_name: str = None,
league_id: str = None) -> RefereePrediction:
"""
Generate referee-based prediction.
Args:
match_id: Match ID to find referee
referee_name: Or provide referee name directly
league_id: League ID to scope stats (prevents name collisions)
Returns:
RefereePrediction with referee analysis
"""
# Get referee features
if match_id:
features = self.referee_engine.get_features(match_id, league_id=league_id)
# Live flows may already have referee_name while match_officials table is sparse.
# Prefer the richer profile if direct-name lookup has more history.
if referee_name:
name_features = self.referee_engine.get_features_by_name(referee_name, league_id=league_id)
if (name_features.get("referee_matches", 0) or 0) > (features.get("referee_matches", 0) or 0):
features = name_features
elif referee_name:
features = self.referee_engine.get_features_by_name(referee_name, league_id=league_id)
else:
# Return default
return RefereePrediction(confidence=10.0)
ref_name = features.get("referee_name", "Unknown")
matches = features.get("referee_matches", 0)
if matches < 5:
# Not enough data
return RefereePrediction(
referee_name=ref_name,
matches_officiated=matches,
confidence=20.0
)
# Extract features
avg_yellow = features.get("referee_avg_yellow", 4.0)
avg_red = features.get("referee_avg_red", 0.2)
avg_goals = features.get("referee_avg_goals", 2.5)
over25_rate = features.get("referee_over25_rate", 0.5)
home_win_rate = features.get("referee_home_win_rate", 0.45) if "referee_home_win_rate" in features else 0.45
home_bias = features.get("referee_home_bias", 0.0)
penalty_rate = features.get("referee_penalty_rate", 0.15)
# Determine tendencies
is_card_heavy = (avg_yellow + avg_red * 4) > (self.LEAGUE_AVG_YELLOW + 1)
is_high_scoring = avg_goals > self.LEAGUE_AVG_GOALS
# Confidence based on matches officiated
confidence = min(90.0, 30.0 + matches * 2)
return RefereePrediction(
referee_name=ref_name,
matches_officiated=matches,
avg_yellow_cards=avg_yellow,
avg_red_cards=avg_red,
is_card_heavy=is_card_heavy,
avg_goals_per_match=avg_goals,
over_25_rate=over25_rate,
is_high_scoring=is_high_scoring,
home_win_rate=home_win_rate,
home_bias=home_bias,
penalty_rate=penalty_rate,
confidence=confidence
)
def get_modifiers(self, prediction: RefereePrediction) -> Dict[str, float]:
"""
Get modifiers to apply to other predictions based on referee profile.
"""
return {
# Home team gets slight boost if referee has home bias
"home_modifier": 1.0 + (prediction.home_bias * 0.05),
# O/U modifier
"over_25_modifier": 1.0 + (prediction.avg_goals_per_match - self.LEAGUE_AVG_GOALS) * 0.1,
# Card modifier for card markets
"cards_modifier": 1.0 + (prediction.avg_yellow_cards - self.LEAGUE_AVG_YELLOW) * 0.05
}
# Singleton
_engine: Optional[RefereePredictorEngine] = None
def get_referee_predictor() -> RefereePredictorEngine:
global _engine
if _engine is None:
_engine = RefereePredictorEngine()
return _engine
if __name__ == "__main__":
engine = get_referee_predictor()
print("\n🧪 Referee Predictor Engine Test")
print("=" * 50)
pred = engine.predict(referee_name="Cüneyt Çakır")
print(f"\n📊 Prediction:")
for k, v in pred.to_dict().items():
print(f" {k}: {v}")
-286
View File
@@ -1,286 +0,0 @@
"""
Team Predictor Engine - V20 Ensemble Component
Combines ELO ratings, form stats, H2H records and team statistics.
Weight: 30% in ensemble
"""
import os
import sys
from typing import Dict, Optional, Tuple, Any
from dataclasses import dataclass, field
# Add parent to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from features.elo_system import get_elo_system
from features.h2h_engine import get_h2h_engine
from features.momentum_engine import get_momentum_engine, MomentumData
from features.team_stats_engine import get_team_stats_engine
@dataclass
class TeamPrediction:
"""Team engine prediction output."""
home_win_prob: float = 0.33
draw_prob: float = 0.33
away_win_prob: float = 0.33
home_xg: float = 1.3
away_xg: float = 1.1
form_advantage: float = 0.0 # -1 to +1, positive = home advantage
h2h_advantage: float = 0.0 # -1 to +1
elo_diff: float = 0.0
confidence: float = 0.0
def to_dict(self) -> dict:
return {
"home_win_prob": round(self.home_win_prob * 100, 1),
"draw_prob": round(self.draw_prob * 100, 1),
"away_win_prob": round(self.away_win_prob * 100, 1),
"home_xg": round(self.home_xg, 2),
"away_xg": round(self.away_xg, 2),
"form_advantage": round(self.form_advantage, 2),
"h2h_advantage": round(self.h2h_advantage, 2),
"elo_diff": round(self.elo_diff, 0),
"confidence": round(self.confidence, 1)
}
raw_features: Dict[str, Any] = field(default_factory=dict)
class TeamPredictorEngine:
"""
Team-based prediction engine.
Uses:
- ELO Rating System (venue-adjusted, league-weighted)
- H2H Engine (head-to-head history)
- Momentum Engine (recent form)
- Team Stats Engine (possession, shots, corners)
"""
def __init__(self):
self.elo_system = get_elo_system()
self.h2h_engine = get_h2h_engine()
self.momentum_engine = get_momentum_engine()
self.team_stats_engine = get_team_stats_engine()
print("✅ TeamPredictorEngine initialized")
def predict(self,
home_team_id: str,
away_team_id: str,
match_date_ms: int,
home_team_name: str = "",
away_team_name: str = "") -> TeamPrediction:
"""
Generate team-based prediction.
Args:
home_team_id: Home team ID
away_team_id: Away team ID
match_date_ms: Match date in milliseconds
home_team_name: Home team name (for ELO)
away_team_name: Away team name (for ELO)
Returns:
TeamPrediction with 1X2 probabilities and xG
"""
# 1. Get ELO predictions
elo_pred = self.elo_system.predict_match(home_team_id, away_team_id)
elo_features = self.elo_system.get_match_features(home_team_id, away_team_id)
# 2. Get H2H features
try:
h2h_features = self.h2h_engine.get_features(
home_team_id, away_team_id, match_date_ms
)
except Exception:
h2h_features = {
"h2h_home_win_rate": 0.5,
"h2h_away_win_rate": 0.5,
"h2h_avg_goals": 2.5,
"h2h_btts_rate": 0.5
}
# 3. Get Momentum/Form features
try:
# key: form_score should be 0-1 derived from momentum_score (-1 to 1)
home_mom_data = self.momentum_engine.calculate_momentum(home_team_id, match_date_ms)
away_mom_data = self.momentum_engine.calculate_momentum(away_team_id, match_date_ms)
home_form_score = (home_mom_data.momentum_score + 1) / 2
away_form_score = (away_mom_data.momentum_score + 1) / 2
except Exception as e:
print(f"⚠️ MomentumEngine error: {e}")
home_mom_data = MomentumData()
away_mom_data = MomentumData()
home_form_score = 0.5
away_form_score = 0.5
# 4. Get Team Stats
home_stats = self.team_stats_engine.get_features(home_team_id, match_date_ms)
away_stats = self.team_stats_engine.get_features(away_team_id, match_date_ms)
# 5. Combine predictions
# ELO-based 1X2 (60% weight)
elo_home = elo_pred.get("home_win_prob", 0.33)
elo_draw = elo_pred.get("draw_prob", 0.33)
elo_away = elo_pred.get("away_win_prob", 0.33)
# Adjust based on H2H (20% weight)
h2h_home_rate = h2h_features.get("h2h_home_win_rate", 0.5)
h2h_away_rate = h2h_features.get("h2h_away_win_rate", 0.5)
# Adjust based on form (20% weight)
home_form = home_form_score
away_form = away_form_score
form_diff = (home_form - away_form) # -1 to +1
# Weighted combination
final_home = elo_home * 0.6 + h2h_home_rate * 0.2 + (0.5 + form_diff * 0.3) * 0.2
final_away = elo_away * 0.6 + h2h_away_rate * 0.2 + (0.5 - form_diff * 0.3) * 0.2
final_draw = 1.0 - final_home - final_away
# Normalize
total = final_home + final_draw + final_away
if total > 0:
final_home /= total
final_draw /= total
final_away /= total
# Calculate xG based on stats and form (conservative base)
home_conversion = home_stats.get("shot_conversion_rate", 0.1)
away_conversion = away_stats.get("shot_conversion_rate", 0.1)
base_home_xg = 1.35 + (home_conversion * 3.0)
base_away_xg = 1.10 + (away_conversion * 2.5)
# Defense weakness factor: opponent's defensive quality affects xG
# Higher shots on target against = weaker defense
away_def_weakness = away_stats.get("shot_accuracy", 0.35) # opponent's shot accuracy as proxy
home_def_weakness = home_stats.get("shot_accuracy", 0.35)
# Adjust xG: stronger opponent defense → lower xG
home_xg = base_home_xg * (1 + form_diff * 0.15) * (0.8 + away_def_weakness * 0.6)
away_xg = base_away_xg * (1 - form_diff * 0.15) * (0.8 + home_def_weakness * 0.6)
# Apply xG Underperformance Penalty directly to calculated xG
# If a team chronically underperforms its xG, we subtract that historical difference here
if hasattr(home_mom_data, 'xg_underperformance') and home_mom_data.xg_underperformance > 0.2:
home_xg -= min(0.5, home_mom_data.xg_underperformance * 0.5)
if hasattr(away_mom_data, 'xg_underperformance') and away_mom_data.xg_underperformance > 0.2:
away_xg -= min(0.5, away_mom_data.xg_underperformance * 0.5)
# H2H adjustment (more conservative)
h2h_avg_goals = h2h_features.get("h2h_avg_goals", 2.5)
if h2h_avg_goals > 3.0:
home_xg *= 1.05
away_xg *= 1.05
elif h2h_avg_goals < 2.0:
home_xg *= 0.95
away_xg *= 0.95
# Clamp xG to reasonable range
home_xg = max(0.5, min(3.5, home_xg))
away_xg = max(0.3, min(3.0, away_xg))
# Calculate confidence
# Higher when ELO, H2H, and Form all agree
elo_winner = "H" if elo_home > max(elo_draw, elo_away) else ("A" if elo_away > elo_draw else "D")
h2h_winner = "H" if h2h_home_rate > h2h_away_rate else "A"
form_winner = "H" if form_diff > 0.1 else ("A" if form_diff < -0.1 else "D")
agreement = sum([
elo_winner == h2h_winner,
elo_winner == form_winner,
h2h_winner == form_winner
])
max_prob = max(final_home, final_draw, final_away)
confidence = max_prob * 100 * (0.7 + agreement * 0.1)
# Collect Raw Features for XGBoost
# Note: home_mom_data is an object now
def get_rate(val): return val if val is not None else 0.5
raw_features = {
**elo_features, # 8 features
# Form Features (need key mapping to match extract_training_data.py)
"home_goals_avg": 1.5 + home_mom_data.goals_trend, # Proxy
"home_conceded_avg": 1.5 - home_mom_data.conceded_trend, # Proxy
"away_goals_avg": 1.5 + away_mom_data.goals_trend,
"away_conceded_avg": 1.5 - away_mom_data.conceded_trend,
"home_clean_sheet_rate": 0.2, # Not in new MomentumData
"away_clean_sheet_rate": 0.2,
"home_scoring_rate": 0.8,
"away_scoring_rate": 0.8,
"home_winning_streak": home_mom_data.winning_streak,
"away_winning_streak": away_mom_data.winning_streak,
"home_unbeaten_streak": home_mom_data.unbeaten_streak,
"away_unbeaten_streak": away_mom_data.unbeaten_streak,
# H2H Features
**h2h_features,
# Team Stats
"home_avg_possession": home_stats.get("avg_possession", 0.5),
"away_avg_possession": away_stats.get("avg_possession", 0.5),
"home_avg_shots_on_target": home_stats.get("avg_shots_on_target", 3.5),
"away_avg_shots_on_target": away_stats.get("avg_shots_on_target", 3.5),
"home_shot_conversion": home_stats.get("shot_conversion_rate", 0.1),
"away_shot_conversion": away_stats.get("shot_conversion_rate", 0.1),
"home_avg_corners": home_stats.get("avg_corners", 4.5),
"away_avg_corners": away_stats.get("avg_corners", 4.5),
# Derived
"home_xga": 1.5 - home_mom_data.conceded_trend, # reusing as proxy
"away_xga": 1.5 - away_mom_data.conceded_trend
}
return TeamPrediction(
home_win_prob=final_home,
draw_prob=final_draw,
away_win_prob=final_away,
home_xg=home_xg,
away_xg=away_xg,
form_advantage=form_diff,
h2h_advantage=h2h_home_rate - h2h_away_rate,
elo_diff=elo_features.get("elo_diff", 0),
confidence=confidence,
raw_features=raw_features
)
# Singleton
_engine: Optional[TeamPredictorEngine] = None
def get_team_predictor() -> TeamPredictorEngine:
global _engine
if _engine is None:
_engine = TeamPredictorEngine()
return _engine
if __name__ == "__main__":
engine = get_team_predictor()
print("\n🧪 Team Predictor Engine Test")
print("=" * 50)
# Test with sample IDs
pred = engine.predict(
home_team_id="test_home",
away_team_id="test_away",
match_date_ms=1707393600000
)
print(f"\n📊 Prediction:")
for k, v in pred.to_dict().items():
print(f" {k}: {v}")
+1
View File
@@ -0,0 +1 @@
# data package
+97
View File
@@ -0,0 +1,97 @@
"""
Async Database Module — V2 Betting Engine
==========================================
Provides async SQLAlchemy sessions via asyncpg for the V2 router.
Usage:
async with get_session() as session:
result = await session.execute(text("SELECT ..."))
"""
from __future__ import annotations
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from dotenv import load_dotenv
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
load_dotenv()
_engine: AsyncEngine | None = None
_session_maker: async_sessionmaker[AsyncSession] | None = None
def _get_async_dsn() -> str:
"""
Convert DATABASE_URL to asyncpg-compatible format.
Handles:
1. Prisma's ``?schema=public`` suffix → stripped
2. ``postgresql://`` driver prefix → ``postgresql+asyncpg://``
"""
dsn = os.getenv(
"DATABASE_URL",
"postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db",
)
# Strip Prisma's ?schema= parameter
if "?" in dsn:
base, query = dsn.split("?", 1)
kept_parts = [
part for part in query.split("&") if part and not part.startswith("schema=")
]
dsn = base if not kept_parts else f"{base}?{'&'.join(kept_parts)}"
# Convert driver prefix for asyncpg
if dsn.startswith("postgresql://"):
dsn = dsn.replace("postgresql://", "postgresql+asyncpg://", 1)
elif dsn.startswith("postgres://"):
dsn = dsn.replace("postgres://", "postgresql+asyncpg://", 1)
return dsn
def _ensure_engine() -> AsyncEngine:
global _engine, _session_maker
if _engine is None:
_engine = create_async_engine(
_get_async_dsn(),
pool_size=5,
max_overflow=5,
pool_timeout=10,
pool_pre_ping=True,
echo=False,
)
_session_maker = async_sessionmaker(
bind=_engine,
class_=AsyncSession,
expire_on_commit=False,
)
print("✅ Async database engine created (asyncpg)")
return _engine
@asynccontextmanager
async def get_session() -> AsyncGenerator[AsyncSession, None]:
"""Provide an async session context manager."""
_ensure_engine()
assert _session_maker is not None
async with _session_maker() as session:
yield session
async def dispose_engine() -> None:
"""Shut down the async engine cleanly."""
global _engine, _session_maker
if _engine is not None:
await _engine.dispose()
_engine = None
_session_maker = None
print("️ Async database engine disposed")
+92
View File
@@ -0,0 +1,92 @@
"""
Synchronous psycopg2 database helper for the AI Engine.
Uses a thread-safe connection pool for legacy V20+ endpoints.
"""
from __future__ import annotations
import os
from contextlib import contextmanager
from typing import Generator
import psycopg2
from psycopg2 import pool
from psycopg2.extensions import connection as PgConnection
from dotenv import load_dotenv
load_dotenv()
# Safe default with no credentials — will fail fast if not configured.
_DEFAULT_DSN = "postgresql://postgres:postgres@localhost:15432/boilerplate_db"
def get_clean_dsn() -> str:
"""
Return a psycopg2-compatible DSN from DATABASE_URL.
Handles DSN cleanup issues that break raw usage:
1. Prisma appends '?schema=public' which psycopg2 cannot parse.
"""
dsn: str = os.getenv("DATABASE_URL", _DEFAULT_DSN)
connect_timeout: str = os.getenv("PGCONNECT_TIMEOUT", "5").strip() or "5"
# Strip Prisma's ?schema= query parameter while preserving any other query args.
if "?" in dsn:
base, query = dsn.split("?", 1)
kept_parts: list[str] = [
part for part in query.split("&") if part and not part.startswith("schema=")
]
dsn = base if not kept_parts else f"{base}?{'&'.join(kept_parts)}"
# Force bounded DB connect attempts so API calls do not hang indefinitely.
if "connect_timeout=" not in dsn:
separator = "&" if "?" in dsn else "?"
dsn = f"{dsn}{separator}connect_timeout={connect_timeout}"
return dsn
class Database:
_pool: pool.ThreadedConnectionPool | None = None
@classmethod
def initialize(cls) -> None:
if cls._pool is None:
dsn: str = get_clean_dsn()
try:
cls._pool = pool.ThreadedConnectionPool(
minconn=1,
maxconn=10,
dsn=dsn,
)
print("✅ Database connection pool created")
except Exception as e:
print(f"❌ Failed to create DB pool: {e}")
raise
@classmethod
def get_conn(cls) -> PgConnection:
if cls._pool is None:
cls.initialize()
assert cls._pool is not None # guaranteed by initialize()
return cls._pool.getconn()
@classmethod
def return_conn(cls, conn: PgConnection) -> None:
if cls._pool:
cls._pool.putconn(conn)
@classmethod
@contextmanager
def connection(cls) -> Generator[PgConnection, None, None]:
"""Context manager for safe connection handling."""
conn: PgConnection = cls.get_conn()
try:
yield conn
finally:
cls.return_conn(conn)
@classmethod
def close_all(cls) -> None:
if cls._pool:
cls._pool.closeall()
print("️ Database connection pool closed")
+726
View File
@@ -0,0 +1,726 @@
{
"version": "v1",
"description": "Per-league odds reliability scores computed from Brier Score analysis",
"min_matches_threshold": 50,
"total_leagues": 265,
"default_reliability": 0.35,
"lookup": {
"bx57cmq1edfq53ckfk791supi": 0.9476,
"55hcphd1ccc6eai1ms77460on": 0.9445,
"d9eaigzyfnfiraqc3ius757tl": 0.9402,
"1gxlzw2ezkyeykhcaa5x8ozkk": 0.9259,
"5jd0k2txwnq69frs79eulba8j": 0.9233,
"6694fff47wqxl10lrd9tb91f8": 0.9193,
"4jg7he1n3rb5dniq6hf49xorq": 0.9061,
"59tpnfrwnvhnhzmnvfyug68hj": 0.8988,
"ac42gi3penartj88fe9l6plpk": 0.8937,
"3j81qr7yc4gdnakfwnxf95ovh": 0.8771,
"9z5643nd06afqu01ea2wt8y4g": 0.8734,
"482ofyysbdbeoxauk19yg7tdt": 0.8722,
"ahl3vljaignq9ebaos4uqkrvo": 0.8696,
"8x3sbh85gc8qir50utw39jl04": 0.865,
"agpweohvn9tugnyl6ry4rhivp": 0.8428,
"4c1nfi2j1m731hcay25fcgndq": 0.8425,
"1j4ehtrbry9depwt6oghaq3lu": 0.8299,
"40yjcbx2sq6oq736iqqqczwt1": 0.8237,
"145hkd59i6foieuwr4mwi6wlq": 0.823,
"34pl8szyvrbwcmfkuocjm3r6t": 0.8227,
"cse5oqqt2pzfcy8uz6yz3tkbj": 0.8212,
"zs18qaehvhg3w1208874zvfa": 0.8176,
"57nu0wygurzkp6fuy5hhrtaa2": 0.8099,
"1eruend45vd20g9hbrpiggs5u": 0.8083,
"595nsvo7ykvoe690b1e4u5n56": 0.7987,
"6vq8j5p3av14nr3iuyi4okhjt": 0.793,
"486rhdgz7yc0sygziht7hje65": 0.7901,
"9hh6n2f84k31zmlcxyvmc1w2y": 0.789,
"3n5046abeu3x482ds3jwda238": 0.7863,
"8yi6ejjd1zudcqtbn07haahg6": 0.7752,
"byhmntnl1b4lxw0zz21im3zkd": 0.7719,
"2bmwykmdlcc2u1c40ytoc39vy": 0.7668,
"82jkgccg7phfjpd0mltdl3pat": 0.7643,
"2nttcoriwf5co73vmz1vr8frm": 0.7641,
"dr2xk7muj8aqcjdz2b3li1c0k": 0.759,
"4yngyfinzd6bb1k7anqtqs0wt": 0.7586,
"eog6knrkfei68si736fpquyzc": 0.756,
"eg6s9f1jj7jr6stmbosn0g6c8": 0.7538,
"ae1wva3zrzcp2zd15gpvsntg6": 0.7517,
"cesdwwnxbc5fmajgroc0hqzy2": 0.7466,
"8k1xcsyvxapl4jlsluh3eomre": 0.7463,
"bdtat25m14jy85y484z3e6lf": 0.7437,
"iu1vi94p4p28oozl1h9bvplr": 0.7411,
"1r097lpxe0xn03ihb7wi98kao": 0.7391,
"2kwbbcootiqqgmrzs6o5inle5": 0.7386,
"9fuwphq8kvugrlc3ckm7k8wes": 0.7358,
"civf31q1inxohs4a03y8reetf": 0.735,
"ili150pwfuf39f7yfdch9lhw": 0.7286,
"abs7n2ae3oydilk0tgmpnsj89": 0.7277,
"9nbpdi9q3ywcm4q0j5u0ekwcq": 0.7254,
"6by3h89i2eykc341oz7lv1ddd": 0.7252,
"4qehj8hfxmy6o2ohp4fxinnzo": 0.7244,
"9u4pm8x0lfmfq3r0pypmrls71": 0.7244,
"c7b8o53flg36wbuevfzy3lb10": 0.7144,
"89ovpy1rarewwzqvi30bfdr8b": 0.7068,
"4d5d3sf6805n5u6jdoa0hdlog": 0.7052,
"eqz64pn0qsp2y7aq4m9id3fn6": 0.7031,
"8q60vlvn3krynkob6igrncdjq": 0.703,
"6ihotpaocgiovlxw18e9r9prx": 0.7019,
"c0r21rtokgnbtc0o2rldjmkxu": 0.7013,
"1mpjd0vbxbtu9zw89yj09xk3z": 0.6996,
"4zwgbb66rif2spcoeeol2motx": 0.6995,
"bu1l7ckihyr0errxw61p0m05": 0.6995,
"cv3tuitw3ho3v0opjjxpn83b9": 0.6974,
"8r98daokeuzsamu5fmjtblqx5": 0.6922,
"dvstmwnvw0mt5p38twn9yttyb": 0.688,
"8y29fg2s85ppcb8uugm5ee8s4": 0.6866,
"19q13y6ruzo0o84ipblcuouzs": 0.6858,
"f4jc2cc5nq7flaoptpi5ua4k4": 0.6852,
"4oogyu6o156iphvdvphwpck10": 0.684,
"3e40pestup9xzagsu2o6c0i8u": 0.6824,
"4rls982p5uzil6x30mhyhv9f3": 0.6812,
"e21cf135btr8t3upw0vl6n6x0": 0.6771,
"65q4uwm6ol1rkf5dp89m8omny": 0.6754,
"46b141eaqq9q7o4gz5gtdpikk": 0.6752,
"75i269i1ak43magshljadydrh": 0.6741,
"3ab1uwtoyjopdj1y1fynyy9jg": 0.6737,
"4mbfidy8zum5u0aqjqo0vuqs2": 0.673,
"7wssxdqi4xihseeam8grqa2b8": 0.666,
"61fzfjogstjuukzcehighq7mu": 0.6641,
"6g8hw3acenrw828la7gwx4mvs": 0.663,
"e1kxdivp5g4cpldgpwvnzl1vv": 0.6626,
"9ikchyu9fb8bvx0s673jofj6s": 0.6622,
"a9vrdkelbgif0gtu3wxsr75xo": 0.6618,
"6sxm2iln2w45ux498pty9miw8": 0.6615,
"ea0h6cf3bhl698hkxhpulh2zz": 0.661,
"apdwh753fupxheygs8seahh7x": 0.6604,
"er5745q30wnr8jv9nr863omzg": 0.659,
"2z7257m7hj58zuxcjrsg4erzc": 0.6551,
"2o9svokc5s7diish3ycrzk7jm": 0.655,
"8usjlmziv3p2re0r2wwzezki9": 0.6549,
"c0yqkbilbbg70ij2473xymmqv": 0.6506,
"du6jsenbjql5e8f3yk880ox4g": 0.6494,
"cbdbziaqczfuyuwqsylqi26zd": 0.6478,
"725gd73msyt08xm76v7gkxj7u": 0.6445,
"enzlj1as2raqm4ids1zyb07y1": 0.6442,
"scf9p4y91yjvqvg5jndxzhxj": 0.6414,
"5z8v4mj6cjs9ex6hdrpourjzh": 0.6389,
"4zwjlzdszduqmxzusysvzymms": 0.6387,
"7nmz249q89qg5ezcvzlheljji": 0.6381,
"2mdmx668tyhy4u4z9zszwjv5v": 0.6345,
"4a7o9rf7ytl8g3ejwpblc6p5n": 0.6306,
"2ty8ihceabty8yddmu31iuuej": 0.6283,
"dy8zaksw5e9nwrs1p5ss4o1nu": 0.628,
"1b70m6qtxrp75b4vtk8hxh8c3": 0.6261,
"ajxs0e0g6ryg5ol8qvw3evrcz": 0.6249,
"a4fgj2rfbpf4ejo1qi624fefo": 0.6184,
"akmkihra9ruad09ljapsm84b3": 0.6182,
"907l7wtxdvugdo9i2249wcmr0": 0.6171,
"6lwpjhktjhl9g7x2w7njmzva6": 0.6164,
"ax1yf4nlzqpcji4j8epdgx3zl": 0.6163,
"6ybvtzejh91761lqe7y1csrqo": 0.6158,
"3btdfgw79qiz3jmyfudovtbu2": 0.6122,
"5cwsxtx37les6m10xj71htkgf": 0.6101,
"9p3nnxhdjahfn8qswpzy8oyc3": 0.61,
"2xg0qvif1rh7du6wmk2eleku3": 0.6091,
"1wwro3z1eb3fl601dju6inlc6": 0.6084,
"gfskxsdituog2kqp9yiu7bzi": 0.6076,
"zilopfej2h0n3vpan5tcynpo": 0.6051,
"2hsidwomhjsaaytdy9u5niyi4": 0.6012,
"1klyfth8tl6lu6ra7k8zmy2n2": 0.5996,
"cegl2ivkc25blcatxp4jmk1ec": 0.5993,
"7qf0jaayyxy3ruamsexv5p1kl": 0.5988,
"erpufio3qaujd9gkszcqvb0bf": 0.5972,
"cfesxhzb83yl8b779uv3revz1": 0.597,
"3ww12jab49q8q8mk9avdwjqgk": 0.5961,
"8t2o4huu2e48ij23dxnl9w5qx": 0.5928,
"5vq1bl8h8dxdr34w0jaanokto": 0.5919,
"ac112osli9fvox1epcg4ld3t6": 0.59,
"3frp1zxrqulrlrnk503n6l4l": 0.5808,
"c76z5d6j7dpi1e79tm8fpm39z": 0.5807,
"6ifaeunfdelecgticvxanikzu": 0.5796,
"81txfenlgw75nq3u2nfdkj92o": 0.5789,
"yv73ms6v1995b5wny16jcfi3": 0.5787,
"b3ufcd24wfnnd5j98ped6irfu": 0.5752,
"29actv1ohj8r10kd9hu0jnb0n": 0.5737,
"bfqezwfhot1l3p1cpk4oonh25": 0.5705,
"5taraea6mqjjldg9zxswo825y": 0.5696,
"7qdv1xae7ikfe8dft3oj29yqc": 0.5692,
"dm5ka0os1e3dxcp3vh05kmp33": 0.5678,
"ay4u6j7lfkcg7x21mx5q121j": 0.5676,
"7af85xa75vozt2l4hzi6ryts7": 0.5663,
"5k620c7y6dlbmcm88dt3eb7t": 0.5644,
"ejunkmfhjz9weugd2bqrkgobb": 0.564,
"3428tckxcirwwh3o3jgc1m8ji": 0.5597,
"d6zovb8puwgcmsg91iya6rbtm": 0.5593,
"2wolc27r8z03itcvwp43e38c5": 0.5592,
"alpfd99yd3lfv7bhjo0biuq7b": 0.5582,
"beqqnubkv05mamuwvimeum015": 0.5577,
"4w7x0s5gfs5abasphlha5de8k": 0.5558,
"9ynnnx1qmkizq1o3qr3v0nsuk": 0.554,
"722fdbecxzcq9788l6jqclzlw": 0.5539,
"287tckirbfj9nb8ar2k9r60vn": 0.5529,
"esrunz7rjb0td98mx9e5cedoy": 0.5516,
"32n2r9bl6x90psj0wa7bfs6vq": 0.5487,
"50ap4sua1xyut3mpu7ehesp63": 0.5483,
"5c96g1zm7vo5ons9c42uy2w3r": 0.5469,
"3p81ltz6845appgkbgkzxueii": 0.5454,
"3n9mk5b2mxmq831wfmv6pu86i": 0.5437,
"5zr0b05eyx25km7z1k03ca9jx": 0.5424,
"1owhvvge4wlx7e0e431b4vhqx": 0.5423,
"3iwftmprsznl6yribr11a8l9m": 0.5393,
"7r1f93t6ddrsa5n8v1nq6qlzm": 0.5393,
"1gwajyt0pk2jm5fx5mu36v114": 0.5389,
"581t4mywybx21wcpmpykhyzr3": 0.5388,
"6wubmo7di3kdpflluf6s8c7vs": 0.5375,
"bq89wbdvedtov6auzuh6rsv7s": 0.5363,
"byu00jvt1j6csyv4y1lkt2fm2": 0.5359,
"af79lqrc0ntom74zq13ccjslo": 0.5357,
"3ri6juw2w6ma0jezszdlv1uqm": 0.5356,
"3l29w00m506ex93t5bbh9cg2a": 0.5355,
"1zp1du9n4rj36p1ss9zbxtqfb": 0.5353,
"9chuiarcjofld1dkj9kysehmb": 0.5346,
"5aw6uyw4pz2bpj24t5z8aacim": 0.5333,
"by5nibd18nkt40t0j8a0j5yzx": 0.5332,
"4yzidekywejmxxp77gqmdgopg": 0.5323,
"7ntvbsyq31jnzoqoa8850b9b8": 0.5305,
"a7247po5qs29o3zsfmt222ydu": 0.5299,
"117yqo02rs8dykkxpm274w3bd": 0.5298,
"193wqkyb0v5jnsblhvd2ocmyo": 0.5296,
"8jh0jejuxfhrpawnoztz2jlv4": 0.5295,
"5y0z0l2epprzbscvzsgldw8vu": 0.5288,
"47s2kt0e8m444ftqvsrqa3bvq": 0.5268,
"2hj3286pqov1g1g59k2t2qcgm": 0.5245,
"7swf4kpu3v38i2it4h94c5s9k": 0.5227,
"78wml3z5wrfxe5iky50tiotgu": 0.5196,
"f39uq10c8xhg5e6rwwcf6lhgc": 0.5186,
"bbajzna018c79opa1kl5kmkqo": 0.5172,
"4davonpqws4a4ejl1awu98zdg": 0.5168,
"1fedahp0rws09tj451onten8r": 0.5163,
"aho73e5udydy96iun3tkzdzsi": 0.5149,
"3aa4mumjl6zyetg6o9hwd5hhx": 0.5125,
"7cwemnr3vi40znjq451zxkus6": 0.5115,
"ajm86skyzse4ym8g6fpgzncxa": 0.5112,
"bgen5kjer2ytfp7lo9949t72g": 0.5102,
"8ey0ww2zsosdmwr8ehsorh6t7": 0.51,
"8najqkluatpaxvqws78b9s17c": 0.5082,
"8v97rcbthsxmzqk4ufxws9mug": 0.506,
"degxm4y6gmvp011ccyrev6z5p": 0.5049,
"3oa9e03e7w9nr8kqwqc3tlqz9": 0.5049,
"5dycj9wdhxh3n33qubw18ohlk": 0.5036,
"3is4bkgf3loxv9qfg3hm8zfqb": 0.5033,
"f47f3717z2vtpxfxrpdd4jl1x": 0.498,
"8ivsfwex4dfx1tvgsiq8askcx": 0.4972,
"8vbck9a4mxjms783lf72779uu": 0.4946,
"aql5z4osw5wmun0emnakfpwji": 0.4946,
"e6vzdkz6l236s9p288mharefy": 0.4925,
"4nidzmunvpvxk1ir9b6m8mpay": 0.4874,
"ein4fkggto3pdh5msp8huafiq": 0.4856,
"1q4ab2bpg5e8jl1g2udnakrju": 0.4852,
"8ztsv3pzrsyq5w1r3a0nfk1y5": 0.4842,
"1qd0wvt30rlswa4g6nu4na660": 0.4826,
"jznihqxle06xych9ygwiwnsa": 0.4796,
"2y8bntiif3a9y6gtmauv30gt": 0.4782,
"477yyajzheg2z8u7uick0e13e": 0.4706,
"bockl24qpr7ryjl8b6obukga": 0.4671,
"7mxwwunvot2pi69pj1yr1kh8i": 0.466,
"3w1hkk9k9gr8fwssyn4icvdfo": 0.4657,
"1txej2dzohnydl21zc9pgx6hy": 0.464,
"b8rae0ib0frjmwlca429bq19q": 0.4624,
"b5udgm9vakjqz8dcmy5b2g0xt": 0.4582,
"eitf7hulqfv1clb7toewkil24": 0.458,
"7hl0svs2hg225i2zud0g3xzp2": 0.4559,
"2aso72utuctat2ecs6nahjss6": 0.4521,
"3ymqchdzk8tt6lfphf26xfvh0": 0.4519,
"2yyjcbbryf1r10apyzl7c7jvp": 0.4507,
"bly7ema5au6j40i0grhl0pnub": 0.4476,
"b1rveez5u792gess9w3e7v5le": 0.4444,
"8sdpk4aerruf515yh76ezo7vi": 0.4434,
"32vph7vcjqgo1ksj1548di90n": 0.44,
"65ggsqdi6drpa4m8y3gkll25k": 0.4394,
"xaouuwuk8qyhv1libkeexwjh": 0.4347,
"6qitd9h242qkvjenaytfdnsf2": 0.4312,
"duuc1qczfnawwncru1ly6o66": 0.4213,
"b60nisd3qn427jm0hrg9kvmab": 0.4203,
"xwnjb1az11zffwty3m6vn8y6": 0.4197,
"dkarmrybx9vx10rg7cywumth0": 0.4158,
"75434tz9rc14xkkvudex742ui": 0.4137,
"c1d9p6b2e9zr5tqlzx3ktjplg": 0.4129,
"b73zounsynk9d3u1p9nvpu7i2": 0.4049,
"913mb508il6jzwtlj28fl892h": 0.4044,
"e0lck99w8meo9qoalfrxgo33o": 0.401,
"8dn0w8zh7nbn2i904603eigwf": 0.3984,
"ddyrh5latwfhesgfh4w401n92": 0.3973,
"avs3xposm3t9x1x2vzsoxzcbu": 0.3957,
"eu2g5j36zzxiazpd729osx0wm": 0.3924,
"67uya58idol2eq18ljecsru5o": 0.3912,
"23e698ls3x6vi9x8wl0mz7bsa": 0.3838,
"6321dlqv4ziuwqte4xpohijtw": 0.382,
"8o5tv5viv4hy1qg9jp94k7ayb": 0.381,
"53tknno09wqihmwxrqcuwq9sa": 0.3782,
"82wo38rqeizxlfjjhfjy4rx7u": 0.3781,
"dvtl8sf1262pd2aqgu641qa7u": 0.3767,
"663a54fmymndjeev47qm7d3nf": 0.3522,
"macko16888165594668885588": 0.3309,
"macko16698982162572521585": 0.3262,
"6lkj3o21cr4g7bql6tb3fk222": 0.3261,
"cu0rmpyff5692eo06ltddjo8a": 0.3161,
"1cnx2c8g3hhp8ssxnwwli0mjb": 0.3121,
"4vt0ldrcl6thpxpcs8zmpdq1g": 0.2926,
"etta63x1t7tnkn4jheisjwk4p": 0.2907,
"1n9l0ex47bu0762qg574hzjtd": 0.2626,
"6jgwiu2gq3dllmrwt45pfdn2z": 0.2416,
"392slbmf1kdqlr6sd1ckt71rs": 0.24,
"8z3180hhw2pj1i65uftlk54uz": 0.2096
},
"details": [
{
"league_id": "bx57cmq1edfq53ckfk791supi",
"league_name": "CAF Konfederasyon Kupası",
"match_count": 98,
"brier_score": 0.3046,
"heavy_fav_win_pct": 84.1,
"fav_win_pct": 63.3,
"odds_reliability": 0.9476
},
{
"league_id": "55hcphd1ccc6eai1ms77460on",
"league_name": "Şampiyonlar Ligi Kadınlar",
"match_count": 89,
"brier_score": 0.3258,
"heavy_fav_win_pct": 83.3,
"fav_win_pct": 74.2,
"odds_reliability": 0.9445
},
{
"league_id": "d9eaigzyfnfiraqc3ius757tl",
"league_name": "Kupa",
"match_count": 78,
"brier_score": 0.3141,
"heavy_fav_win_pct": 81.2,
"fav_win_pct": 73.1,
"odds_reliability": 0.9402
},
{
"league_id": "1gxlzw2ezkyeykhcaa5x8ozkk",
"league_name": "Concacaf Orta Amerika Kupası",
"match_count": 88,
"brier_score": 0.3338,
"heavy_fav_win_pct": 79.4,
"fav_win_pct": 61.4,
"odds_reliability": 0.9259
},
{
"league_id": "5jd0k2txwnq69frs79eulba8j",
"league_name": "Kupa",
"match_count": 69,
"brier_score": 0.3223,
"heavy_fav_win_pct": 78.4,
"fav_win_pct": 66.7,
"odds_reliability": 0.9233
},
{
"league_id": "6694fff47wqxl10lrd9tb91f8",
"league_name": "Kupa",
"match_count": 55,
"brier_score": 0.3099,
"heavy_fav_win_pct": 78.8,
"fav_win_pct": 67.3,
"odds_reliability": 0.9193
},
{
"league_id": "4jg7he1n3rb5dniq6hf49xorq",
"league_name": "Premier Lig",
"match_count": 79,
"brier_score": 0.3333,
"heavy_fav_win_pct": 77.1,
"fav_win_pct": 64.6,
"odds_reliability": 0.9061
},
{
"league_id": "59tpnfrwnvhnhzmnvfyug68hj",
"league_name": "Libertadores Kupası",
"match_count": 180,
"brier_score": 0.3408,
"heavy_fav_win_pct": 76.2,
"fav_win_pct": 61.7,
"odds_reliability": 0.8988
},
{
"league_id": "ac42gi3penartj88fe9l6plpk",
"league_name": "Premier Lig",
"match_count": 185,
"brier_score": 0.3148,
"heavy_fav_win_pct": 70.7,
"fav_win_pct": 68.1,
"odds_reliability": 0.8937
},
{
"league_id": "3j81qr7yc4gdnakfwnxf95ovh",
"league_name": "Premier Lig",
"match_count": 106,
"brier_score": 0.333,
"heavy_fav_win_pct": 72.2,
"fav_win_pct": 60.4,
"odds_reliability": 0.8771
},
{
"league_id": "9z5643nd06afqu01ea2wt8y4g",
"league_name": "Kuu Bara Ligi",
"match_count": 110,
"brier_score": 0.3294,
"heavy_fav_win_pct": 70.3,
"fav_win_pct": 53.6,
"odds_reliability": 0.8734
},
{
"league_id": "482ofyysbdbeoxauk19yg7tdt",
"league_name": "Trendyol Süper Lig",
"match_count": 342,
"brier_score": 0.3627,
"heavy_fav_win_pct": 80.7,
"fav_win_pct": 59.6,
"odds_reliability": 0.8722
},
{
"league_id": "ahl3vljaignq9ebaos4uqkrvo",
"league_name": "Kupa",
"match_count": 105,
"brier_score": 0.331,
"heavy_fav_win_pct": 70.4,
"fav_win_pct": 63.8,
"odds_reliability": 0.8696
},
{
"league_id": "8x3sbh85gc8qir50utw39jl04",
"league_name": "UEFA Kadınlar Euro 2025 Elemeleri",
"match_count": 88,
"brier_score": 0.3421,
"heavy_fav_win_pct": 75.5,
"fav_win_pct": 61.4,
"odds_reliability": 0.865
},
{
"league_id": "agpweohvn9tugnyl6ry4rhivp",
"league_name": "Eredivisie Kadınlar",
"match_count": 51,
"brier_score": 0.3356,
"heavy_fav_win_pct": 72.0,
"fav_win_pct": 56.9,
"odds_reliability": 0.8428
},
{
"league_id": "4c1nfi2j1m731hcay25fcgndq",
"league_name": "Avrupa Ligi",
"match_count": 242,
"brier_score": 0.3625,
"heavy_fav_win_pct": 77.6,
"fav_win_pct": 61.6,
"odds_reliability": 0.8425
},
{
"league_id": "1j4ehtrbry9depwt6oghaq3lu",
"league_name": "Süper Lig",
"match_count": 84,
"brier_score": 0.3201,
"heavy_fav_win_pct": 65.9,
"fav_win_pct": 60.7,
"odds_reliability": 0.8299
},
{
"league_id": "40yjcbx2sq6oq736iqqqczwt1",
"league_name": "DK Elemeler",
"match_count": 88,
"brier_score": 0.3383,
"heavy_fav_win_pct": 68.6,
"fav_win_pct": 55.7,
"odds_reliability": 0.8237
},
{
"league_id": "145hkd59i6foieuwr4mwi6wlq",
"league_name": "Pro Lig",
"match_count": 143,
"brier_score": 0.3546,
"heavy_fav_win_pct": 73.8,
"fav_win_pct": 60.1,
"odds_reliability": 0.823
},
{
"league_id": "34pl8szyvrbwcmfkuocjm3r6t",
"league_name": "LaLiga",
"match_count": 364,
"brier_score": 0.3773,
"heavy_fav_win_pct": 80.2,
"fav_win_pct": 56.6,
"odds_reliability": 0.8227
},
{
"league_id": "cse5oqqt2pzfcy8uz6yz3tkbj",
"league_name": "CAF Şampiyonlar Ligi",
"match_count": 91,
"brier_score": 0.3513,
"heavy_fav_win_pct": 73.9,
"fav_win_pct": 57.1,
"odds_reliability": 0.8212
},
{
"league_id": "zs18qaehvhg3w1208874zvfa",
"league_name": "1. Lig",
"match_count": 225,
"brier_score": 0.3744,
"heavy_fav_win_pct": 82.1,
"fav_win_pct": 59.6,
"odds_reliability": 0.8176
},
{
"league_id": "57nu0wygurzkp6fuy5hhrtaa2",
"league_name": "1. Lig",
"match_count": 286,
"brier_score": 0.3626,
"heavy_fav_win_pct": 72.9,
"fav_win_pct": 59.1,
"odds_reliability": 0.8099
},
{
"league_id": "1eruend45vd20g9hbrpiggs5u",
"league_name": "Botola Pro",
"match_count": 265,
"brier_score": 0.3625,
"heavy_fav_win_pct": 72.9,
"fav_win_pct": 50.2,
"odds_reliability": 0.8083
},
{
"league_id": "595nsvo7ykvoe690b1e4u5n56",
"league_name": "UEFA Uluslar Ligi",
"match_count": 67,
"brier_score": 0.3687,
"heavy_fav_win_pct": 83.3,
"fav_win_pct": 50.7,
"odds_reliability": 0.7987
},
{
"league_id": "6vq8j5p3av14nr3iuyi4okhjt",
"league_name": "Süper Lig Kadınlar",
"match_count": 70,
"brier_score": 0.356,
"heavy_fav_win_pct": 73.5,
"fav_win_pct": 58.6,
"odds_reliability": 0.793
},
{
"league_id": "486rhdgz7yc0sygziht7hje65",
"league_name": "Kupa",
"match_count": 62,
"brier_score": 0.3704,
"heavy_fav_win_pct": 81.1,
"fav_win_pct": 66.1,
"odds_reliability": 0.7901
},
{
"league_id": "9hh6n2f84k31zmlcxyvmc1w2y",
"league_name": "2. Lig",
"match_count": 204,
"brier_score": 0.357,
"heavy_fav_win_pct": 69.2,
"fav_win_pct": 62.3,
"odds_reliability": 0.789
},
{
"league_id": "3n5046abeu3x482ds3jwda238",
"league_name": "WE Lig Kadınlar",
"match_count": 102,
"brier_score": 0.3761,
"heavy_fav_win_pct": 85.4,
"fav_win_pct": 58.8,
"odds_reliability": 0.7863
},
{
"league_id": "8yi6ejjd1zudcqtbn07haahg6",
"league_name": "Premier Lig",
"match_count": 302,
"brier_score": 0.3712,
"heavy_fav_win_pct": 72.1,
"fav_win_pct": 56.3,
"odds_reliability": 0.7752
},
{
"league_id": "byhmntnl1b4lxw0zz21im3zkd",
"league_name": "Kupa",
"match_count": 96,
"brier_score": 0.3528,
"heavy_fav_win_pct": 68.2,
"fav_win_pct": 58.3,
"odds_reliability": 0.7719
},
{
"league_id": "2bmwykmdlcc2u1c40ytoc39vy",
"league_name": "Açık Kupası",
"match_count": 93,
"brier_score": 0.3807,
"heavy_fav_win_pct": 84.6,
"fav_win_pct": 66.7,
"odds_reliability": 0.7668
},
{
"league_id": "82jkgccg7phfjpd0mltdl3pat",
"league_name": "Süper Lig",
"match_count": 289,
"brier_score": 0.3782,
"heavy_fav_win_pct": 74.0,
"fav_win_pct": 57.4,
"odds_reliability": 0.7643
},
{
"league_id": "2nttcoriwf5co73vmz1vr8frm",
"league_name": "Nesine 2. Lig",
"match_count": 525,
"brier_score": 0.3782,
"heavy_fav_win_pct": 71.8,
"fav_win_pct": 55.2,
"odds_reliability": 0.7641
},
{
"league_id": "dr2xk7muj8aqcjdz2b3li1c0k",
"league_name": "Meistaradeildin",
"match_count": 129,
"brier_score": 0.3714,
"heavy_fav_win_pct": 73.6,
"fav_win_pct": 61.2,
"odds_reliability": 0.759
},
{
"league_id": "4yngyfinzd6bb1k7anqtqs0wt",
"league_name": "Premier Lig",
"match_count": 195,
"brier_score": 0.3772,
"heavy_fav_win_pct": 74.4,
"fav_win_pct": 57.4,
"odds_reliability": 0.7586
},
{
"league_id": "eog6knrkfei68si736fpquyzc",
"league_name": "Lig Kupası",
"match_count": 120,
"brier_score": 0.3632,
"heavy_fav_win_pct": 69.9,
"fav_win_pct": 66.7,
"odds_reliability": 0.756
},
{
"league_id": "eg6s9f1jj7jr6stmbosn0g6c8",
"league_name": "Süper Lig",
"match_count": 108,
"brier_score": 0.3657,
"heavy_fav_win_pct": 71.2,
"fav_win_pct": 55.6,
"odds_reliability": 0.7538
},
{
"league_id": "ae1wva3zrzcp2zd15gpvsntg6",
"league_name": "Ulusal Lig",
"match_count": 278,
"brier_score": 0.3783,
"heavy_fav_win_pct": 72.7,
"fav_win_pct": 55.0,
"odds_reliability": 0.7517
},
{
"league_id": "cesdwwnxbc5fmajgroc0hqzy2",
"league_name": "Hazırlık Maçları Ülkeler",
"match_count": 235,
"brier_score": 0.3669,
"heavy_fav_win_pct": 67.6,
"fav_win_pct": 56.2,
"odds_reliability": 0.7466
},
{
"league_id": "8k1xcsyvxapl4jlsluh3eomre",
"league_name": "Premier Lig",
"match_count": 328,
"brier_score": 0.385,
"heavy_fav_win_pct": 74.2,
"fav_win_pct": 45.7,
"odds_reliability": 0.7463
},
{
"league_id": "bdtat25m14jy85y484z3e6lf",
"league_name": "Kupa",
"match_count": 90,
"brier_score": 0.3772,
"heavy_fav_win_pct": 75.7,
"fav_win_pct": 55.6,
"odds_reliability": 0.7437
},
{
"league_id": "iu1vi94p4p28oozl1h9bvplr",
"league_name": "1. Lig",
"match_count": 158,
"brier_score": 0.3729,
"heavy_fav_win_pct": 71.2,
"fav_win_pct": 50.0,
"odds_reliability": 0.7411
},
{
"league_id": "1r097lpxe0xn03ihb7wi98kao",
"league_name": "Serie A",
"match_count": 359,
"brier_score": 0.3732,
"heavy_fav_win_pct": 67.8,
"fav_win_pct": 56.5,
"odds_reliability": 0.7391
},
{
"league_id": "2kwbbcootiqqgmrzs6o5inle5",
"league_name": "Premier Lig",
"match_count": 369,
"brier_score": 0.3791,
"heavy_fav_win_pct": 70.2,
"fav_win_pct": 54.2,
"odds_reliability": 0.7386
},
{
"league_id": "9fuwphq8kvugrlc3ckm7k8wes",
"league_name": "Ligler Kupası",
"match_count": 143,
"brier_score": 0.3934,
"heavy_fav_win_pct": 81.6,
"fav_win_pct": 50.3,
"odds_reliability": 0.7358
},
{
"league_id": "civf31q1inxohs4a03y8reetf",
"league_name": "Premier Lig",
"match_count": 320,
"brier_score": 0.3721,
"heavy_fav_win_pct": 67.2,
"fav_win_pct": 57.8,
"odds_reliability": 0.735
},
{
"league_id": "ili150pwfuf39f7yfdch9lhw",
"league_name": "UEFA U21 Şampiyonası Elemeler",
"match_count": 112,
"brier_score": 0.3715,
"heavy_fav_win_pct": 70.4,
"fav_win_pct": 67.9,
"odds_reliability": 0.7286
},
{
"league_id": "abs7n2ae3oydilk0tgmpnsj89",
"league_name": "Azadegan Ligi",
"match_count": 217,
"brier_score": 0.3801,
"heavy_fav_win_pct": 71.4,
"fav_win_pct": 45.2,
"odds_reliability": 0.7277
},
{
"league_id": "9nbpdi9q3ywcm4q0j5u0ekwcq",
"league_name": "Serie D",
"match_count": 232,
"brier_score": 0.3718,
"heavy_fav_win_pct": 67.2,
"fav_win_pct": 54.7,
"odds_reliability": 0.7254
}
]
}
File diff suppressed because it is too large Load Diff
+243
View File
@@ -0,0 +1,243 @@
"""
V27 Rolling Window Feature Calculator
======================================
Computes rolling averages over 5/10/20 match windows,
with home/away splits and trend detection.
"""
from __future__ import annotations
from typing import Dict, List, Tuple
import math
def calc_rolling_features(
team_matches: List[Tuple], # [(mst, is_home, team_goals, opp_goals, opp_id), ...]
before_date: int,
team_is_home: bool,
) -> Dict[str, float]:
"""Calculate rolling window features for a team before a given date."""
valid = [m for m in team_matches if m[0] < before_date]
defaults = {
"rolling5_goals_avg": 1.3, "rolling5_conceded_avg": 1.2,
"rolling10_goals_avg": 1.3, "rolling10_conceded_avg": 1.2,
"rolling20_goals_avg": 1.3, "rolling20_conceded_avg": 1.2,
"rolling5_clean_sheets": 0.25,
"venue_goals_avg": 1.3, "venue_conceded_avg": 1.2,
"goal_trend": 0.0,
}
if len(valid) < 3:
return defaults
result = {}
for window in [5, 10, 20]:
recent = valid[-window:] if len(valid) >= window else valid
n = len(recent)
g_sum = sum(m[2] for m in recent)
c_sum = sum(m[3] for m in recent)
result[f"rolling{window}_goals_avg"] = g_sum / n
result[f"rolling{window}_conceded_avg"] = c_sum / n
# Clean sheet rate (last 5)
r5 = valid[-5:] if len(valid) >= 5 else valid
result["rolling5_clean_sheets"] = sum(1 for m in r5 if m[3] == 0) / len(r5)
# Venue-specific (home-only or away-only)
venue_matches = [m for m in valid if m[1] == team_is_home]
if venue_matches:
vm = venue_matches[-10:] if len(venue_matches) >= 10 else venue_matches
result["venue_goals_avg"] = sum(m[2] for m in vm) / len(vm)
result["venue_conceded_avg"] = sum(m[3] for m in vm) / len(vm)
else:
result["venue_goals_avg"] = defaults["venue_goals_avg"]
result["venue_conceded_avg"] = defaults["venue_conceded_avg"]
# Goal trend: compare last 3 vs previous 3
if len(valid) >= 6:
last3 = sum(m[2] for m in valid[-3:]) / 3
prev3 = sum(m[2] for m in valid[-6:-3]) / 3
result["goal_trend"] = last3 - prev3
else:
result["goal_trend"] = 0.0
return result
def calc_league_quality(
all_matches: List[Tuple], # all FT matches in this league
) -> Dict[str, float]:
"""Calculate league-level quality features."""
defaults = {
"league_home_win_rate": 0.45,
"league_draw_rate": 0.25,
"league_btts_rate": 0.50,
"league_ou25_rate": 0.50,
"league_reliability_score": 0.50,
}
if len(all_matches) < 20:
return defaults
n = len(all_matches)
home_wins = sum(1 for m in all_matches if m[2] > m[3])
draws = sum(1 for m in all_matches if m[2] == m[3])
btts = sum(1 for m in all_matches if m[2] > 0 and m[3] > 0)
ou25 = sum(1 for m in all_matches if (m[2] + m[3]) > 2.5)
hw_rate = home_wins / n
dr_rate = draws / n
btts_rate = btts / n
ou25_rate = ou25 / n
# Reliability: leagues closer to averages are more predictable
predictability = 1.0 - abs(hw_rate - 0.45) - abs(dr_rate - 0.27) * 0.5
reliability = max(0.2, min(0.95, predictability))
return {
"league_home_win_rate": round(hw_rate, 4),
"league_draw_rate": round(dr_rate, 4),
"league_btts_rate": round(btts_rate, 4),
"league_ou25_rate": round(ou25_rate, 4),
"league_reliability_score": round(reliability, 4),
}
def calc_time_features(
team_matches: List[Tuple],
match_mst: int,
) -> Dict[str, float]:
"""Calculate time-based features."""
from datetime import datetime
# Days since last match
valid = [m for m in team_matches if m[0] < match_mst]
if valid:
last_mst = valid[-1][0]
days_rest = (match_mst - last_mst) / 86_400_000 # ms to days
days_rest = min(days_rest, 60.0) # cap at 60 days
else:
days_rest = 14.0
# Month and season flags
try:
dt = datetime.utcfromtimestamp(match_mst / 1000)
month = dt.month
is_season_start = 1.0 if month in (7, 8) else 0.0
is_season_end = 1.0 if month in (5, 6) else 0.0
except Exception:
month = 6
is_season_start = 0.0
is_season_end = 0.0
return {
"days_rest": round(days_rest, 2),
"match_month": month,
"is_season_start": is_season_start,
"is_season_end": is_season_end,
}
def calc_advanced_h2h(
team_matches: List[Tuple],
home_id: int,
away_id: int,
before_date: int,
) -> Dict[str, float]:
"""Calculate advanced H2H features."""
defaults = {
"h2h_home_goals_avg": 1.3,
"h2h_away_goals_avg": 1.1,
"h2h_recent_trend": 0.0,
"h2h_venue_advantage": 0.0,
}
h2h = [m for m in team_matches if m[4] == away_id and m[0] < before_date]
if not h2h:
return defaults
recent = h2h[-10:]
home_goals_total = 0
away_goals_total = 0
venue_home_wins = 0
venue_total = 0
for mst, is_home, team_goals, opp_goals, _ in recent:
if is_home:
home_goals_total += team_goals
away_goals_total += opp_goals
venue_total += 1
if team_goals > opp_goals:
venue_home_wins += 1
else:
home_goals_total += opp_goals
away_goals_total += team_goals
n = len(recent)
result = {
"h2h_home_goals_avg": home_goals_total / n,
"h2h_away_goals_avg": away_goals_total / n,
"h2h_venue_advantage": venue_home_wins / venue_total if venue_total > 0 else 0.5,
}
# Recent trend: last 3 vs overall
if len(h2h) >= 4:
last3_pts = sum(
1.0 if m[2] > m[3] else (0.5 if m[2] == m[3] else 0.0)
for m in h2h[-3:]
) / 3
overall_pts = sum(
1.0 if m[2] > m[3] else (0.5 if m[2] == m[3] else 0.0)
for m in h2h
) / len(h2h)
result["h2h_recent_trend"] = round(last3_pts - overall_pts, 4)
else:
result["h2h_recent_trend"] = 0.0
return result
def calc_strength_diff(
home_form: Dict[str, float],
away_form: Dict[str, float],
home_elo: Dict[str, float],
away_elo: Dict[str, float],
home_momentum: float,
away_momentum: float,
upset_potential: float,
) -> Dict[str, float]:
"""Calculate strength differential features."""
# Attack vs Defense mismatches
h_attack = home_form.get("goals_avg", 1.3)
a_defense = away_form.get("conceded_avg", 1.2)
a_attack = away_form.get("goals_avg", 1.3)
h_defense = home_form.get("conceded_avg", 1.2)
atk_def_home = h_attack - a_defense # positive = home attack > away defense
atk_def_away = a_attack - h_defense
# XG diff approximation
xg_diff = (h_attack + a_defense) / 2 - (a_attack + h_defense) / 2
# Form × Momentum interaction
form_mom = (home_momentum - away_momentum) * (
home_form.get("scoring_rate", 0.75) - away_form.get("scoring_rate", 0.75)
)
# ELO-Form consistency
elo_diff = home_elo.get("overall", 1500) - away_elo.get("overall", 1500)
form_diff = h_attack - a_attack
elo_form_consistency = 1.0 if (elo_diff > 0 and form_diff > 0) or (elo_diff < 0 and form_diff < 0) else 0.0
# Upset × ELO gap
elo_gap = abs(elo_diff)
upset_x_elo = upset_potential * (elo_gap / 400.0)
return {
"attack_vs_defense_home": round(atk_def_home, 4),
"attack_vs_defense_away": round(atk_def_away, 4),
"xg_diff": round(xg_diff, 4),
"form_momentum_interaction": round(form_mom, 4),
"elo_form_consistency": elo_form_consistency,
"upset_x_elo_gap": round(upset_x_elo, 4),
}
-4
View File
@@ -15,13 +15,9 @@ Orijinal Faktörler:
- Tarihsel upset pattern - Tarihsel upset pattern
""" """
import os
import sys
from typing import Dict, Any, Optional, Tuple, List from typing import Dict, Any, Optional, Tuple, List
from dataclasses import dataclass, field from dataclasses import dataclass, field
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try: try:
import psycopg2 import psycopg2
from psycopg2.extras import RealDictCursor from psycopg2.extras import RealDictCursor
+202 -32
View File
@@ -7,16 +7,24 @@ import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Any from typing import Any
from datetime import datetime
import uvicorn import uvicorn
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Request from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import subprocess
from pydantic import BaseModel from pydantic import BaseModel
from models.basketball_v25 import get_basketball_v25_predictor try:
from models.basketball_v25 import get_basketball_v25_predictor
HAS_BASKETBALL = True
except ImportError:
HAS_BASKETBALL = False
from services.single_match_orchestrator import get_single_match_orchestrator from services.single_match_orchestrator import get_single_match_orchestrator
from data.database import dispose_engine from services.v26_shadow_engine import get_v26_shadow_engine
from models.league_model import get_league_model_loader
load_dotenv() load_dotenv()
@@ -33,12 +41,30 @@ class CouponRequest(BaseModel):
min_confidence: float | None = None min_confidence: float | None = None
class RetrainRequest(BaseModel):
reason: str | None = "manual"
markets: str | None = None # comma-separated, e.g. "MS,OU25,BTTS"
trials: int | None = 50
# ─── Retrain state tracking ──────────────────────────────────
_retrain_state: dict[str, Any] = {
"running": False,
"last_started": None,
"last_completed": None,
"last_status": None,
"last_error": None,
"pid": None,
}
@asynccontextmanager @asynccontextmanager
async def lifespan(_: FastAPI): async def lifespan(_: FastAPI):
try: try:
print("🚀 Initializing V25 orchestrator...", flush=True) print("🚀 Initializing V28 orchestrator...", flush=True)
get_single_match_orchestrator() get_single_match_orchestrator()
print("✅ V25 orchestrator ready", flush=True) get_v26_shadow_engine()
print("✅ V28 orchestrator ready", flush=True)
except Exception as error: except Exception as error:
print(f"❌ Failed to initialize orchestrator: {error}", flush=True) print(f"❌ Failed to initialize orchestrator: {error}", flush=True)
import traceback import traceback
@@ -47,14 +73,11 @@ async def lifespan(_: FastAPI):
yield yield
# Cleanup async DB connections on shutdown
await dispose_engine()
app = FastAPI( app = FastAPI(
title="Suggest-Bet AI Engine", title="Suggest-Bet AI Engine",
version="25.0.0", version="28.0.0",
description="V25 Single Match Prediction Package API", description="V28 Single Match Prediction Package API",
lifespan=lifespan, lifespan=lifespan,
) )
@@ -102,8 +125,9 @@ async def global_exception_handler(_: Request, exc: Exception):
@app.get("/") @app.get("/")
def read_root() -> dict[str, Any]: def read_root() -> dict[str, Any]:
return { return {
"status": "Suggest-Bet AI Engine v25", "status": "Suggest-Bet AI Engine v28",
"engine": "V25 Single Match Orchestrator", "engine": "V28 Single Match Orchestrator",
"mode": os.getenv("AI_ENGINE_MODE", "v28"),
"routes": [ "routes": [
"POST /v20plus/analyze/{match_id}", "POST /v20plus/analyze/{match_id}",
"GET /v20plus/analyze-htms/{match_id}", "GET /v20plus/analyze-htms/{match_id}",
@@ -111,6 +135,8 @@ def read_root() -> dict[str, Any]:
"GET /v20plus/reversal-watchlist", "GET /v20plus/reversal-watchlist",
"POST /v20plus/coupon", "POST /v20plus/coupon",
"GET /v20plus/daily-banker", "GET /v20plus/daily-banker",
"POST /v1/admin/retrain",
"GET /v1/admin/retrain/status",
], ],
} }
@@ -118,33 +144,70 @@ def read_root() -> dict[str, Any]:
@app.get("/health") @app.get("/health")
def health_check() -> dict[str, Any]: def health_check() -> dict[str, Any]:
try: try:
get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
basketball_predictor = get_basketball_v25_predictor() shadow_engine = get_v26_shadow_engine()
basketball_readiness = basketball_predictor.readiness_summary()
ready = bool(basketball_readiness["fully_loaded"]) # Per-market V25 model status
v25_readiness: dict[str, Any] = {"fully_loaded": False}
try:
v25_predictor = orchestrator._get_v25_predictor()
v25_readiness = v25_predictor.readiness_summary()
except Exception as v25_err:
v25_readiness = {"fully_loaded": False, "error": str(v25_err)}
if HAS_BASKETBALL:
basketball_predictor = get_basketball_v25_predictor()
basketball_readiness = basketball_predictor.readiness_summary()
ready = bool(basketball_readiness.get("fully_loaded", True))
else:
basketball_readiness = {"fully_loaded": False, "error": "Basketball module not found"}
ready = True
league_readiness = get_league_model_loader().readiness_summary()
overall_ready = ready and v25_readiness.get("fully_loaded", False)
return { return {
"status": "healthy" if ready else "degraded", "status": "healthy" if overall_ready else "degraded",
"engine": "v25.main", "engine": "v28.main",
"ready": ready, "mode": os.getenv("AI_ENGINE_MODE", "v28"),
"ready": overall_ready,
"v25_football": v25_readiness,
"league_specific": league_readiness,
"basketball_v25": basketball_readiness, "basketball_v25": basketball_readiness,
"v26_shadow": shadow_engine.readiness_summary(),
"prediction_service_ready": True,
"model_loaded": overall_ready,
"orchestrator_mode": getattr(orchestrator, "engine_mode", "v28"),
} }
except Exception as error: except Exception as error:
return {"status": "unhealthy", "ready": False, "error": str(error)} return {"status": "unhealthy", "ready": False, "error": str(error)}
_REQUIRED_RESPONSE_FIELDS = ("match_info", "market_board", "main_pick", "bet_summary", "data_quality")
@app.post("/v20plus/analyze/{match_id}") @app.post("/v20plus/analyze/{match_id}")
async def analyze_match_v20plus(match_id: str) -> dict[str, Any]: async def analyze_match_v20plus(match_id: str) -> dict[str, Any]:
started_at = time.time()
orchestrator = get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
result = orchestrator.analyze_match(match_id) result = await asyncio.to_thread(orchestrator.analyze_match, match_id)
elapsed_ms = int((time.time() - started_at) * 1000)
if not result: if not result:
raise HTTPException(status_code=404, detail=f"Match not found: {match_id}") raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")
# Response validation: log missing required fields (non-fatal)
missing_fields = [f for f in _REQUIRED_RESPONSE_FIELDS if f not in result]
if missing_fields:
print(f"⚠️ [API] analyze/{match_id} response missing fields: {missing_fields} ({elapsed_ms}ms)")
result["timing_ms"] = elapsed_ms
return result return result
@app.get("/v20plus/analyze-htms/{match_id}") @app.get("/v20plus/analyze-htms/{match_id}")
async def analyze_match_htms_v20plus(match_id: str) -> dict[str, Any]: async def analyze_match_htms_v20plus(match_id: str) -> dict[str, Any]:
orchestrator = get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
result = orchestrator.analyze_match_htms(match_id) result = await asyncio.to_thread(orchestrator.analyze_match_htms, match_id)
if not result: if not result:
raise HTTPException(status_code=404, detail=f"Match not found: {match_id}") raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")
return result return result
@@ -196,7 +259,7 @@ async def analyze_match_htft_v20plus(match_id: str, timeout_sec: int = 30) -> di
key=lambda item: float(item[1]), key=lambda item: float(item[1]),
) )
return { return {
"engine": "v25.main", "engine": "v28.main",
"match_info": result.get("match_info", {}), "match_info": result.get("match_info", {}),
"timing_ms": int((time.time() - started_at) * 1000), "timing_ms": int((time.time() - started_at) * 1000),
"ht_ft_probs": htft_probs, "ht_ft_probs": htft_probs,
@@ -215,11 +278,12 @@ async def analyze_match_htft_v20plus(match_id: str, timeout_sec: int = 30) -> di
@app.post("/v20plus/coupon") @app.post("/v20plus/coupon")
async def generate_coupon_v20plus(request: CouponRequest) -> dict[str, Any]: async def generate_coupon_v20plus(request: CouponRequest) -> dict[str, Any]:
orchestrator = get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
return orchestrator.build_coupon( return await asyncio.to_thread(
match_ids=request.match_ids, orchestrator.build_coupon,
strategy=request.strategy or "BALANCED", request.match_ids,
max_matches=request.max_matches, request.strategy or "BALANCED",
min_confidence=request.min_confidence, request.max_matches,
request.min_confidence,
) )
@@ -229,7 +293,7 @@ async def get_daily_banker_v20plus(count: int = 3) -> dict[str, Any]:
raise HTTPException(status_code=400, detail="count must be >= 1") raise HTTPException(status_code=400, detail="count must be >= 1")
orchestrator = get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
bankers = orchestrator.get_daily_bankers(count=count) bankers = await asyncio.to_thread(orchestrator.get_daily_bankers, count)
return {"count": len(bankers), "bankers": bankers} return {"count": len(bankers), "bankers": bankers}
@app.get("/v20plus/reversal-watchlist") @app.get("/v20plus/reversal-watchlist")
@@ -247,14 +311,120 @@ async def get_reversal_watchlist_v20plus(
raise HTTPException(status_code=400, detail="min_score must be between 0 and 100") raise HTTPException(status_code=400, detail="min_score must be between 0 and 100")
orchestrator = get_single_match_orchestrator() orchestrator = get_single_match_orchestrator()
return orchestrator.get_reversal_watchlist( return await asyncio.to_thread(
count=count, orchestrator.get_reversal_watchlist,
horizon_hours=horizon_hours, count,
min_score=min_score, horizon_hours,
top_leagues_only=top_leagues_only, min_score,
top_leagues_only,
) )
# ─── ADMIN: Retrain Pipeline ─────────────────────────────────
def _run_retrain_pipeline(markets: str | None, trials: int):
"""Background function: extract data → train model → reload."""
global _retrain_state
ai_dir = os.path.dirname(os.path.abspath(__file__))
scripts_dir = os.path.join(ai_dir, "scripts")
python = os.path.join(ai_dir, "venv", "bin", "python3")
if not os.path.exists(python):
python = sys.executable # fallback
try:
# Step 1: Extract training data
print("🔄 [RETRAIN] Step 1/3: Extracting training data...", flush=True)
result = subprocess.run(
[python, os.path.join(scripts_dir, "extract_training_data.py")],
capture_output=True, text=True, timeout=600, cwd=ai_dir,
)
if result.returncode != 0:
raise RuntimeError(f"Extract failed:\n{result.stderr[-500:]}")
print(f"✅ [RETRAIN] Extract done", flush=True)
# Step 2: Train V25 Pro
print("🔄 [RETRAIN] Step 2/3: Training V25 Pro model...", flush=True)
train_cmd = [python, os.path.join(scripts_dir, "train_v25_pro.py")]
if markets:
train_cmd += ["--markets", markets]
train_cmd += ["--trials", str(trials)]
result = subprocess.run(
train_cmd, capture_output=True, text=True, timeout=3600, cwd=ai_dir,
)
if result.returncode != 0:
raise RuntimeError(f"Training failed:\n{result.stderr[-500:]}")
print(f"✅ [RETRAIN] Training done", flush=True)
# Step 3: Reload models in memory
print("🔄 [RETRAIN] Step 3/3: Reloading models...", flush=True)
try:
orchestrator = get_single_match_orchestrator()
v25 = orchestrator._get_v25_predictor()
v25._loaded = False
v25.load_models()
print("✅ [RETRAIN] Models reloaded in memory", flush=True)
except Exception as reload_err:
print(f"⚠️ [RETRAIN] Hot reload failed (restart needed): {reload_err}", flush=True)
_retrain_state.update({
"running": False,
"last_completed": datetime.now().isoformat(),
"last_status": "success",
"last_error": None,
})
print("🎉 [RETRAIN] Pipeline complete!", flush=True)
except Exception as err:
_retrain_state.update({
"running": False,
"last_completed": datetime.now().isoformat(),
"last_status": "failed",
"last_error": str(err),
})
print(f"❌ [RETRAIN] Pipeline failed: {err}", flush=True)
@app.post("/v1/admin/retrain")
async def admin_retrain(request: RetrainRequest) -> dict[str, Any]:
"""Trigger full retrain pipeline: extract → train → reload."""
if _retrain_state["running"]:
return {
"status": "already_running",
"message": f"Retrain in progress since {_retrain_state['last_started']}",
}
_retrain_state.update({
"running": True,
"last_started": datetime.now().isoformat(),
"last_status": "running",
"last_error": None,
})
# Run in background thread
import threading
thread = threading.Thread(
target=_run_retrain_pipeline,
args=(request.markets, request.trials or 50),
daemon=True,
)
thread.start()
return {
"status": "triggered",
"message": "Retrain pipeline started in background",
"reason": request.reason,
"markets": request.markets or "all",
"trials": request.trials or 50,
}
@app.get("/v1/admin/retrain/status")
async def admin_retrain_status() -> dict[str, Any]:
"""Check retrain pipeline status."""
return {**_retrain_state}
if __name__ == "__main__": if __name__ == "__main__":
port = int(os.getenv("PORT", "8000")) port = int(os.getenv("PORT", "8000"))
uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True) uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
+456
View File
@@ -0,0 +1,456 @@
"""
Calibration Module for XGBoost Models
=====================================
Calibrates raw probabilities from XGBoost models using Isotonic Regression.
Ensures that a predicted probability of 70% actually corresponds to a 70% win rate.
Usage:
from ai_engine.models.calibration import Calibrator
calibrator = Calibrator()
calibrated_prob = calibrator.calibrate("ms", raw_prob)
# Training new calibration models:
calibrator.train_calibration(valid_df, market="ms")
"""
import os
import pickle
import json
import numpy as np
import pandas as pd
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Any
from sklearn.isotonic import IsotonicRegression
from sklearn.calibration import calibration_curve
from sklearn.metrics import brier_score_loss
AI_ENGINE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CALIBRATION_DIR = os.path.join(AI_ENGINE_DIR, "models", "calibration")
os.makedirs(CALIBRATION_DIR, exist_ok=True)
# Supported markets for calibration
SUPPORTED_MARKETS = [
"ms", # Match Result (1X2) - multi-class, calibrated per class
"ms_home", # Standard Home win probability
"ms_home_heavy_fav", # Context: home odds <= 1.40
"ms_home_fav", # Context: 1.40 < home odds <= 1.80
"ms_home_balanced", # Context: 1.80 < home odds <= 2.50
"ms_home_underdog", # Context: home odds > 2.50
"ms_draw", # Draw probability
"ms_away", # Away win probability
"ou15", # Over/Under 1.5
"ou25", # Over/Under 2.5
"ou35", # Over/Under 3.5
"btts", # Both Teams to Score
"ht_ft", # Half-Time/Full-Time
"dc", # Double Chance
"ht", # Half-Time Result
"ht_home", # Half-Time Home win
"ht_draw", # Half-Time Draw
"ht_away", # Half-Time Away win
]
class CalibrationMetrics:
"""Stores calibration quality metrics for a market."""
def __init__(self):
self.brier_score: float = 0.0
self.calibration_error: float = 0.0
self.sample_count: int = 0
self.last_trained: str = ""
self.mean_predicted: float = 0.0
self.mean_actual: float = 0.0
def to_dict(self) -> Dict:
return {
"brier_score": round(self.brier_score, 4),
"calibration_error": round(self.calibration_error, 4),
"sample_count": self.sample_count,
"last_trained": self.last_trained,
"mean_predicted": round(self.mean_predicted, 4),
"mean_actual": round(self.mean_actual, 4),
}
class Calibrator:
"""
Probability calibration using Isotonic Regression.
Isotonic Regression is a non-parametric method that fits a piecewise
constant function that is monotonically increasing. It's ideal for
calibrating probabilities because:
1. It preserves ranking (if P(A) > P(B) before, P(A) > P(B) after)
2. It doesn't assume a specific distribution shape
3. It can correct systematic over/under-confidence
Example:
# Before calibration: model predicts 70% but actual win rate is 60%
# After calibration: model predicts 70% → calibrated to 60%
"""
def __init__(self):
self.calibrators: Dict[str, IsotonicRegression] = {}
self.metrics: Dict[str, CalibrationMetrics] = {}
# Less aggressive shrinkage — only meaningful overconfident bands are pulled.
# Default raised from ~0.85-0.90 to 0.95+ since the orchestrator and config
# already apply market-level multipliers; double-shrinkage was the root cause
# of 24-35pt avg calibrated-vs-raw drops in production traces.
self.heuristic_fallback: Dict[str, float] = {
"ms": 0.96,
"ms_home": 0.96,
"ms_home_heavy_fav": 0.98,
"ms_home_fav": 0.96,
"ms_home_balanced": 0.94,
"ms_home_underdog": 0.92,
"ms_draw": 0.94,
"ms_away": 0.96,
"ou15": 0.96,
"ou25": 0.96,
"ou35": 0.94,
"btts": 0.96,
"ht_ft": 0.92,
"dc": 0.97,
"ht": 0.92,
"ht_home": 0.92,
"ht_draw": 0.92,
"ht_away": 0.92,
}
self._load_calibrators()
def _load_calibrators(self):
"""Load trained calibrators for each market from disk."""
for market in SUPPORTED_MARKETS:
model_path = os.path.join(CALIBRATION_DIR, f"{market}_calibrator.pkl")
metrics_path = os.path.join(CALIBRATION_DIR, f"{market}_metrics.json")
if os.path.exists(model_path):
try:
with open(model_path, "rb") as f:
self.calibrators[market] = pickle.load(f)
print(f"[Calibrator] Loaded calibration model for {market}")
except Exception as e:
print(f"[Calibrator] Warning: Failed to load {market}: {e}")
if os.path.exists(metrics_path):
try:
with open(metrics_path, "r") as f:
data = json.load(f)
metrics = CalibrationMetrics()
metrics.brier_score = data.get("brier_score", 0.0)
metrics.calibration_error = data.get("calibration_error", 0.0)
metrics.sample_count = data.get("sample_count", 0)
metrics.last_trained = data.get("last_trained", "")
metrics.mean_predicted = data.get("mean_predicted", 0.0)
metrics.mean_actual = data.get("mean_actual", 0.0)
self.metrics[market] = metrics
except Exception as e:
print(f"[Calibrator] Warning: Failed to load metrics for {market}: {e}")
# Below this sample count, blend isotonic with raw_prob to dampen overfit jumps.
# Above this count, trust isotonic fully.
TRUSTED_SAMPLE_FLOOR = 30
TRUSTED_SAMPLE_CEILING = 200
# Hard cap on how far calibration can move probability in either direction.
MAX_DELTA = 0.20
def calibrate(self, market_type: str, raw_prob: float, odds_val: Optional[float] = None) -> float:
"""
Calibrate a raw probability using Isotonic Regression with safeguards.
Args:
market_type (str): 'ms_home', 'ou25', 'btts', 'ht_ft', etc.
raw_prob (float): The raw probability from XGBoost (0.0 - 1.0)
odds_val (float, optional): The pre-match odds, used for context-aware bucket mapping
Returns:
float: Calibrated probability (0.0 - 1.0)
Safeguards:
* Low-sample trained models are blended with raw_prob to dampen overfit.
* MAX_DELTA caps the per-call adjustment (prevents 40pp swings).
"""
# Normalize market type
market_key = market_type.lower().replace("-", "_")
# Route to bucket if ms_home and odds provided
if market_key == "ms_home" and odds_val is not None and odds_val > 1.0:
if odds_val <= 1.40:
bucket_key = "ms_home_heavy_fav"
elif odds_val <= 1.80:
bucket_key = "ms_home_fav"
elif odds_val <= 2.50:
bucket_key = "ms_home_balanced"
else:
bucket_key = "ms_home_underdog"
if bucket_key in self.calibrators:
market_key = bucket_key
# If we have a trained Isotonic Regression model, use it (with safeguards)
if market_key in self.calibrators:
try:
iso_pred = float(self.calibrators[market_key].predict([raw_prob])[0])
# Sample-count weighted blend with raw probability.
# Sparse models barely move probability; mature models dominate.
metrics = self.metrics.get(market_key)
n_samples = metrics.sample_count if metrics else 0
if n_samples >= self.TRUSTED_SAMPLE_CEILING:
iso_weight = 1.0
elif n_samples <= self.TRUSTED_SAMPLE_FLOOR:
# Very sparse: at least 30% trust to surface the signal
iso_weight = max(0.30, n_samples / self.TRUSTED_SAMPLE_CEILING)
else:
# Linearly ramp 30% → 100% between floor and ceiling
span = self.TRUSTED_SAMPLE_CEILING - self.TRUSTED_SAMPLE_FLOOR
iso_weight = 0.30 + 0.70 * (n_samples - self.TRUSTED_SAMPLE_FLOOR) / span
blended = iso_weight * iso_pred + (1.0 - iso_weight) * raw_prob
# Cap delta to avoid huge swings on noisy calibrators
delta = blended - raw_prob
if delta > self.MAX_DELTA:
blended = raw_prob + self.MAX_DELTA
elif delta < -self.MAX_DELTA:
blended = raw_prob - self.MAX_DELTA
return float(np.clip(blended, 0.01, 0.99))
except Exception as e:
print(f"[Calibrator] Warning: Isotonic failed for {market_key}: {e}")
# Fall through to heuristic
# Fallback to heuristic calibration
return self._heuristic_calibrate(market_key, raw_prob)
def _heuristic_calibrate(self, market_type: str, raw_prob: float) -> float:
"""
Heuristic calibration fallback when no trained model exists.
This applies a conservative shrinkage towards the mean:
- Binary markets (OU, BTTS): shrink towards 0.5
- Multi-class (MS): shrink towards 0.33
- HT/FT: stronger shrinkage due to higher variance
"""
# Get shrinkage factor for this market
shrinkage = self.heuristic_fallback.get(market_type, 0.90)
if market_type in ["ms", "ms_home", "ms_home_heavy_fav", "ms_home_fav", "ms_home_balanced", "ms_home_underdog", "ms_draw", "ms_away"]:
# Pull towards 0.33 (uniform for 3-class)
return (raw_prob * shrinkage) + (0.33 * (1.0 - shrinkage))
elif market_type in ["ou15", "ou25", "ou35", "btts"]:
# Pull towards 0.5 (uniform for binary)
return (raw_prob * shrinkage) + (0.5 * (1.0 - shrinkage))
elif market_type in ["ht_ft", "ht"]:
# Stronger shrinkage for high-variance markets
return raw_prob * shrinkage
elif market_type == "dc":
# Double chance is more reliable
return (raw_prob * shrinkage) + (0.66 * (1.0 - shrinkage))
return raw_prob
def train_calibration(
self,
df: pd.DataFrame,
market: str,
prob_col: str,
actual_col: str,
min_samples: int = 100,
save: bool = True,
) -> CalibrationMetrics:
"""
Train an Isotonic Regression calibration model for a specific market.
Args:
df: DataFrame with predictions and actual outcomes
market: Market identifier (e.g., 'ms_home', 'ou25', 'btts')
prob_col: Column name for raw probabilities
actual_col: Column name for actual outcomes (0 or 1)
min_samples: Minimum samples required to train
save: Whether to save the model to disk
Returns:
CalibrationMetrics with quality metrics
"""
# Filter valid data
valid_df = df[[prob_col, actual_col]].dropna()
n_samples = len(valid_df)
if n_samples < min_samples:
print(f"[Calibrator] Warning: Only {n_samples} samples for {market}, "
f"need at least {min_samples}")
metrics = CalibrationMetrics()
metrics.sample_count = n_samples
return metrics
# Extract arrays
raw_probs = valid_df[prob_col].values
actuals = valid_df[actual_col].values
# Train Isotonic Regression
iso = IsotonicRegression(out_of_bounds="clip", increasing=True)
iso.fit(raw_probs, actuals)
# Calculate calibrated probabilities
calibrated_probs = iso.predict(raw_probs)
# Calculate metrics
metrics = CalibrationMetrics()
metrics.sample_count = n_samples
metrics.last_trained = datetime.utcnow().isoformat()
metrics.brier_score = brier_score_loss(actuals, calibrated_probs)
metrics.mean_predicted = np.mean(raw_probs)
metrics.mean_actual = np.mean(actuals)
# Calculate Expected Calibration Error (ECE)
metrics.calibration_error = self._calculate_ece(
calibrated_probs, actuals, n_bins=10
)
# Store in memory
self.calibrators[market] = iso
self.metrics[market] = metrics
# Save to disk
if save:
self._save_calibration(market, iso, metrics)
print(f"[Calibrator] Trained {market}: "
f"Brier={metrics.brier_score:.4f}, "
f"ECE={metrics.calibration_error:.4f}, "
f"n={n_samples}")
return metrics
def train_all_markets(
self,
df: pd.DataFrame,
market_config: Dict[str, Tuple[str, str]],
min_samples: int = 100,
) -> Dict[str, CalibrationMetrics]:
"""
Train calibration models for multiple markets at once.
Args:
df: DataFrame with all predictions and outcomes
market_config: Dict mapping market -> (prob_col, actual_col)
e.g., {'ou25': ('ou25_over_prob', 'ou25_over_actual')}
min_samples: Minimum samples per market
Returns:
Dict of market -> CalibrationMetrics
"""
results = {}
for market, (prob_col, actual_col) in market_config.items():
print(f"\n[Calibrator] Training {market}...")
try:
metrics = self.train_calibration(
df=df,
market=market,
prob_col=prob_col,
actual_col=actual_col,
min_samples=min_samples,
save=True,
)
results[market] = metrics
except Exception as e:
print(f"[Calibrator] Failed to train {market}: {e}")
return results
def _calculate_ece(
self,
probs: np.ndarray,
actuals: np.ndarray,
n_bins: int = 10
) -> float:
"""
Calculate Expected Calibration Error (ECE).
ECE = sum(|bin_accuracy - bin_confidence| * bin_weight)
Lower is better. Perfect calibration = 0.
"""
bin_boundaries = np.linspace(0, 1, n_bins + 1)
ece = 0.0
for i in range(n_bins):
in_bin = (probs >= bin_boundaries[i]) & (probs < bin_boundaries[i + 1])
prop_in_bin = np.mean(in_bin)
if prop_in_bin > 0:
accuracy_in_bin = np.mean(actuals[in_bin])
avg_confidence_in_bin = np.mean(probs[in_bin])
ece += np.abs(accuracy_in_bin - avg_confidence_in_bin) * prop_in_bin
return ece
def _save_calibration(
self,
market: str,
calibrator: IsotonicRegression,
metrics: CalibrationMetrics
):
"""Save calibration model and metrics to disk."""
# Save model
model_path = os.path.join(CALIBRATION_DIR, f"{market}_calibrator.pkl")
with open(model_path, "wb") as f:
pickle.dump(calibrator, f)
# Save metrics
metrics_path = os.path.join(CALIBRATION_DIR, f"{market}_metrics.json")
with open(metrics_path, "w") as f:
json.dump(metrics.to_dict(), f, indent=2)
print(f"[Calibrator] Saved {market} to {CALIBRATION_DIR}")
def get_calibration_report(self) -> Dict[str, Any]:
"""Generate a summary report of all calibration models."""
report = {
"trained_markets": list(self.calibrators.keys()),
"metrics": {},
"heuristic_only": [],
}
for market in SUPPORTED_MARKETS:
if market in self.metrics:
report["metrics"][market] = self.metrics[market].to_dict()
elif market not in self.calibrators:
report["heuristic_only"].append(market)
return report
def get_calibrated_probabilities(
self,
market: str,
raw_probs: np.ndarray
) -> np.ndarray:
"""
Batch calibration for array of probabilities.
Args:
market: Market type
raw_probs: Array of raw probabilities
Returns:
Array of calibrated probabilities
"""
return np.array([self.calibrate(market, p) for p in raw_probs])
# Singleton instance
_calibrator_instance: Optional[Calibrator] = None
def get_calibrator() -> Calibrator:
"""Get or create the global Calibrator instance."""
global _calibrator_instance
if _calibrator_instance is None:
_calibrator_instance = Calibrator()
return _calibrator_instance
+191
View File
@@ -0,0 +1,191 @@
"""
League-Specific Model Loader
=============================
Loads per-league XGBoost models + isotonic calibrators trained by
scripts/train_league_models.py and provides a unified prediction interface.
Falls back to general V25 for any market/league without a dedicated model.
"""
import os
import json
import pickle
from functools import lru_cache
from typing import Dict, Optional, Tuple
import numpy as np
import pandas as pd
import xgboost as xgb
AI_ENGINE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LEAGUE_MODEL_DIR = os.path.join(AI_ENGINE_DIR, "models", "league_specific")
# Market file name → (num_class, label_list)
MARKET_META: Dict[str, Tuple[int, list]] = {
"ms": (3, ["1", "X", "2"]),
"ou15": (2, ["Over", "Under"]),
"ou25": (2, ["Over", "Under"]),
"ou35": (2, ["Over", "Under"]),
"btts": (2, ["Yes", "No"]),
"ht": (3, ["1", "X", "2"]),
"ht_ou05": (2, ["Over", "Under"]),
"ht_ou15": (2, ["Over", "Under"]),
"htft": (9, ["1/1","1/X","1/2","X/1","X/X","X/2","2/1","2/X","2/2"]),
"oe": (2, ["Odd", "Even"]),
"cards": (2, ["Over", "Under"]),
"handicap": (3, ["1", "X", "2"]),
}
# Signal key map (file key → uppercase signal key used in _get_v25_signal)
FILE_TO_SIGNAL = {
"ms": "MS", "ou15": "OU15", "ou25": "OU25", "ou35": "OU35",
"btts": "BTTS", "ht": "HT", "ht_ou05": "HT_OU05", "ht_ou15": "HT_OU15",
"htft": "HTFT", "oe": "OE", "cards": "CARDS", "handicap": "HCAP",
}
class LeagueModel:
"""Holds XGBoost models + isotonic calibrators for one league."""
def __init__(self, league_id: str):
self.league_id = league_id
self.league_dir = os.path.join(LEAGUE_MODEL_DIR, league_id)
self.models: Dict[str, xgb.Booster] = {} # market_key → booster
self.calibrators: Dict[str, object] = {} # cal_key → isotonic
self.feature_cols: Optional[list] = None
self._loaded = False
def load(self) -> bool:
if not os.path.isdir(self.league_dir):
return False
try:
fc_path = os.path.join(self.league_dir, "feature_cols.json")
if os.path.exists(fc_path):
with open(fc_path) as f:
self.feature_cols = json.load(f)
for mkey in MARKET_META:
xgb_path = os.path.join(self.league_dir, f"xgb_{mkey}.json")
if os.path.exists(xgb_path) and os.path.getsize(xgb_path) > 100:
b = xgb.Booster()
b.load_model(xgb_path)
self.models[mkey] = b
for fname in os.listdir(self.league_dir):
if fname.startswith("cal_") and fname.endswith(".pkl"):
cal_key = fname[4:-4] # strip cal_ and .pkl
with open(os.path.join(self.league_dir, fname), "rb") as f:
self.calibrators[cal_key] = pickle.load(f)
self._loaded = bool(self.models or self.calibrators)
return self._loaded
except Exception as e:
print(f"[LeagueModel] Load failed for {self.league_id}: {e}")
return False
def has_market(self, mkey: str) -> bool:
return mkey in self.models
def predict_market(
self,
mkey: str,
feature_row: Dict[str, float],
) -> Optional[Dict[str, float]]:
"""
Predict one market using league-specific XGBoost + isotonic calibration.
Returns {label: prob} dict or None if no model available.
"""
if mkey not in self.models:
return None
num_class, labels = MARKET_META[mkey]
fc = self.feature_cols
if fc is None:
# Fallback to whatever the booster expects (it knows its feature names)
fc = list(self.models[mkey].feature_names or [])
try:
X = pd.DataFrame([{col: feature_row.get(col, 0.0) for col in fc}])
dmat = xgb.DMatrix(X)
raw = self.models[mkey].predict(dmat)
if num_class > 2:
probs_arr = raw.reshape(-1, num_class)[0]
probs = {labels[i]: float(probs_arr[i]) for i in range(num_class)}
# Apply isotonic calibration per class
cal_total = 0.0
for i, label in enumerate(labels):
cal_key = f"{mkey}_{i}"
if cal_key in self.calibrators:
p_cal = float(self.calibrators[cal_key].predict([probs_arr[i]])[0])
probs[label] = max(0.01, min(0.99, p_cal))
cal_total += probs[label]
if cal_total > 0:
probs = {k: v / cal_total for k, v in probs.items()}
else:
p = float(raw[0])
cal_key = mkey
if cal_key in self.calibrators:
p = float(self.calibrators[cal_key].predict([p])[0])
p = max(0.01, min(0.99, p))
probs = {labels[0]: p, labels[1]: 1.0 - p}
return probs
except Exception as e:
print(f"[LeagueModel] predict_market({mkey}) failed for {self.league_id}: {e}")
return None
class LeagueModelLoader:
"""
In-memory cache for league-specific models.
Thread-safe for single-process async servers (FastAPI/uvicorn).
"""
def __init__(self, max_cached: int = 80):
self._cache: Dict[str, Optional[LeagueModel]] = {}
self._max_cached = max_cached
def get(self, league_id: str) -> Optional[LeagueModel]:
"""Return loaded LeagueModel for this league, or None if unavailable."""
if league_id in self._cache:
return self._cache[league_id]
# Evict oldest entry if cache is full
if len(self._cache) >= self._max_cached:
oldest = next(iter(self._cache))
del self._cache[oldest]
model = LeagueModel(league_id)
loaded = model.load()
self._cache[league_id] = model if loaded else None
if loaded:
n_models = len(model.models)
n_cals = len(model.calibrators)
print(f"[LeagueModel] Loaded {league_id}: {n_models} XGB models, {n_cals} calibrators")
return self._cache[league_id]
def available_leagues(self) -> list:
if not os.path.isdir(LEAGUE_MODEL_DIR):
return []
return [d for d in os.listdir(LEAGUE_MODEL_DIR)
if os.path.isdir(os.path.join(LEAGUE_MODEL_DIR, d))]
def readiness_summary(self) -> dict:
leagues = self.available_leagues()
return {
"league_specific_dir": LEAGUE_MODEL_DIR,
"available_leagues": len(leagues),
"cached": len([v for v in self._cache.values() if v is not None]),
}
# ── Singleton ──────────────────────────────────────────────────────
_loader: Optional[LeagueModelLoader] = None
def get_league_model_loader() -> LeagueModelLoader:
global _loader
if _loader is None:
_loader = LeagueModelLoader()
return _loader
+154
View File
@@ -0,0 +1,154 @@
[
"home_overall_elo",
"away_overall_elo",
"elo_diff",
"home_home_elo",
"away_away_elo",
"home_form_elo",
"away_form_elo",
"form_elo_diff",
"home_goals_avg",
"home_conceded_avg",
"away_goals_avg",
"away_conceded_avg",
"home_clean_sheet_rate",
"away_clean_sheet_rate",
"home_scoring_rate",
"away_scoring_rate",
"home_winning_streak",
"away_winning_streak",
"home_unbeaten_streak",
"away_unbeaten_streak",
"h2h_total_matches",
"h2h_home_win_rate",
"h2h_draw_rate",
"h2h_avg_goals",
"h2h_btts_rate",
"h2h_over25_rate",
"home_avg_possession",
"away_avg_possession",
"home_avg_shots_on_target",
"away_avg_shots_on_target",
"home_shot_conversion",
"away_shot_conversion",
"home_avg_corners",
"away_avg_corners",
"odds_ms_h",
"odds_ms_d",
"odds_ms_a",
"implied_home",
"implied_draw",
"implied_away",
"odds_ht_ms_h",
"odds_ht_ms_d",
"odds_ht_ms_a",
"odds_ou05_o",
"odds_ou05_u",
"odds_ou15_o",
"odds_ou15_u",
"odds_ou25_o",
"odds_ou25_u",
"odds_ou35_o",
"odds_ou35_u",
"odds_ht_ou05_o",
"odds_ht_ou05_u",
"odds_ht_ou15_o",
"odds_ht_ou15_u",
"odds_btts_y",
"odds_btts_n",
"odds_ms_h_present",
"odds_ms_d_present",
"odds_ms_a_present",
"odds_ht_ms_h_present",
"odds_ht_ms_d_present",
"odds_ht_ms_a_present",
"odds_ou05_o_present",
"odds_ou05_u_present",
"odds_ou15_o_present",
"odds_ou15_u_present",
"odds_ou25_o_present",
"odds_ou25_u_present",
"odds_ou35_o_present",
"odds_ou35_u_present",
"odds_ht_ou05_o_present",
"odds_ht_ou05_u_present",
"odds_ht_ou15_o_present",
"odds_ht_ou15_u_present",
"odds_btts_y_present",
"odds_btts_n_present",
"home_xga",
"away_xga",
"league_avg_goals",
"league_zero_goal_rate",
"upset_atmosphere",
"upset_motivation",
"upset_fatigue",
"upset_potential",
"referee_home_bias",
"referee_avg_goals",
"referee_cards_total",
"referee_avg_yellow",
"referee_experience",
"home_momentum_score",
"away_momentum_score",
"momentum_diff",
"home_squad_quality",
"away_squad_quality",
"squad_diff",
"home_key_players",
"away_key_players",
"home_missing_impact",
"away_missing_impact",
"home_goals_form",
"away_goals_form",
"home_lineup_goals_per90",
"away_lineup_goals_per90",
"home_lineup_assists_per90",
"away_lineup_assists_per90",
"home_squad_continuity",
"away_squad_continuity",
"home_top_scorer_form",
"away_top_scorer_form",
"home_avg_player_exp",
"away_avg_player_exp",
"home_goals_diversity",
"away_goals_diversity",
"h2h_home_goals_avg",
"h2h_away_goals_avg",
"h2h_recent_trend",
"h2h_venue_advantage",
"home_rolling5_goals",
"home_rolling5_conceded",
"home_rolling10_goals",
"home_rolling10_conceded",
"home_rolling20_goals",
"home_rolling20_conceded",
"away_rolling5_goals",
"away_rolling5_conceded",
"away_rolling10_goals",
"away_rolling10_conceded",
"home_rolling5_cs",
"away_rolling5_cs",
"home_venue_goals",
"home_venue_conceded",
"away_venue_goals",
"away_venue_conceded",
"home_goal_trend",
"away_goal_trend",
"home_days_rest",
"away_days_rest",
"match_month",
"is_season_start",
"is_season_end",
"attack_vs_defense_home",
"attack_vs_defense_away",
"xg_diff",
"form_momentum_interaction",
"elo_form_consistency",
"upset_x_elo_gap",
"league_home_win_rate",
"league_draw_rate",
"league_btts_rate",
"league_ou25_rate",
"league_reliability_score"
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+891
View File
@@ -0,0 +1,891 @@
tree
version=v4
num_class=1
num_tree_per_iteration=1
label_index=0
max_feature_idx=151
objective=binary sigmoid:1
feature_names=Column_0 Column_1 Column_2 Column_3 Column_4 Column_5 Column_6 Column_7 Column_8 Column_9 Column_10 Column_11 Column_12 Column_13 Column_14 Column_15 Column_16 Column_17 Column_18 Column_19 Column_20 Column_21 Column_22 Column_23 Column_24 Column_25 Column_26 Column_27 Column_28 Column_29 Column_30 Column_31 Column_32 Column_33 Column_34 Column_35 Column_36 Column_37 Column_38 Column_39 Column_40 Column_41 Column_42 Column_43 Column_44 Column_45 Column_46 Column_47 Column_48 Column_49 Column_50 Column_51 Column_52 Column_53 Column_54 Column_55 Column_56 Column_57 Column_58 Column_59 Column_60 Column_61 Column_62 Column_63 Column_64 Column_65 Column_66 Column_67 Column_68 Column_69 Column_70 Column_71 Column_72 Column_73 Column_74 Column_75 Column_76 Column_77 Column_78 Column_79 Column_80 Column_81 Column_82 Column_83 Column_84 Column_85 Column_86 Column_87 Column_88 Column_89 Column_90 Column_91 Column_92 Column_93 Column_94 Column_95 Column_96 Column_97 Column_98 Column_99 Column_100 Column_101 Column_102 Column_103 Column_104 Column_105 Column_106 Column_107 Column_108 Column_109 Column_110 Column_111 Column_112 Column_113 Column_114 Column_115 Column_116 Column_117 Column_118 Column_119 Column_120 Column_121 Column_122 Column_123 Column_124 Column_125 Column_126 Column_127 Column_128 Column_129 Column_130 Column_131 Column_132 Column_133 Column_134 Column_135 Column_136 Column_137 Column_138 Column_139 Column_140 Column_141 Column_142 Column_143 Column_144 Column_145 Column_146 Column_147 Column_148 Column_149 Column_150 Column_151
feature_infos=[1150.3663761896189:1903.4781806887747] [1158.5088961211511:1916.84579108047] [-496.81477567713546:573.10259120534784] [1159.8767670517543:1884.5959848901657] [1151.7894548779084:1919.4116678360419] [1426.1496448360797:1585.9817930954068] [1427.9817118206745:1588.9895054335384] [-113.02538114266532:114.69704651598067] [0:5.9333333333333336] [0:6.2666666666666666] [0:6.0666666666666664] [0:5.2000000000000002] [0:1] [0:1] [0:1] [0:1] [0:5] [0:5] [0:5] [0:5] [0:8] [0:1] [0:1] [0:11] [0:1] [0:1] [0.20999999999999999:0.81000000000000005] [0.22500000000000001:0.76000000000000001] [0:13] [0:13] [0:6.333333333333333] [0:6.666666666666667] [0:4.5] [0:4.5] [0:22.550000000000001] [0:17.5] [0:35.5] [0.065285302506677897:0.80962131918207236] [0.1044438556117265:0.4288719106684401] [0.056651501780225801:0.79902050363656307] [0:26] [0:5.0899999999999999] [0:26.5] [0:1.0900000000000001] [0:13.050000000000001] [0:1.76] [0:13.25] [0:3.7400000000000002] [0:7.6500000000000004] [0:8.5600000000000005] [0:3.5299999999999998] [0:1.72] [0:7.3300000000000001] [0:5.0199999999999996] [0:2.5800000000000001] [0:3.77] [0:4.1299999999999999] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:1] [0:6.2666666666666666] [0:5.2000000000000002] [2.0046118370484245:4.1840324763193504] [0.018042399639151999:0.1552651806302843] [0:0.45000000000000001] none none [0:0.1575] [-0.92000000000000004:1] [0:7] none none [0:1] [-1:0.61250000000000004] [-1:0.59166666666666667] [-1.2908333333333333:1.3799999999999999] [0:40.799999999999997] [0:36.299999999999997] [-29.999999999999996:31.499999999999996] [0:10] [0:10] [0:1] [0:1] [0:5.7999999999999998] [0:5] [0:5] [0:6.25] [0:4] [0:5] [0:1] [0:1] [0:10] [0:11] [0:42.100000000000001] [0:41.799999999999997] [0:1] [0:1] [0:8] [0:8] [-1:1] [0:1] [0:5.7999999999999998] [0:6.5] [0:5.2000000000000002] [0:6.5] [0:4.4119999999999999] [0:6.5] [0:5.3330000000000002] [0:4.7999999999999998] [0:5.3330000000000002] [0:4.5] [0:1] [0:1] [0:7] [0:8] [0:6] [0:7] [-1.8:1.8] [-2.2000000000000002:2] [1:30] [1.2:30] [1:12] [0:1] [0:1] [-4.4669999999999996:4.5999999999999996] [-5.2000000000000002:5] [-4.133:5.3330000000000002] [-0.045499999999999999:0.088099999999999998] [0.063600000000000004:1] [0:0.088800000000000004] [0.35623409669211198:0.51556156968876865] [0.1051136363636363:0.32744043043812449] [0.40430438124519602:0.64185836716283262] [0.32129131437355879:0.77537212449255755] [0.70399999999999996:1]
tree_sizes=946 1005 993 986 1007 997 994 1005 992 1009 984 1007 997 684 1002 999 897 992 991 990 1001 905 1003 995 896 1010 908 1005 1007 1014 1012 1005 1005 691 688
Tree=0
num_leaves=8
num_cat=0
split_feature=45 54 46 96 4 3 16
split_gain=174.584 35.5641 24.9346 16.8552 14.3832 11.0732 10.8094
threshold=1.155 1.405 2.5850000000000004 4.5000000000000009 1704.4687685416313 1441.3975280143418 1.0000000180025095e-35
decision_type=2 2 2 2 2 2 2
left_child=1 3 5 -1 -4 -2 -3
right_child=2 6 4 -5 -6 -7 -8
leaf_value=0.87683759709368503 0.84782001969484888 0.90404985782878589 0.85793883408869098 0.90097077069702014 0.73127673195863474 0.81008235044809496 0.93515098554525133
leaf_weight=939.87957760691643 113.51603642106056 283.89385342597961 418.36988925933838 472.32632339000702 9.9611878395080549 330.17187193036079 212.09029108285904
leaf_count=4529 547 1368 2016 2276 48 1591 1022
internal_value=0.875686 0.893342 0.837052 0.884909 0.85499 0.819737 0.91735
internal_weight=2780.21 1908.19 872.019 1412.21 428.331 443.688 495.984
internal_count=13397 9195 4202 6805 2064 2138 2390
is_linear=0
shrinkage=1
Tree=1
num_leaves=8
num_cat=0
split_feature=47 48 49 86 141 133 150
split_gain=140.072 28.5634 26.9673 23.4898 19.0464 14.6224 10.7313
threshold=1.6850000000000003 2.1650000000000005 3.3850000000000002 3.3279569892473124 1.2620000000000002 0.31650000000000006 0.48538789856891795
decision_type=2 2 2 2 2 2 2
left_child=1 3 5 -1 -4 -2 -3
right_child=2 6 4 -5 -6 -7 -8
leaf_value=0.0068305934170468773 -0.15031955758812821 -0.031384052219233315 -0.052289652303345355 0.062911487393225371 0.031971580220150592 -0.011449057668411599 0.04709896679065162
leaf_weight=1284.6507234424353 8.3659095764160138 19.989385187625885 501.71516834199429 86.587033972144127 30.929726287722588 495.32643516361713 353.17835873365402
leaf_count=6221 40 98 2370 419 146 2373 1730
internal_value=-0.000699774 0.0173296 -0.0310475 0.0103722 -0.0473963 -0.0137584 0.0428942
internal_weight=2780.74 1744.41 1036.34 1371.24 532.645 503.692 373.168
internal_count=13397 8468 4929 6640 2516 2413 1828
is_linear=0
shrinkage=0.104222
Tree=2
num_leaves=8
num_cat=0
split_feature=45 50 53 39 96 28 150
split_gain=110.988 23.3948 18.1465 13.3271 12.0676 11.8858 11.7377
threshold=1.155 1.4150000000000003 3.3250000000000006 0.36187197152743827 4.5000000000000009 3.9500000000000006 0.48538789856891795
decision_type=2 2 2 2 2 2 2
left_child=1 4 5 -4 -1 -2 -3
right_child=2 6 3 -5 -6 -7 -8
leaf_value=0.0012888166907150367 -0.037269165059036741 -0.040251887739062138 -0.05134333704385069 -0.13896201271441588 0.020859848952961575 -0.011322757210899898 0.041785901483228478
leaf_weight=991.85861267149448 395.00907251238823 20.026126876473427 106.85419258475304 22.895305529236794 522.50144763290882 372.75306884944439 348.38899676501751
leaf_count=4802 1851 97 494 106 2555 1751 1741
internal_value=-0.000605268 0.0137719 -0.0307651 -0.0668133 0.00804152 -0.0246723 0.0373256
internal_weight=2780.29 1882.78 897.512 129.749 1514.36 767.762 368.415
internal_count=13397 9195 4202 600 7357 3602 1838
is_linear=0
shrinkage=0.104222
Tree=3
num_leaves=8
num_cat=0
split_feature=45 48 86 11 53 144 104
split_gain=91.2855 30.6398 20.2224 17.4942 14.4128 10.3549 10.2483
threshold=1.155 2.2850000000000006 3.3279569892473124 0.30952380952380959 3.4950000000000006 0.0075500000000000003 1.0005000000000002
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 6 -6 -2
right_child=4 3 -4 -5 5 -7 -8
leaf_value=0.0040069597334026 -0.027086096902533729 -0.1715719381102207 0.053726932967164444 0.048773966775503941 -0.061900987427835959 -0.23396750965062757 0.016338062312498316
leaf_weight=1510.6300098896027 782.38381478190422 3.9652889966964713 94.400425717234612 257.22131448984146 73.248439565300941 3.9991354644298545 63.84617380797863
leaf_count=7369 3628 20 471 1306 331 18 297
internal_value=-0.000943352 0.0123188 0.00693156 0.0454226 -0.0277441 -0.0708364 -0.0238097
internal_weight=2789.69 1866.22 1605.03 261.187 923.478 77.2476 846.23
internal_count=13440 9166 7840 1326 4274 349 3925
is_linear=0
shrinkage=0.104222
Tree=4
num_leaves=8
num_cat=0
split_feature=49 35 6 53 44 126 38
split_gain=70.6071 28.8927 21.165 13.8246 12.9797 16.2351 8.96833
threshold=2.8350000000000004 3.3550000000000004 1541.1282734213053 1.9650000000000001 6.5150000000000006 1.8450000000000002 0.27603289214361998
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 5 -2 -6
right_child=4 3 -4 -5 6 -7 -8
leaf_value=-0.0027517198054468608 -0.029319791370752923 0.043695199840511914 0.083541694807877556 0.015347179328011443 -0.028790250204289367 -0.099217570104222469 -0.0022072689999685109
leaf_weight=937.29022094607353 417.85833202302456 320.03798474371433 31.915322333574295 449.0833810120821 218.10735833644867 39.50090055167675 374.63410261273384
leaf_count=4582 1898 1642 157 2211 1027 181 1742
internal_value=-0.000829778 0.0120602 9.06525e-05 0.0271434 -0.0221677 -0.0353586 -0.0119891
internal_weight=2788.43 1738.33 969.206 769.121 1050.1 457.359 592.741
internal_count=13440 8592 4739 3853 4848 2079 2769
is_linear=0
shrinkage=0.104222
Tree=5
num_leaves=8
num_cat=0
split_feature=51 50 35 143 44 147 11
split_gain=63.712 19.0005 13.7227 11.8206 10.6018 10.4763 6.84163
threshold=1.2550000000000001 1.4650000000000001 5.035000000000001 -1.7974999999999997 7.2550000000000008 0.47199397614381194 2.6904761904761911
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 6 -5 -3 -2
right_child=3 5 -4 4 -6 -7 -8
leaf_value=0.0047544573525090221 -0.065940592898403594 0.046851958603355899 0.063639968219378742 -0.028402060075216558 -0.0037183287940315865 -0.010933729592520421 -0.2419531318087022
leaf_weight=1547.0932241082191 6.1367039680480939 207.7856714874506 44.20663620531559 674.41967558860779 262.60369572043419 40.760551080107689 3.9397892653942108
leaf_count=7584 28 1099 222 3053 1221 215 18
internal_value=-0.000729499 0.0105766 0.00639068 -0.0226927 -0.0214845 0.0373753 -0.134921
internal_weight=2786.95 1839.85 1591.3 947.1 937.023 248.546 10.0765
internal_count=13440 9120 7806 4320 4274 1314 46
is_linear=0
shrinkage=0.104222
Tree=6
num_leaves=8
num_cat=0
split_feature=41 130 54 48 99 34 105
split_gain=42.6482 15.4832 11.8244 11.5616 10.5481 8.08864 5.28375
threshold=1.9950000000000003 2.1835000000000004 1.6950000000000001 2.0250000000000004 0.13392857142857142 2.1550000000000007 1.0765000000000002
decision_type=2 2 2 2 2 2 2
left_child=1 3 4 -1 -2 -3 -4
right_child=2 5 6 -5 -6 -7 -8
leaf_value=-0.012930103603477631 -0.025837088447404372 0.009985288944886676 0.093598551064641447 0.0827918792790409 0.017739499999769957 0.067684395437451708 -0.011462427181900188
leaf_weight=1452.7631230950356 63.837418377399445 116.17519751191139 30.260349631309509 13.827319353818892 1098.0612086355686 34.140560820698738 6.2758191227912894
leaf_count=6763 321 560 163 70 5525 162 34
internal_value=0.00228156 -0.00876197 0.0171813 -0.0120271 0.0153451 0.0230937 0.0755539
internal_weight=2815.34 1616.91 1198.43 1466.59 1161.9 150.316 36.5362
internal_count=13598 7555 6043 6833 5846 722 197
is_linear=0
shrinkage=0.104222
Tree=7
num_leaves=8
num_cat=0
split_feature=41 135 130 21 53 102 16
split_gain=34.421 15.4189 13.9177 13.4109 11.0563 13.2179 9.32957
threshold=1.9950000000000003 -0.78349999999999997 2.1340000000000003 0.4642857142857143 2.0250000000000004 2.1120000000000005 1.5000000000000002
decision_type=2 2 2 2 2 2 2
left_child=1 3 -3 -1 6 -6 -2
right_child=4 2 -4 -5 5 -7 -8
leaf_value=-0.039029586512136658 0.021256889774663217 -0.0093694924607743823 0.024219953555788796 -0.18450744298912586 0.010656362766355395 -0.067587490182026186 0.059808579820180507
leaf_weight=29.468837857246399 324.64188092947006 1435.6618839651346 147.77664601802826 8.9744612574577314 753.81818389892578 24.195777088403702 86.313260063529015
leaf_count=138 1721 6659 716 42 3733 122 467
internal_value=0.00205658 -0.00781819 -0.00623458 -0.0730278 0.0155268 0.00822224 0.0293551
internal_weight=2810.85 1621.88 1583.44 38.4433 1188.97 778.014 410.955
internal_count=13598 7555 7375 180 6043 3855 2188
is_linear=0
shrinkage=0.104222
Tree=8
num_leaves=8
num_cat=0
split_feature=86 41 30 92 147 8 85
split_gain=36.407 25.9777 15.256 11.9724 10.8984 10.2187 7.72256
threshold=3.2679487179487183 1.8750000000000002 0.46841755319148942 -0.76249999999999984 0.40846280364372473 1.1083333333333336 -0.021724137931034445
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 5 -2 -6
right_child=4 3 -4 -5 6 -7 -8
leaf_value=-0.012159457825842523 0.064307005786210347 -0.085506751396820332 -0.10592968794102031 0.0081406001340509019 0.027805156010338752 -0.1311253894779951 0.074917050140623179
leaf_weight=1030.2416722476482 5.0686567574739438 14.960604444146155 19.18836584687233 1572.8768468499184 63.189212292432785 6.7883874922990799 94.08995346724987
leaf_count=4724 25 75 92 7835 328 34 485
internal_value=0.00185481 -0.00115146 -0.0138749 0.00725775 0.0487278 -0.0475971 0.055992
internal_weight=2806.4 2637.27 1049.43 1587.84 169.136 11.857 157.279
internal_count=13598 12726 4816 7910 872 59 813
is_linear=0
shrinkage=0.104222
Tree=9
num_leaves=8
num_cat=0
split_feature=49 150 52 133 30 91 30
split_gain=26.3296 13.6848 11.0511 10.5586 10.2017 14.0051 13.9274
threshold=2.7850000000000006 0.60942003008724055 2.9650000000000003 2.7320000000000007 0.10822072072072071 -0.0024999999999999497 0.12681311751103638
decision_type=2 2 2 2 2 2 2
left_child=1 3 -3 -1 5 -2 -6
right_child=4 2 -4 -5 6 -7 -8
leaf_value=0.0056197323214787877 0.0092359089138059399 0.013793895571876906 0.055269467591862222 -0.056030521488072138 0.020769131467870516 -0.044189842169712931 -0.012941111969465513
leaf_weight=1335.4049420952797 73.188210651278496 194.0794630497694 108.95535556972027 30.86374768614769 165.2346598058939 196.06340071558952 684.92604845762253
leaf_count=6712 329 987 589 157 747 879 3130
internal_value=0.000378046 0.00867093 0.0287076 0.00422669 -0.0119885 -0.0296678 -0.00638913
internal_weight=2788.72 1669.3 303.035 1366.27 1119.41 269.252 850.161
internal_count=13530 8445 1576 6869 5085 1208 3877
is_linear=0
shrinkage=0.104222
Tree=10
num_leaves=8
num_cat=0
split_feature=86 41 0 6 110 102 53
split_gain=38.5149 20.3558 13.2957 10.1636 9.93058 14.9217 6.02783
threshold=3.0790020790020796 1.925 1413.5914823844723 1484.8073996434571 2.9500000000000006 0.65050000000000019 2.2150000000000003
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 6 -6 -2
right_child=4 3 -4 -5 5 -7 -8
leaf_value=0.0140954684441122 0.085154679849317738 0.026410531501952182 -0.017501548280296479 0.0014621478522681489 -0.046636790759956186 0.037447698249607933 0.031794502065436499
leaf_weight=167.52074213325977 60.752241030335426 211.7988056242466 1059.5275938510895 1090.8869259208441 27.780392900109291 130.96362222731113 37.02421498298645
leaf_count=765 323 1076 4848 5512 140 685 181
internal_value=0.000347101 -0.00355495 -0.0131877 0.00551857 0.0388267 0.0227309 0.0649542
internal_weight=2786.25 2529.73 1227.05 1302.69 256.52 158.744 97.7765
internal_count=13530 12201 5613 6588 1329 825 504
is_linear=0
shrinkage=0.104222
Tree=11
num_leaves=8
num_cat=0
split_feature=86 122 0 132 136 148 145
split_gain=31.5628 19.7563 17.5348 15.2483 8.699 11.6526 8.53686
threshold=3.0790020790020796 1.0250000000000001 1444.5595683182339 0.68350000000000011 11.950000000000001 0.25070180469638537 0.63955000000000006
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 6 -6 -2
right_child=4 3 -4 -5 5 -7 -8
leaf_value=-0.0029563172992086786 0.032749371502154893 -0.027078470578097272 -0.04142343191974563 0.004355282116605525 0.042836068356502124 -0.043229223026257771 0.082015555649337976
leaf_weight=268.76511310040951 128.29142379760742 184.50364246964455 247.01660700142384 1831.7398000508547 37.845243006944656 31.138597756624222 54.413731098175049
leaf_count=1259 680 882 1128 8932 205 158 286
internal_value=0.000321355 -0.0031776 -0.0213793 0.00147871 0.0355201 0.00398648 0.0474254
internal_weight=2783.71 2532.03 515.782 2016.24 251.689 68.9838 182.705
internal_count=13530 12201 2387 9814 1329 363 966
is_linear=0
shrinkage=0.104222
Tree=12
num_leaves=8
num_cat=0
split_feature=86 41 94 4 100 132 111
split_gain=16.7583 14.9692 11.4051 9.36529 9.15568 6.8883 6.86331
threshold=3.2679487179487183 2.5250000000000004 19.050000000000004 1454.7040164610194 2.1000000000000005 2.5855000000000006 1.6500000000000001
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 4 -2 -3 -5
right_child=3 5 -4 6 -6 -7 -8
leaf_value=-0.0077031612935488866 -0.037300665068035477 0.045270021664037818 0.0069515620157151858 0.08783804546768309 0.1145092464596736 -0.063294467679354607 0.032433508236858831
leaf_weight=1642.7547204941511 28.476281866431236 95.693837076425552 889.01127083599567 33.729012683033943 5.0763863474130622 6.7911695241928092 86.767844557762146
leaf_count=7824 156 540 4317 188 29 38 478
internal_value=0.000977474 -0.000976496 -0.00255725 0.0343884 -0.0143067 0.0380708 0.0479478
internal_weight=2788.3 2634.25 2531.77 154.05 33.5527 102.485 120.497
internal_count=13570 12719 12141 851 185 578 666
is_linear=0
shrinkage=0.104222
Tree=13
num_leaves=5
num_cat=0
split_feature=49 91 105 52
split_gain=12.5399 11.9283 13.2239 11.4266
threshold=6.7950000000000008 0.46333333333333332 0.66550000000000009 3.1450000000000005
decision_type=2 2 2 2
left_child=1 3 -3 -1
right_child=-2 2 -4 -5
leaf_value=-0.00096173862485333158 -0.18852641170062942 -0.26944729151735436 0.0083924991906715318 0.019698628791158867
leaf_weight=2444.542382568121 3.7820855826139441 3.7145474255084983 3.7179097086191177 330.02280321717262
leaf_count=11674 16 18 18 1844
internal_value=0.000884212 0.00114235 -0.13063 0.00149578
internal_weight=2785.78 2782 7.43246 2774.57
internal_count=13570 13554 36 13518
is_linear=0
shrinkage=0.104222
Tree=14
num_leaves=8
num_cat=0
split_feature=35 136 1 3 120 119 137
split_gain=12.9995 12.1396 9.67922 10.2069 8.87434 8.76233 7.82142
threshold=3.4050000000000007 3.1500000000000008 1408.1043210310934 1436.2136975970254 1.4145000000000001 0.55000000000000016 12.850000000000003
decision_type=2 2 2 2 2 2 2
left_child=1 5 3 -2 -3 -1 -4
right_child=2 4 6 -5 -6 -7 -8
leaf_value=-0.065982224130305259 -0.028897601514020019 -0.010800039930687538 0.014598348000302748 0.051779760894765055 0.0040416852382515198 0.035952394418601007 -0.011457004864636652
leaf_weight=9.93890532851219 20.277691304683685 1238.979572609067 441.32176646590233 106.29307742416859 676.56466819345951 115.69292894005775 174.66667002439499
leaf_count=46 107 5755 2341 568 3282 548 923
internal_value=0.000796137 -0.00349957 0.0126042 0.0388534 -0.00555799 0.0278839 0.00721016
internal_weight=2783.74 2041.18 742.559 126.571 1915.54 125.632 615.988
internal_count=13570 9631 3939 675 9037 594 3264
is_linear=0
shrinkage=0.104222
Tree=15
num_leaves=8
num_cat=0
split_feature=94 53 50 125 104 49 120
split_gain=15.421 19.7004 11.5702 10.8982 8.46618 7.68903 0.284565
threshold=17.550000000000004 3.5150000000000001 2.0050000000000003 0.45000000000000007 0.45750000000000007 1.8950000000000002 2.4365000000000001
decision_type=2 2 2 2 2 2 2
left_child=2 5 3 -1 -3 -2 -4
right_child=1 4 6 -5 -6 -7 -8
leaf_value=-0.040237205117941127 0.029193543717025244 -0.027898057080941927 0.13173751892789104 -0.0049749588986612815 -0.15821400423700094 0.0070086816806797644 0.089170490566897243
leaf_weight=101.41680394113064 210.2422444075346 11.58236499130726 5.4282936453819257 1551.7522683441639 10.168696627020834 879.85487067699432 2.6328659802675247
leaf_count=475 1114 49 33 7519 43 4276 17
internal_value=-0.000172415 0.00932761 -0.00653072 -0.00713841 -0.0888634 0.0112876 0.117955
internal_weight=2773.08 1111.85 1661.23 1653.17 21.7511 1090.1 8.06116
internal_count=13526 5482 8044 7994 92 5390 50
is_linear=0
shrinkage=0.104222
Tree=16
num_leaves=7
num_cat=0
split_feature=54 49 149 143 133 120
split_gain=13.5064 12.5454 12.7609 9.52104 4.64479 6.69198
threshold=1.6950000000000001 4.1950000000000012 0.53851874003189804 -2.5639999999999996 1.2110000000000001 3.0500000000000003
decision_type=2 2 2 2 2 2
left_child=1 3 -3 -1 -2 -6
right_child=4 2 -4 -5 5 -7
leaf_value=-0.11165723984306615 -0.022805170561939463 -0.034049153155287885 0.071318390164509041 0.0013999129723307169 0.11023664759120146 -0.064625521974505357
leaf_weight=8.1074528247117978 4.7248373478651073 177.6567175835371 13.41981780529022 2544.4961924999952 20.898206591606144 2.6760432869195929
leaf_count=41 29 758 58 12491 132 17
internal_value=-0.000148839 -0.000887801 -0.0266458 0.00104042 0.0714722 0.0903692
internal_weight=2771.98 2743.68 191.077 2552.6 28.2991 23.5742
internal_count=13526 13348 816 12532 178 149
is_linear=0
shrinkage=0.104222
Tree=17
num_leaves=8
num_cat=0
split_feature=86 89 86 49 89 92 95
split_gain=12.3529 16.6857 29.867 13.2745 12.903 19.095 7.28162
threshold=2.9298029556650254 1.0000000180025095e-35 1.8603896103896107 3.3850000000000002 0.27000000000000007 -0.62124999999999986 8.25
decision_type=2 2 2 2 2 2 2
left_child=1 3 -3 -1 6 -6 -2
right_child=4 2 -4 -5 5 -7 -8
leaf_value=0.0076740310795042049 0.058197093639699833 -0.094247277907232085 -0.010401082561217358 -0.014939641022089629 -0.10188514738956803 0.012348640807380216 -0.023647947189664047
leaf_weight=1310.5064302459359 93.891582764685154 49.551189616322517 670.03426378965378 359.25059229135513 16.932199478149414 257.1773796081543 13.500689059495924
leaf_count=6543 499 228 3182 1562 87 1350 75
internal_value=-0.000128236 -0.00290891 -0.016176 0.00280861 0.0172867 0.00528897 0.0479069
internal_weight=2770.84 2389.34 719.585 1669.76 381.502 274.11 107.392
internal_count=13526 11515 3410 8105 2011 1437 574
is_linear=0
shrinkage=0.104222
Tree=18
num_leaves=8
num_cat=0
split_feature=149 56 148 55 148 53 92
split_gain=15.7381 11.9383 11.3441 10.9087 9.49514 8.82734 5.7139
threshold=0.53433908520280893 1.7550000000000001 0.26796584848230992 1.905 0.24185130950380487 2.1250000000000004 -0.0099999999999999482
decision_type=2 2 2 2 2 2 2
left_child=1 4 -3 5 -1 -2 -5
right_child=3 2 -4 6 -6 -7 -8
leaf_value=-0.054435660661113758 0.0153280247340685 0.02819107947162831 -0.017585503842290271 -0.01840079708748131 -0.010724207322160621 -0.0015876644117591623 0.10180483948952145
leaf_weight=56.825720146298409 617.56049958616495 165.87729875743389 91.08600726723671 5.4966678321361568 1074.6435647159815 732.60994145274162 19.602076262235641
leaf_count=283 3334 829 448 27 4917 3565 97
internal_value=-0.000486987 -0.00831442 0.0119646 0.00741537 -0.01292 0.00614953 0.0754826
internal_weight=2763.7 1388.43 256.963 1375.27 1131.47 1350.17 25.0987
internal_count=13500 6477 1277 7023 5200 6899 124
is_linear=0
shrinkage=0.104222
Tree=19
num_leaves=8
num_cat=0
split_feature=149 55 135 1 27 115 54
split_gain=12.6285 9.13776 8.91602 12.9607 10.4883 7.46851 5.43256
threshold=0.53433908520280893 1.905 0.30550000000000005 1494.2870431281676 0.50845238095238099 2.3665000000000007 1.3650000000000004
decision_type=2 2 2 2 2 2 2
left_child=2 5 3 -1 -4 -2 -3
right_child=1 6 4 -5 -6 -7 -8
leaf_value=0.00092966247827827358 0.0028048803388890822 0.099877401856329082 -0.0069739024068449437 -0.021042388628781803 0.03967412519492558 0.027656530085268364 -0.011168774035797255
leaf_weight=536.86709239333868 1198.4237125739455 17.76616628468037 128.78820982575417 638.33134125173092 88.204329490661621 147.51364935934544 6.5457842200994483
leaf_count=2501 6137 84 598 2961 417 762 40
internal_value=-0.000430022 0.0066729 -0.007421 -0.0110049 0.0119887 0.00552878 0.0699864
internal_weight=2762.44 1370.25 1392.19 1175.2 216.993 1345.94 24.312
internal_count=13500 7023 6477 5462 1015 6899 124
is_linear=0
shrinkage=0.104222
Tree=20
num_leaves=8
num_cat=0
split_feature=86 29 98 85 111 54 1
split_gain=13.4852 11.6743 11.2269 10.3561 7.51975 9.02896 6.85752
threshold=3.3717607973421928 3.8452380952380958 0.53589743589743599 -0.45393665158371038 1.7500000000000002 1.3250000000000004 1430.9302281483838
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 6 -6 -2
right_child=4 3 -4 -5 5 -7 -8
leaf_value=0.010161271773601144 -0.030831897558424644 -0.065476683381506764 -0.016436452248208454 0.004566632375329463 -0.017458853996767588 0.051773327327659352 0.10280712840651235
leaf_weight=220.18627671897411 5.0186129659414318 23.250052616000175 793.86604422330856 1612.9528871029615 39.005949392914772 43.020874515175819 24.570655010640621
leaf_count=1065 29 113 3791 7879 209 264 150
internal_value=-0.000380007 -0.0018746 -0.0106611 0.00357096 0.0351053 0.0188533 0.0801381
internal_weight=2761.87 2650.26 1014.05 1636.2 111.616 82.0268 29.5893
internal_count=13500 12848 4856 7992 652 473 179
is_linear=0
shrinkage=0.104222
Tree=21
num_leaves=7
num_cat=0
split_feature=96 132 136 141 123 120
split_gain=13.2003 16.5388 14.2713 13.2653 11.1928 9.3524
threshold=4.5000000000000009 0.64600000000000013 7.8500000000000005 -2.3269999999999995 0.73000000000000009 3.1835000000000004
decision_type=2 2 2 2 2 2
left_child=1 2 -1 -2 -3 -5
right_child=3 4 -4 5 -6 -7
leaf_value=-0.060960557629992744 -0.18516760103419116 0.033438636082733196 -0.0037890386549494811 0.011554321509567651 -0.0045660462097220139 -0.095189759824081052
leaf_weight=104.0578725785017 3.772067964076995 88.859501846134663 87.140555322170258 867.02598301321268 1593.5797092542052 8.9989516139030439
leaf_count=484 19 416 405 4401 7693 51
internal_value=-0.000914735 -0.00585967 -0.0349056 0.00961608 -0.00255862 0.0104568
internal_weight=2753.43 1873.64 191.198 879.797 1682.44 876.025
internal_count=13469 8998 889 4471 8109 4452
is_linear=0
shrinkage=0.104222
Tree=22
num_leaves=8
num_cat=0
split_feature=86 96 132 53 17 141 14
split_gain=11.7147 10.9619 13.1395 10.1877 3.25091 5.30827 0.81688
threshold=3.9466666666666668 4.5000000000000009 0.64600000000000013 2.0250000000000004 1.0000000180025095e-35 0.45950000000000008 0.63333333333333341
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 -3 5 -2 -6
right_child=4 3 -4 -5 6 -7 -8
leaf_value=-0.03177439277102298 0.091437039897303479 0.024857814401883283 -0.0029740838488449607 0.00058337476930965298 0.07050981561257999 -0.054418786094624118 0.13381725959363758
leaf_weight=191.83759662508965 7.081265255808832 274.07831323891878 1669.0744777023792 596.54252921044827 3.1656967699527723 4.3793987929821014 7.6569998264312744
leaf_count=880 41 1511 8020 2927 18 26 46
internal_value=-0.000813155 -0.00142725 -0.00594319 0.00822535 0.0744319 0.0357031 0.115377
internal_weight=2753.82 2731.53 1860.91 870.621 22.2834 11.4607 10.8227
internal_count=13469 13338 8900 4438 131 67 64
is_linear=0
shrinkage=0.104222
Tree=23
num_leaves=8
num_cat=0
split_feature=54 86 91 40 130 51 10
split_gain=12.222 10.6549 10.0211 7.99429 8.88031 6.65012 3.52018
threshold=1.5750000000000004 3.9115384615384619 0.46333333333333332 1.4550000000000003 3.4720000000000004 1.0650000000000002 1.2583333333333335
decision_type=2 2 2 2 2 2 2
left_child=1 2 -1 4 -2 -5 -3
right_child=3 6 -4 5 -6 -7 -8
leaf_value=-0.0022469666970611943 0.016170707495608474 0.01400989076087111 -0.12852821652405796 0.092762096676041614 -0.16668567356812367 0.014555154480083921 0.10191215472125625
leaf_weight=2637.7453966140747 32.849811799824238 7.6601853668689754 6.834140643477439 32.402883179485798 3.1530902385711661 18.586706772446632 13.987728200852869
leaf_count=12765 209 44 31 200 20 112 88
internal_value=-0.000723185 -0.00197755 -0.00257376 0.0377177 0.000116917 0.0642621 0.0708273
internal_weight=2753.22 2666.23 2644.58 86.9925 36.0029 50.9896 21.6479
internal_count=13469 12928 12796 541 229 312 132
is_linear=0
shrinkage=0.104222
Tree=24
num_leaves=7
num_cat=0
split_feature=49 137 127 10 121 40
split_gain=12.6198 16.4964 12.4085 9.96103 9.53044 3.42942
threshold=3.3850000000000002 2.8500000000000001 1.4365000000000003 4.3000000000000007 2.7750000000000004 2.7850000000000006
decision_type=2 2 2 2 2 2
left_child=3 5 -3 4 -1 -2
right_child=1 2 -4 -5 -6 -7
leaf_value=0.0033265147337535523 -0.2293764266460791 -0.0035570344953408817 -0.040820787979045939 -0.15680120567767367 0.077284263807364459 -0.095337183505289247
leaf_weight=2143.9148783534765 3.9466704875230771 459.63925896584988 123.04180367290974 4.1842633336782447 19.085369817912579 4.4060309380292892
leaf_count=10807 17 1977 532 22 100 19
internal_value=-1.32262e-05 -0.0135124 -0.0114264 0.00366831 0.00397942 -0.158858
internal_weight=2758.22 591.034 582.681 2167.18 2163 8.3527
internal_count=13474 2545 2509 10929 10907 36
is_linear=0
shrinkage=0.104222
Tree=25
num_leaves=8
num_cat=0
split_feature=49 6 94 56 127 104 7
split_gain=10.0943 10.6231 11.9381 10.4719 9.83816 10.0485 9.11168
threshold=3.3850000000000002 1485.1782059621762 17.550000000000004 2.1050000000000004 1.4365000000000003 0.42950000000000005 3.0523414722088096
decision_type=2 2 2 2 2 2 2
left_child=1 3 -3 -1 6 -6 -2
right_child=4 2 -4 -5 5 -7 -8
leaf_value=0.014478237478344286 -0.015463014973786505 -0.0073632514981839545 0.0096110031081186563 0.078530598053871026 -0.0086524748202200501 -0.067456935030922358 0.015332839885013407
leaf_weight=308.73068219423294 309.28241093456745 1017.4353533536196 807.05070595443249 30.452116876840591 63.547168910503387 62.713204026222229 157.49458535015583
leaf_count=1561 1327 5053 4143 172 271 269 678
internal_value=-7.78723e-06 0.00329399 0.000145221 0.0202309 -0.012054 -0.0378634 -0.00507213
internal_weight=2756.71 2163.67 1824.49 339.183 593.037 126.26 466.777
internal_count=13474 10929 9196 1733 2545 540 2005
is_linear=0
shrinkage=0.104222
Tree=26
num_leaves=7
num_cat=0
split_feature=86 78 86 86 106 45
split_gain=11.0807 19.2113 13.2975 16.5366 9.93615 9.16974
threshold=2.6909814323607431 3.2071428571428577 1.0000000180025095e-35 1.8603896103896107 0.59050000000000014 1.1950000000000001
decision_type=2 2 2 2 2 2
left_child=2 4 5 -4 -2 -1
right_child=1 -3 3 -5 -6 -7
leaf_value=0.0057285473288771786 0.034425187503696976 -0.17261249206353166 -0.072289780038289356 -0.011539802081962551 0.0051253977786566941 -0.012137044156937154
leaf_weight=1235.6497117057443 188.36661138385534 5.9902653545141211 54.218099623918533 475.07240612059832 377.99835128337145 417.49042452871799
leaf_count=6255 973 33 239 2206 1968 1800
internal_value=-1.60817e-06 0.0129056 -0.00338664 -0.0177639 0.0148706 0.00121666
internal_weight=2754.79 572.355 2182.43 529.291 566.365 1653.14
internal_count=13474 2974 10500 2445 2941 8055
is_linear=0
shrinkage=0.104222
Tree=27
num_leaves=8
num_cat=0
split_feature=148 141 115 107 104 4 134
split_gain=10.1298 10.8135 12.2053 9.7232 15.8813 8.97442 7.29505
threshold=0.17399267399267399 -0.79299999999999993 1.2250000000000003 0.40950000000000003 0.41250000000000003 1460.7202799166059 -0.30549999999999994
decision_type=2 2 2 2 2 2 2
left_child=3 5 -3 6 -5 -2 -1
right_child=1 2 -4 4 -6 -7 -8
leaf_value=-0.10259253098907423 -0.03269054108208802 -0.0021082423175915925 0.015308582355162925 -0.13734624687852556 0.036154293999346325 -0.0035202193944307817 0.042506627852751083
leaf_weight=4.9914454817771938 183.7718341127038 1603.1649219617248 600.83727415651083 20.186357498168949 7.9962391257286063 304.1783049851656 15.20464117079973
leaf_count=25 894 7830 2977 102 43 1483 78
internal_value=-0.0013178 -0.00046824 0.00263984 -0.0485807 -0.0881234 -0.0145068 0.00661435
internal_weight=2740.33 2691.95 2204 48.3787 28.1826 487.95 20.1961
internal_count=13432 13184 10807 248 145 2377 103
is_linear=0
shrinkage=0.104222
Tree=28
num_leaves=8
num_cat=0
split_feature=86 134 41 106 29 91 3
split_gain=10.2371 10.3112 10.5364 10.1993 11.3955 9.81442 8.99213
threshold=2.864250614250615 -0.43849999999999995 2.1050000000000004 0.95450000000000013 1.3875000000000002 -0.0054166666666666486 1670.9226550788155
decision_type=2 2 2 2 2 2 2
left_child=3 2 -2 5 -5 -1 -3
right_child=1 6 -4 4 -6 -7 -8
leaf_value=-0.015667034131642239 -0.063328583368563382 0.021380817595676553 0.036600112461665557 -0.21945686012767968 -0.026560041934415114 0.0014656039323343243 -0.12083840209161807
leaf_weight=461.02843705564737 34.460375130176544 373.77398503571749 17.162777505815029 3.4011466801166526 135.53743974119425 1711.2739861980081 4.8830340132117263
leaf_count=2173 176 1994 103 17 658 8282 29
internal_value=-0.0011736 0.0135866 -0.0301054 -0.00392153 -0.0312966 -0.00217051 0.0195438
internal_weight=2741.52 430.28 51.6232 2311.24 138.939 2172.3 378.657
internal_count=13432 2302 279 11130 675 10455 2023
is_linear=0
shrinkage=0.104222
Tree=29
num_leaves=8
num_cat=0
split_feature=12 84 115 141 136 11 110
split_gain=9.65816 11.3219 10.1518 11.5361 10.0734 9.44977 6.86501
threshold=1.0000000180025095e-35 0.017970779220779203 1.2915000000000003 -0.60349999999999981 3.6500000000000008 2.1083333333333338 27.750000000000004
decision_type=2 2 2 2 2 2 2
left_child=1 6 4 -4 -2 -3 -1
right_child=2 5 3 -5 -6 -7 -8
leaf_value=0.01695486197013276 0.018272032851518873 -0.047853351303007219 -0.020306195413698006 0.015936397489881831 -0.011004436565708025 0.099082325881264394 -0.086337197330871959
leaf_weight=520.42061326652765 139.83185759186745 50.192012257874012 122.51054417341948 430.93686553835869 1465.7739738970995 5.2428159564733496 7.0750899761915198
leaf_count=2585 694 257 602 2140 7090 29 35
internal_value=-0.00104481 0.0108592 -0.00425885 0.00791357 -0.00845466 -0.0339422 0.0155679
internal_weight=2741.98 582.931 2159.05 553.447 1605.61 55.4348 527.496
internal_count=13432 2906 10526 2742 7784 286 2620
is_linear=0
shrinkage=0.104222
Tree=30
num_leaves=8
num_cat=0
split_feature=120 37 38 38 105 85 100
split_gain=12.0364 15.2453 11.9342 13.3835 10.0958 10.0578 10.0546
threshold=1.4145000000000001 0.42084520042422408 0.23906113829447367 0.14593794095256588 1.1225000000000003 -0.37141065830721004 1.3650000000000002
decision_type=2 2 2 2 2 2 2
left_child=2 6 3 -1 -3 -4 -2
right_child=1 4 5 -5 -6 -7 -8
leaf_value=-0.12038760226397151 -0.0090962232080863334 0.0034107878159782353 -0.060847190395801561 0.022337300771774038 -0.044228760615057698 -0.0060312321358531873 0.030222541925148897
leaf_weight=7.3577561527490607 84.787891268730164 592.17002998292446 37.370823763310909 228.1118380650878 52.602927520871162 1330.5593820437789 423.39983003586531
leaf_count=43 418 3057 171 1205 280 6147 2165
internal_value=0.00204154 0.0101633 -0.00379863 0.0178729 -0.00047641 -0.00752917 0.0236624
internal_weight=2756.36 1152.96 1603.4 235.47 644.773 1367.93 508.188
internal_count=13486 5920 7566 1248 3337 6318 2583
is_linear=0
shrinkage=0.104222
Tree=31
num_leaves=8
num_cat=0
split_feature=86 86 86 1 127 31 122
split_gain=10.4188 10.4465 11.9432 10.3703 15.906 10.0392 7.58651
threshold=2.6939799331103687 1.0000000180025095e-35 1.8603896103896107 1467.9553804385575 2.1340000000000003 0.22980739360049704 2.3515000000000006
decision_type=2 2 2 2 2 2 2
left_child=1 6 -3 5 -5 -2 -1
right_child=3 2 -4 4 -6 -7 -8
leaf_value=0.0014256124570572787 0.0018941270616234518 -0.058645654322910731 -0.0086447877444392647 0.02733676763309234 -0.12590863729503893 -0.082423971345859881 0.042831761918242235
leaf_weight=1608.7813730537891 166.28092505782843 58.273459360003471 473.13907171785831 372.2649027556181 7.4961845725774756 16.887378051877022 49.535993173718452
leaf_count=7823 854 251 2181 1963 43 94 277
internal_value=0.00183871 -0.00141235 -0.0141287 0.0144848 0.0243088 -0.0058835 0.0026627
internal_weight=2752.66 2189.73 531.413 562.929 379.761 183.168 1658.32
internal_count=13486 10532 2432 2954 2006 948 8100
is_linear=0
shrinkage=0.104222
Tree=32
num_leaves=8
num_cat=0
split_feature=93 144 112 94 104 93 120
split_gain=10.1721 16.1941 11.2364 13.1765 9.78364 8.96284 8.48134
threshold=20.550000000000004 0.013450000000000002 0.22750000000000004 5.5500000000000007 1.2865000000000002 25.050000000000004 2.0500000000000003
decision_type=2 2 2 2 2 2 2
left_child=2 4 6 -4 -2 -3 -1
right_child=1 5 3 -5 -6 -7 -8
leaf_value=0.0034609471658556285 0.020050548549636428 -0.009082334773132807 -0.11004659616173273 -0.007594432301873808 -0.021486441758403186 -0.13291664093499478 0.038221762386595907
leaf_weight=820.33589915931225 594.67776323109865 13.120999380946161 13.791079476475714 1142.1561977639794 68.70460732281208 12.293409973382948 84.046657353639603
leaf_count=3903 3054 71 65 5495 382 70 446
internal_value=0.0016558 0.0126202 -0.00200979 -0.00881763 0.0157484 -0.0690103 0.00669173
internal_weight=2749.13 688.797 2060.33 1155.95 663.382 25.4144 904.383
internal_count=13486 3577 9909 5560 3436 141 4349
is_linear=0
shrinkage=0.104222
Tree=33
num_leaves=5
num_cat=0
split_feature=91 141 123 7
split_gain=9.64033 9.54985 10.2025 8.96435
threshold=0.46333333333333332 -0.77399999999999991 2.2565000000000004 33.104396552584838
decision_type=2 2 2 2
left_child=1 3 -3 -1
right_child=-2 2 -4 -5
leaf_value=-0.015732035707484552 -0.12133869515037762 0.0038526132779362976 0.057630556320633561 0.031446003016811871
leaf_weight=450.80555958300829 6.9056807309389105 2188.2216669544578 38.992827542126179 48.440718069672585
leaf_count=2162 31 10769 213 250
internal_value=0.00156231 0.00187402 0.00479437 -0.011154
internal_weight=2733.37 2726.46 2227.21 499.246
internal_count=13425 13394 10982 2412
is_linear=0
shrinkage=0.104222
Tree=34
num_leaves=5
num_cat=0
split_feature=45 98 80 123
split_gain=8.19305 8.37131 12.6079 10.3403
threshold=1.6150000000000004 0.58114035087719318 0.043836402184014855 2.1715000000000004
decision_type=2 2 2 2
left_child=1 3 -3 -1
right_child=-2 2 -4 -5
leaf_value=0.008356015193296689 -0.14816646007318571 -0.033602776052007143 0.00019618273212945178 0.070940775625300892
leaf_weight=749.49989224970341 3.9628616422414771 128.32899699360132 1819.463518589735 29.807201541960239
leaf_count=3713 16 704 8830 162
internal_value=0.00140384 0.0016217 -0.00203079 0.0107506
internal_weight=2731.06 2727.1 1947.79 779.307
internal_count=13425 13409 9534 3875
is_linear=0
shrinkage=0.104222
end of trees
feature_importances:
Column_86=18
Column_49=9
Column_53=8
Column_41=6
Column_54=6
Column_120=6
Column_141=6
Column_45=5
Column_91=5
Column_104=5
Column_1=4
Column_94=4
Column_96=4
Column_132=4
Column_136=4
Column_148=4
Column_3=3
Column_4=3
Column_6=3
Column_11=3
Column_30=3
Column_35=3
Column_38=3
Column_48=3
Column_50=3
Column_85=3
Column_92=3
Column_105=3
Column_115=3
Column_123=3
Column_127=3
Column_130=3
Column_133=3
Column_149=3
Column_150=3
Column_0=2
Column_7=2
Column_10=2
Column_16=2
Column_29=2
Column_40=2
Column_44=2
Column_51=2
Column_52=2
Column_55=2
Column_56=2
Column_89=2
Column_93=2
Column_98=2
Column_100=2
Column_102=2
Column_106=2
Column_110=2
Column_111=2
Column_122=2
Column_134=2
Column_135=2
Column_137=2
Column_143=2
Column_144=2
Column_147=2
Column_8=1
Column_12=1
Column_14=1
Column_17=1
Column_21=1
Column_27=1
Column_28=1
Column_31=1
Column_34=1
Column_37=1
Column_39=1
Column_46=1
Column_47=1
Column_78=1
Column_80=1
Column_84=1
Column_95=1
Column_99=1
Column_107=1
Column_112=1
Column_119=1
Column_121=1
Column_125=1
Column_126=1
Column_145=1
parameters:
[boosting: gbdt]
[objective: binary]
[metric: binary_logloss]
[tree_learner: serial]
[device_type: cpu]
[data_sample_strategy: bagging]
[data: ]
[valid: ]
[num_iterations: 1500]
[learning_rate: 0.104222]
[num_leaves: 8]
[num_threads: 4]
[seed: 42]
[deterministic: 0]
[force_col_wise: 0]
[force_row_wise: 0]
[histogram_pool_size: -1]
[max_depth: 3]
[min_data_in_leaf: 17]
[min_sum_hessian_in_leaf: 0.001]
[bagging_fraction: 0.709098]
[pos_bagging_fraction: 1]
[neg_bagging_fraction: 1]
[bagging_freq: 3]
[bagging_seed: 400]
[bagging_by_query: 0]
[feature_fraction: 0.58888]
[feature_fraction_bynode: 1]
[feature_fraction_seed: 30056]
[extra_trees: 0]
[extra_seed: 12879]
[early_stopping_round: 0]
[early_stopping_min_delta: 0]
[first_metric_only: 0]
[max_delta_step: 0]
[lambda_l1: 7.81041e-07]
[lambda_l2: 0.00942891]
[linear_lambda: 0]
[min_gain_to_split: 0]
[drop_rate: 0.1]
[max_drop: 50]
[skip_drop: 0.5]
[xgboost_dart_mode: 0]
[uniform_drop: 0]
[drop_seed: 17869]
[top_rate: 0.2]
[other_rate: 0.1]
[min_data_per_group: 100]
[max_cat_threshold: 32]
[cat_l2: 10]
[cat_smooth: 10]
[max_cat_to_onehot: 4]
[top_k: 20]
[monotone_constraints: ]
[monotone_constraints_method: basic]
[monotone_penalty: 0]
[feature_contri: ]
[forcedsplits_filename: ]
[refit_decay_rate: 0.9]
[cegb_tradeoff: 1]
[cegb_penalty_split: 0]
[cegb_penalty_feature_lazy: ]
[cegb_penalty_feature_coupled: ]
[path_smooth: 0]
[interaction_constraints: ]
[verbosity: -1]
[saved_feature_importance_type: 0]
[use_quantized_grad: 0]
[num_grad_quant_bins: 4]
[quant_train_renew_leaf: 0]
[stochastic_rounding: 1]
[linear_tree: 0]
[max_bin: 255]
[max_bin_by_feature: ]
[min_data_in_bin: 3]
[bin_construct_sample_cnt: 200000]
[data_random_seed: 175]
[is_enable_sparse: 1]
[enable_bundle: 1]
[use_missing: 1]
[zero_as_missing: 0]
[feature_pre_filter: 1]
[pre_partition: 0]
[two_round: 0]
[header: 0]
[label_column: ]
[weight_column: ]
[group_column: ]
[ignore_column: ]
[categorical_feature: ]
[forcedbins_filename: ]
[precise_float_parser: 0]
[parser_config_file: ]
[objective_seed: 16083]
[num_class: 1]
[is_unbalance: 0]
[scale_pos_weight: 1]
[sigmoid: 1]
[boost_from_average: 1]
[reg_sqrt: 0]
[alpha: 0.9]
[fair_c: 1]
[poisson_max_delta_step: 0.7]
[tweedie_variance_power: 1.5]
[lambdarank_truncation_level: 30]
[lambdarank_norm: 1]
[label_gain: ]
[lambdarank_position_bias_regularization: 0]
[eval_at: ]
[multi_error_top_k: 1]
[auc_mu_weights: ]
[num_machines: 1]
[local_listen_port: 12400]
[time_out: 120]
[machine_list_filename: ]
[machines: ]
[gpu_platform_id: -1]
[gpu_device_id: -1]
[gpu_use_dp: 0]
[num_gpu: 1]
end of parameters
pandas_categorical:null
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+735
View File
@@ -0,0 +1,735 @@
"""
V25 Ensemble Predictor - NO TARGET LEAKAGE
===========================================
Multi-model ensemble for match prediction using XGBoost and LightGBM.
Features:
- 73 engineered features (NO target leakage)
- Market-specific models (MS, OU25, BTTS)
- Weighted ensemble predictions
- Value bet detection
"""
import os
import json
import numpy as np
import pandas as pd
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
import xgboost as xgb
import lightgbm as lgb
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try:
from config.config_loader import get_config as _get_cfg
except ImportError:
_get_cfg = None # type: ignore[assignment]
# CatBoost is optional
try:
from catboost import CatBoostClassifier
CATBOOST_AVAILABLE = True
except ImportError:
CatBoostClassifier = None
CATBOOST_AVAILABLE = False
# Paths
MODELS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'v25')
@dataclass
class MarketPrediction:
"""Prediction for a single betting market."""
market_type: str
pick: str
probability: float
confidence: float
odds: float = 0.0
is_value_bet: bool = False
edge: float = 0.0
def to_dict(self) -> dict:
return {
'market_type': self.market_type,
'pick': self.pick,
'probability': round(self.probability * 100, 1),
'confidence': round(self.confidence, 1),
'odds': self.odds,
'is_value_bet': self.is_value_bet,
'edge': round(self.edge * 100, 1),
}
@dataclass
class ValueBet:
"""Detected value bet opportunity."""
market_type: str
pick: str
probability: float
odds: float
edge: float
confidence: float
def to_dict(self) -> dict:
return {
'market_type': self.market_type,
'pick': self.pick,
'probability': round(self.probability * 100, 1),
'odds': self.odds,
'edge': round(self.edge * 100, 1),
'confidence': round(self.confidence, 1),
}
@dataclass
class MatchPrediction:
"""Complete match prediction with all markets."""
match_id: str
home_team: str
away_team: str
# MS predictions
home_prob: float = 0.0
draw_prob: float = 0.0
away_prob: float = 0.0
ms_pick: str = ''
ms_confidence: float = 0.0
# OU25 predictions
over_prob: float = 0.0
under_prob: float = 0.0
ou25_pick: str = ''
ou25_confidence: float = 0.0
# BTTS predictions
btts_yes_prob: float = 0.0
btts_no_prob: float = 0.0
btts_pick: str = ''
btts_confidence: float = 0.0
# Value bets
value_bets: List[ValueBet] = field(default_factory=list)
def to_dict(self) -> dict:
return {
'match_id': self.match_id,
'home_team': self.home_team,
'away_team': self.away_team,
'ms': {
'home_prob': round(self.home_prob * 100, 1),
'draw_prob': round(self.draw_prob * 100, 1),
'away_prob': round(self.away_prob * 100, 1),
'pick': self.ms_pick,
'confidence': round(self.ms_confidence, 1),
},
'ou25': {
'over_prob': round(self.over_prob * 100, 1),
'under_prob': round(self.under_prob * 100, 1),
'pick': self.ou25_pick,
'confidence': round(self.ou25_confidence, 1),
},
'btts': {
'yes_prob': round(self.btts_yes_prob * 100, 1),
'no_prob': round(self.btts_no_prob * 100, 1),
'pick': self.btts_pick,
'confidence': round(self.btts_confidence, 1),
},
'value_bets': [vb.to_dict() for vb in self.value_bets],
}
class V25Predictor:
"""
V25 Ensemble Predictor - NO TARGET LEAKAGE
Uses market-specific XGBoost and LightGBM models.
Each market (MS, OU25, BTTS) has its own trained models.
"""
# Feature columns — loaded dynamically from feature_cols.json to stay
# in sync with the trained models. The hardcoded list below is only a
# fallback in case the JSON file is missing.
_FALLBACK_FEATURE_COLS = [
# ELO Features (8)
'home_overall_elo', 'away_overall_elo', 'elo_diff',
'home_home_elo', 'away_away_elo',
'home_form_elo', 'away_form_elo', 'form_elo_diff',
# Form Features (12)
'home_goals_avg', 'home_conceded_avg',
'away_goals_avg', 'away_conceded_avg',
'home_clean_sheet_rate', 'away_clean_sheet_rate',
'home_scoring_rate', 'away_scoring_rate',
'home_winning_streak', 'away_winning_streak',
'home_unbeaten_streak', 'away_unbeaten_streak',
# H2H Features (6)
'h2h_total_matches', 'h2h_home_win_rate', 'h2h_draw_rate',
'h2h_avg_goals', 'h2h_btts_rate', 'h2h_over25_rate',
# Team Stats Features (8)
'home_avg_possession', 'away_avg_possession',
'home_avg_shots_on_target', 'away_avg_shots_on_target',
'home_shot_conversion', 'away_shot_conversion',
'home_avg_corners', 'away_avg_corners',
# Odds Features (24)
'odds_ms_h', 'odds_ms_d', 'odds_ms_a',
'implied_home', 'implied_draw', 'implied_away',
'odds_ht_ms_h', 'odds_ht_ms_d', 'odds_ht_ms_a',
'odds_ou05_o', 'odds_ou05_u',
'odds_ou15_o', 'odds_ou15_u',
'odds_ou25_o', 'odds_ou25_u',
'odds_ou35_o', 'odds_ou35_u',
'odds_ht_ou05_o', 'odds_ht_ou05_u',
'odds_ht_ou15_o', 'odds_ht_ou15_u',
'odds_btts_y', 'odds_btts_n',
# Odds Presence Flags (20)
'odds_ms_h_present', 'odds_ms_d_present', 'odds_ms_a_present',
'odds_ht_ms_h_present', 'odds_ht_ms_d_present', 'odds_ht_ms_a_present',
'odds_ou05_o_present', 'odds_ou05_u_present',
'odds_ou15_o_present', 'odds_ou15_u_present',
'odds_ou25_o_present', 'odds_ou25_u_present',
'odds_ou35_o_present', 'odds_ou35_u_present',
'odds_ht_ou05_o_present', 'odds_ht_ou05_u_present',
'odds_ht_ou15_o_present', 'odds_ht_ou15_u_present',
'odds_btts_y_present', 'odds_btts_n_present',
# League Features (4)
'home_xga', 'away_xga',
'league_avg_goals', 'league_zero_goal_rate',
# Upset Engine (4)
'upset_atmosphere', 'upset_motivation', 'upset_fatigue', 'upset_potential',
# Referee Engine (5)
'referee_home_bias', 'referee_avg_goals', 'referee_cards_total',
'referee_avg_yellow', 'referee_experience',
# Momentum Engine (3)
'home_momentum_score', 'away_momentum_score', 'momentum_diff',
# Squad Features (9)
'home_squad_quality', 'away_squad_quality', 'squad_diff',
'home_key_players', 'away_key_players',
'home_missing_impact', 'away_missing_impact',
'home_goals_form', 'away_goals_form',
]
@staticmethod
def _load_feature_cols() -> list:
"""Load feature columns from feature_cols.json, falling back to hardcoded list."""
feature_json = os.path.join(MODELS_DIR, 'feature_cols.json')
try:
if os.path.exists(feature_json):
with open(feature_json, 'r', encoding='utf-8') as f:
cols = json.load(f)
if isinstance(cols, list) and len(cols) > 0:
print(f"[V25] Loaded {len(cols)} feature columns from feature_cols.json")
return cols
except Exception as e:
print(f"[V25] Warning: could not load feature_cols.json: {e}")
print(f"[V25] Using fallback feature columns ({len(V25Predictor._FALLBACK_FEATURE_COLS)} features)")
return V25Predictor._FALLBACK_FEATURE_COLS
# Model weights for ensemble (overridden from config in __init__)
DEFAULT_WEIGHTS = {
'xgb': 0.50,
'lgb': 0.50,
}
def __init__(self, models_dir: Optional[str] = None):
"""
Initialize V25 Predictor.
Args:
models_dir: Directory containing model files. Defaults to v25/ directory.
"""
self.models_dir = models_dir or MODELS_DIR
self.models = {} # market -> {'xgb': model, 'lgb': model}
self._loaded = False
self.FEATURE_COLS = self._load_feature_cols()
# Load weights from config (falls back to class default 0.50/0.50)
if _get_cfg is not None:
try:
cfg = _get_cfg()
self.DEFAULT_WEIGHTS = {
'xgb': float(cfg.get('model_ensemble.xgb_weight', 0.50)),
'lgb': float(cfg.get('model_ensemble.lgb_weight', 0.50)),
}
except Exception:
pass # keep class-level defaults
# All trained market models available in V25
ALL_MARKETS = [
'ms', 'ou25', 'btts', # Core markets
'ou15', 'ou35', # Additional OU lines
'ht_result', 'ht_ou05', 'ht_ou15', # HT markets
'htft', # HT/FT combo
'cards_ou45', # Cards market
'handicap_ms', # Handicap
'odd_even', # Odd/Even goals
]
# Multi-class markets (output > 2 classes)
MULTICLASS_MARKETS = {'ms', 'ht_result', 'htft', 'handicap_ms'}
def load_models(self) -> bool:
"""Load all market-specific models from disk."""
try:
loaded_count = 0
for market in self.ALL_MARKETS:
self.models[market] = {}
# Load XGBoost (read content in Python to avoid non-ASCII path issues)
xgb_path = os.path.join(self.models_dir, f'xgb_v25_{market}.json')
if os.path.exists(xgb_path) and os.path.getsize(xgb_path) > 0:
with open(xgb_path, 'r', encoding='utf-8') as f:
xgb_content = f.read()
booster = xgb.Booster()
booster.load_model(bytearray(xgb_content, 'utf-8'))
# Corruption detection: verify model can run a dummy prediction
try:
_dummy = pd.DataFrame([{col: 0.0 for col in self.FEATURE_COLS}])
booster.predict(xgb.DMatrix(_dummy))
self.models[market]['xgb'] = booster
loaded_count += 1
except Exception as _ce:
print(f"[V25] ⚠️ XGB model for {market} failed integrity check: {_ce} — skipping")
# Load LightGBM (read content in Python to avoid non-ASCII path issues)
lgb_path = os.path.join(self.models_dir, f'lgb_v25_{market}.txt')
if os.path.exists(lgb_path) and os.path.getsize(lgb_path) > 0:
with open(lgb_path, 'r', encoding='utf-8') as f:
model_str = f.read()
lgb_model = lgb.Booster(model_str=model_str)
# Corruption detection: verify model can run a dummy prediction
try:
_dummy = pd.DataFrame([{col: 0.0 for col in self.FEATURE_COLS}])
lgb_model.predict(_dummy)
self.models[market]['lgb'] = lgb_model
loaded_count += 1
except Exception as _ce:
print(f"[V25] ⚠️ LGB model for {market} failed integrity check: {_ce} — skipping")
# Remove empty entries
if not self.models[market]:
del self.models[market]
print(f"[V25] Loaded {loaded_count} model files across {len(self.models)} markets: {list(self.models.keys())}")
self._loaded = loaded_count > 0
return self._loaded
except Exception as e:
print(f"[ERROR] Error loading models: {e}")
import traceback
traceback.print_exc()
return False
def _ensure_loaded(self):
"""Ensure models are loaded before prediction."""
if not self._loaded:
if not self.load_models():
raise RuntimeError("Failed to load V25 models")
def readiness_summary(self) -> Dict[str, Any]:
"""Return per-market model status for health check endpoint."""
if not self._loaded:
self.load_models()
market_status = {}
for market in self.ALL_MARKETS:
m = self.models.get(market, {})
market_status[market] = {
"xgb": "xgb" in m,
"lgb": "lgb" in m,
"ready": bool(m),
}
loaded_markets = [k for k, v in market_status.items() if v["ready"]]
return {
"fully_loaded": len(loaded_markets) == len(self.ALL_MARKETS),
"loaded_markets": loaded_markets,
"missing_markets": [m for m in self.ALL_MARKETS if m not in loaded_markets],
"weights": self.DEFAULT_WEIGHTS,
}
def _prepare_features(self, features: Dict[str, float]) -> pd.DataFrame:
"""Prepare feature vector for prediction."""
X = pd.DataFrame([{col: features.get(col, 0.0) for col in self.FEATURE_COLS}])
return X
def predict_ms(self, features: Dict[str, float]) -> tuple:
"""
Predict match result (1X2).
Returns:
(home_prob, draw_prob, away_prob)
"""
self._ensure_loaded()
X = self._prepare_features(features)
probs = []
# XGBoost
if 'xgb' in self.models.get('ms', {}):
dmat = xgb.DMatrix(X)
xgb_proba = self.models['ms']['xgb'].predict(dmat)
if len(xgb_proba.shape) == 1:
xgb_proba = np.array([xgb_proba])
probs.append(xgb_proba[0] * self.DEFAULT_WEIGHTS['xgb'])
# LightGBM
if 'lgb' in self.models.get('ms', {}):
lgb_proba = self.models['ms']['lgb'].predict(X)
if len(lgb_proba.shape) == 2:
probs.append(lgb_proba[0] * self.DEFAULT_WEIGHTS['lgb'])
if not probs:
return 0.33, 0.33, 0.33
ensemble_proba = np.sum(probs, axis=0)
ensemble_proba = ensemble_proba / ensemble_proba.sum()
return float(ensemble_proba[0]), float(ensemble_proba[1]), float(ensemble_proba[2])
def predict_ou25(self, features: Dict[str, float]) -> tuple:
"""
Predict Over/Under 2.5 goals.
Returns:
(over_prob, under_prob)
"""
self._ensure_loaded()
X = self._prepare_features(features)
probs = []
# XGBoost
if 'xgb' in self.models.get('ou25', {}):
dmat = xgb.DMatrix(X)
xgb_proba = self.models['ou25']['xgb'].predict(dmat)
if isinstance(xgb_proba, np.ndarray) and len(xgb_proba.shape) == 1:
probs.append(xgb_proba[0])
# LightGBM
if 'lgb' in self.models.get('ou25', {}):
lgb_proba = self.models['ou25']['lgb'].predict(X)
if isinstance(lgb_proba, np.ndarray):
probs.append(lgb_proba[0])
if not probs:
return 0.5, 0.5
# Average probability
avg_prob = np.mean(probs)
return float(avg_prob), float(1 - avg_prob)
def predict_btts(self, features: Dict[str, float]) -> tuple:
"""
Predict Both Teams To Score.
Returns:
(yes_prob, no_prob)
"""
self._ensure_loaded()
X = self._prepare_features(features)
probs = []
# XGBoost
if 'xgb' in self.models.get('btts', {}):
dmat = xgb.DMatrix(X)
xgb_proba = self.models['btts']['xgb'].predict(dmat)
if isinstance(xgb_proba, np.ndarray) and len(xgb_proba.shape) == 1:
probs.append(xgb_proba[0])
# LightGBM
if 'lgb' in self.models.get('btts', {}):
lgb_proba = self.models['btts']['lgb'].predict(X)
if isinstance(lgb_proba, np.ndarray):
probs.append(lgb_proba[0])
if not probs:
return 0.5, 0.5
# Average probability
avg_prob = np.mean(probs)
return float(avg_prob), float(1 - avg_prob)
def predict_market(self, market: str, features: Dict[str, float]) -> Optional[np.ndarray]:
"""
Generic prediction for any loaded market.
Args:
market: Market key (e.g. 'ht_result', 'htft', 'cards_ou45')
features: Feature dictionary.
Returns:
numpy array of probabilities.
For binary markets: [positive_prob]
For multi-class markets: [class0_prob, class1_prob, ...]
"""
self._ensure_loaded()
if market not in self.models:
return None
X = self._prepare_features(features)
probs = []
weights = []
is_multiclass = market in self.MULTICLASS_MARKETS
# XGBoost
if 'xgb' in self.models[market]:
dmat = xgb.DMatrix(X)
xgb_proba = self.models[market]['xgb'].predict(dmat)
if isinstance(xgb_proba, np.ndarray):
if is_multiclass and len(xgb_proba.shape) == 2:
probs.append(xgb_proba[0])
elif is_multiclass and len(xgb_proba.shape) == 1:
probs.append(xgb_proba)
else:
probs.append(np.array([xgb_proba[0]]))
weights.append(self.DEFAULT_WEIGHTS['xgb'])
# LightGBM
if 'lgb' in self.models[market]:
lgb_proba = self.models[market]['lgb'].predict(X)
if isinstance(lgb_proba, np.ndarray):
if is_multiclass and len(lgb_proba.shape) == 2:
probs.append(lgb_proba[0])
elif is_multiclass and len(lgb_proba.shape) == 1:
probs.append(lgb_proba)
else:
probs.append(np.array([lgb_proba[0]]))
weights.append(self.DEFAULT_WEIGHTS['lgb'])
if not probs:
return None
# Weighted average
if len(probs) == 1:
return probs[0]
total_w = sum(weights[:len(probs)])
result = np.zeros_like(probs[0])
for p, w in zip(probs, weights):
result += p * (w / total_w)
# Normalize multi-class
if is_multiclass and result.sum() > 0:
result = result / result.sum()
return result
def has_market(self, market: str) -> bool:
"""Check if a specific market model is loaded."""
return market in self.models
def predict_match(
self,
match_id: str,
home_team: str,
away_team: str,
features: Dict[str, float],
odds: Optional[Dict[str, float]] = None,
) -> MatchPrediction:
"""
Predict all markets for a match.
Args:
match_id: Match identifier.
home_team: Home team name.
away_team: Away team name.
features: Feature dictionary.
odds: Optional odds dictionary for value bet detection.
Returns:
MatchPrediction object.
"""
# Get predictions for each market
home_prob, draw_prob, away_prob = self.predict_ms(features)
over_prob, under_prob = self.predict_ou25(features)
btts_yes_prob, btts_no_prob = self.predict_btts(features)
# Determine picks
ms_probs = {'1': home_prob, 'X': draw_prob, '2': away_prob}
ms_pick = max(ms_probs, key=ms_probs.__getitem__)
ms_confidence = ms_probs[ms_pick] * 100
ou25_probs = {'Over': over_prob, 'Under': under_prob}
ou25_pick = max(ou25_probs, key=ou25_probs.__getitem__)
ou25_confidence = ou25_probs[ou25_pick] * 100
btts_probs = {'Yes': btts_yes_prob, 'No': btts_no_prob}
btts_pick = max(btts_probs, key=btts_probs.__getitem__)
btts_confidence = btts_probs[btts_pick] * 100
# Create prediction
prediction = MatchPrediction(
match_id=match_id,
home_team=home_team,
away_team=away_team,
home_prob=home_prob,
draw_prob=draw_prob,
away_prob=away_prob,
ms_pick=ms_pick,
ms_confidence=ms_confidence,
over_prob=over_prob,
under_prob=under_prob,
ou25_pick=ou25_pick,
ou25_confidence=ou25_confidence,
btts_yes_prob=btts_yes_prob,
btts_no_prob=btts_no_prob,
btts_pick=btts_pick,
btts_confidence=btts_confidence,
)
# Detect value bets
if odds:
prediction.value_bets = self._detect_value_bets(
prediction, odds, home_prob, draw_prob, away_prob,
over_prob, under_prob, btts_yes_prob, btts_no_prob
)
return prediction
def _detect_value_bets(
self,
prediction: MatchPrediction,
odds: Dict[str, float],
home_prob: float,
draw_prob: float,
away_prob: float,
over_prob: float,
under_prob: float,
btts_yes_prob: float,
btts_no_prob: float,
) -> List[ValueBet]:
"""Detect value bets based on model vs market odds."""
value_bets = []
# Market-specific minimum edge thresholds
# MS: higher variance → require more edge
# OU/BTTS: binary markets → tighter edge acceptable
EDGE_THRESHOLDS = {
'MS': 0.06,
'OU25': 0.04,
'BTTS': 0.04,
}
ms_edge = EDGE_THRESHOLDS['MS']
ou_edge = EDGE_THRESHOLDS['OU25']
btts_edge = EDGE_THRESHOLDS['BTTS']
# MS value bets
if 'ms_h' in odds and odds['ms_h'] > 0:
implied = 1 / odds['ms_h']
edge = home_prob - implied
if edge > ms_edge:
value_bets.append(ValueBet(
market_type='MS',
pick='1',
probability=home_prob,
odds=odds['ms_h'],
edge=edge,
confidence=home_prob * 100,
))
if 'ms_d' in odds and odds['ms_d'] > 0:
implied = 1 / odds['ms_d']
edge = draw_prob - implied
if edge > ms_edge:
value_bets.append(ValueBet(
market_type='MS',
pick='X',
probability=draw_prob,
odds=odds['ms_d'],
edge=edge,
confidence=draw_prob * 100,
))
if 'ms_a' in odds and odds['ms_a'] > 0:
implied = 1 / odds['ms_a']
edge = away_prob - implied
if edge > ms_edge:
value_bets.append(ValueBet(
market_type='MS',
pick='2',
probability=away_prob,
odds=odds['ms_a'],
edge=edge,
confidence=away_prob * 100,
))
# OU25 value bets
if 'ou25_o' in odds and odds['ou25_o'] > 0:
implied = 1 / odds['ou25_o']
edge = over_prob - implied
if edge > ou_edge:
value_bets.append(ValueBet(
market_type='OU25',
pick='Over',
probability=over_prob,
odds=odds['ou25_o'],
edge=edge,
confidence=over_prob * 100,
))
if 'ou25_u' in odds and odds['ou25_u'] > 0:
implied = 1 / odds['ou25_u']
edge = under_prob - implied
if edge > ou_edge:
value_bets.append(ValueBet(
market_type='OU25',
pick='Under',
probability=under_prob,
odds=odds['ou25_u'],
edge=edge,
confidence=under_prob * 100,
))
# BTTS value bets
if 'btts_y' in odds and odds['btts_y'] > 0:
implied = 1 / odds['btts_y']
edge = btts_yes_prob - implied
if edge > btts_edge:
value_bets.append(ValueBet(
market_type='BTTS',
pick='Yes',
probability=btts_yes_prob,
odds=odds['btts_y'],
edge=edge,
confidence=btts_yes_prob * 100,
))
if 'btts_n' in odds and odds['btts_n'] > 0:
implied = 1 / odds['btts_n']
edge = btts_no_prob - implied
if edge > btts_edge:
value_bets.append(ValueBet(
market_type='BTTS',
pick='No',
probability=btts_no_prob,
odds=odds['btts_n'],
edge=edge,
confidence=btts_no_prob * 100,
))
return value_bets
# Singleton instance
_v25_predictor: Optional[V25Predictor] = None
def get_v25_predictor() -> V25Predictor:
"""Get or create V25 predictor instance."""
global _v25_predictor
if _v25_predictor is None:
_v25_predictor = V25Predictor()
_v25_predictor.load_models()
return _v25_predictor
+343
View File
@@ -0,0 +1,343 @@
"""
V27 Pro Predictor Odds-Free Fundamentals + Value Edge Detection
This module loads V27 ensemble models (XGBoost, LightGBM, CatBoost)
and produces market-independent probability estimates.
The key insight: V27 is trained WITHOUT odds features, so it produces
"true" probabilities unbiased by market pricing. The divergence between
V25 (odds-aware) and V27 (odds-free) predictions signals market mispricing.
"""
import json
import logging
import os
import pickle
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import numpy as np
logger = logging.getLogger(__name__)
V27_DIR = Path(__file__).parent / "v27"
class V27Predictor:
"""
Loads V27 ensemble models and provides predictions using the
82-feature odds-free vector.
"""
MARKETS = ['ms', 'ou25', 'btts']
def __init__(self):
self.models: Dict[str, Dict[str, object]] = {}
self.feature_cols: List[str] = []
self._loaded = False
def load_models(self) -> bool:
"""Load all V27 ensemble models and feature column spec."""
if self._loaded:
return True
# Feature columns
cols_path = V27_DIR / "v27_feature_cols.json"
if not cols_path.exists():
logger.error("[V27] Feature columns file not found: %s", cols_path)
return False
try:
with open(cols_path, "r", encoding="utf-8") as f:
self.feature_cols = json.load(f)
logger.info("[V27] Loaded %d feature columns", len(self.feature_cols))
except Exception as e:
logger.error("[V27] Failed to load feature columns: %s", e)
return False
# Load models per market
model_types = {"xgb": "xgb", "lgb": "lgb"}
for market in self.MARKETS:
self.models[market] = {}
for short, label in model_types.items():
# Try market-specific file first: v27_ms_xgb.pkl
path = V27_DIR / f"v27_{market}_{short}.pkl"
if not path.exists():
# Fallback to generic: v27_xgboost.pkl (for MS only)
generic_names = {"xgb": "v27_xgboost.pkl", "lgb": "v27_lightgbm.pkl", "cb": "v27_catboost.pkl"}
path = V27_DIR / generic_names.get(short, "")
if not path.exists():
logger.warning("[V27] Model file not found for %s/%s", market, short)
continue
try:
with open(path, "rb") as f:
model = pickle.load(f)
self.models[market][label] = model
logger.info("[V27] ✓ Loaded %s/%s from %s", market, label, path.name)
except Exception as e:
logger.error("[V27] ✗ Failed to load %s/%s: %s", market, label, e)
loaded_count = sum(len(v) for v in self.models.values())
if loaded_count == 0:
logger.error("[V27] No models loaded!")
return False
self._loaded = True
logger.info("[V27] Total models loaded: %d across %d markets", loaded_count, len(self.models))
return True
def _build_feature_array(self, features: Dict[str, float]) -> np.ndarray:
"""
Build ordered feature array from the full feature dict.
V27 uses only its 82 features (odds-free subset).
"""
row = []
for col in self.feature_cols:
row.append(float(features.get(col, 0.0)))
return np.array([row])
def _predict_with_model(self, model, X: np.ndarray, label: str, expected_classes: int) -> Optional[np.ndarray]:
"""
Predict probabilities from a model, handling both sklearn wrappers
(predict_proba) and raw Booster objects (predict).
For raw XGBoost Boosters, DMatrix is created WITH feature_names
to match the training schema.
"""
import xgboost as xgb
import lightgbm as lgbm
import pandas as pd
# 1. Try sklearn-style predict_proba first
if hasattr(model, 'predict_proba'):
try:
proba = model.predict_proba(X)[0]
if len(proba) == expected_classes:
return proba
logger.warning("[V27] %s predict_proba returned %d classes, expected %d", label, len(proba), expected_classes)
except Exception:
pass # Fall through to raw predict
# 2. Raw xgboost.Booster — MUST pass feature_names
if isinstance(model, xgb.Booster):
try:
feature_names = self.feature_cols if self.feature_cols else None
dmat = xgb.DMatrix(X, feature_names=feature_names)
raw = model.predict(dmat)
if isinstance(raw, np.ndarray):
if raw.ndim == 2 and raw.shape[1] == expected_classes:
return raw[0]
elif raw.ndim == 1 and expected_classes == 2:
p = float(raw[0])
return np.array([1.0 - p, p])
elif raw.ndim == 1 and len(raw) == expected_classes:
return raw
except Exception as e:
logger.warning("[V27] %s xgb.Booster predict failed: %s", label, e)
return None
# 3. Raw lightgbm.Booster — pass as DataFrame with column names
if isinstance(model, lgbm.Booster):
try:
if self.feature_cols:
X_named = pd.DataFrame(X, columns=self.feature_cols)
raw = model.predict(X_named)
else:
raw = model.predict(X)
if isinstance(raw, np.ndarray):
if raw.ndim == 2 and raw.shape[1] == expected_classes:
return raw[0]
elif raw.ndim == 1 and expected_classes == 2:
p = float(raw[0])
return np.array([1.0 - p, p])
elif raw.ndim == 1 and len(raw) == expected_classes:
return raw
except Exception as e:
logger.warning("[V27] %s lgb.Booster predict failed: %s", label, e)
return None
# 4. Generic fallback (CatBoost, etc.)
try:
if hasattr(model, 'predict'):
raw = model.predict(X)
if isinstance(raw, np.ndarray):
if raw.ndim == 2 and raw.shape[1] == expected_classes:
return raw[0]
elif raw.ndim == 1 and expected_classes == 2:
p = float(raw[0])
return np.array([1.0 - p, p])
elif raw.ndim == 1 and len(raw) == expected_classes:
return raw
except Exception as e:
logger.warning("[V27] %s generic predict failed: %s", label, e)
return None
def predict_ms(self, features: Dict[str, float]) -> Optional[Dict[str, float]]:
"""
Predict Match Score probabilities (Home/Draw/Away).
Returns dict with keys: home, draw, away.
"""
if not self._loaded or "ms" not in self.models or not self.models["ms"]:
return None
X = self._build_feature_array(features)
probs_list = []
for label, model in self.models["ms"].items():
proba = self._predict_with_model(model, X, f"MS/{label}", expected_classes=3)
if proba is not None and len(proba) == 3:
probs_list.append(proba)
if not probs_list:
return None
# Ensemble average
avg = np.mean(probs_list, axis=0)
return {
"home": float(avg[0]),
"draw": float(avg[1]),
"away": float(avg[2]),
}
def predict_ou25(self, features: Dict[str, float]) -> Optional[Dict[str, float]]:
"""
Predict Over/Under 2.5 probabilities.
Returns dict with keys: under, over.
"""
if not self._loaded or "ou25" not in self.models or not self.models["ou25"]:
return None
X = self._build_feature_array(features)
probs_list = []
for label, model in self.models["ou25"].items():
proba = self._predict_with_model(model, X, f"OU25/{label}", expected_classes=2)
if proba is not None and len(proba) == 2:
probs_list.append(proba)
if not probs_list:
return None
avg = np.mean(probs_list, axis=0)
return {
"under": float(avg[0]),
"over": float(avg[1]),
}
def predict_btts(self, features: Dict[str, float]) -> Optional[Dict[str, float]]:
"""
Predict Both Teams To Score probabilities.
Returns dict with keys: no, yes.
"""
if not self._loaded or 'btts' not in self.models or not self.models['btts']:
return None
X = self._build_feature_array(features)
probs_list = []
for label, model in self.models['btts'].items():
proba = self._predict_with_model(model, X, f'BTTS/{label}', expected_classes=2)
if proba is not None and len(proba) == 2:
probs_list.append(proba)
if not probs_list:
return None
avg = np.mean(probs_list, axis=0)
return {
'no': float(avg[0]),
'yes': float(avg[1]),
}
def predict_dc(self, features: Dict[str, float]) -> Optional[Dict[str, float]]:
"""
Predict Double Chance probabilities.
DC is algebraically derived from MS predictions:
1X = home + draw
X2 = draw + away
12 = home + away
This gives an odds-free DC estimate for divergence detection.
"""
ms_probs = self.predict_ms(features)
if not ms_probs:
return None
home = ms_probs['home']
draw = ms_probs['draw']
away = ms_probs['away']
return {
'1x': round(home + draw, 4),
'x2': round(draw + away, 4),
'12': round(home + away, 4),
}
def predict_all(self, features: Dict[str, float]) -> Dict[str, Optional[Dict[str, float]]]:
"""Run predictions for all supported markets."""
return {
'ms': self.predict_ms(features),
'ou25': self.predict_ou25(features),
'btts': self.predict_btts(features),
'dc': self.predict_dc(features),
}
def compute_divergence(
v25_probs: Dict[str, float],
v27_probs: Dict[str, float],
) -> Dict[str, float]:
"""
Compute the divergence signal between V25 (odds-aware) and V27 (odds-free).
Positive divergence = V27 thinks it's MORE likely than the market → VALUE BET
Negative divergence = V27 thinks it's LESS likely than the market → PASS
Returns per-outcome divergence values.
"""
divergence = {}
for key in v27_probs:
v25_val = v25_probs.get(key, 0.33)
v27_val = v27_probs.get(key, 0.33)
divergence[key] = round(v27_val - v25_val, 4)
return divergence
def compute_value_edge(
v25_probs: Dict[str, float],
v27_probs: Dict[str, float],
odds: Dict[str, float],
) -> Dict[str, Dict]:
"""
Detect value bets by combining V25/V27 divergence with odds.
A value bet exists when:
1. V27 (odds-free) probability > implied odds probability (model says it's underpriced)
2. V27 and V25 divergence is positive (V27 sees more signal than the market)
Returns per-outcome: { probability, implied_prob, edge, is_value }
"""
results = {}
for key in v27_probs:
v27_p = v27_probs[key]
v25_p = v25_probs.get(key, 0.33)
odds_val = odds.get(key, 0.0)
implied_p = (1.0 / odds_val) if odds_val > 1.01 else 0.0
divergence = v27_p - v25_p
edge = v27_p - implied_p if implied_p > 0 else 0.0
results[key] = {
"v27_prob": round(v27_p, 4),
"v25_prob": round(v25_p, 4),
"implied_prob": round(implied_p, 4),
"divergence": round(divergence, 4),
"edge": round(edge, 4),
"is_value": edge > 0.05 and divergence > 0.02, # 5% edge + 2% divergence
}
return results
Binary file not shown.
+160
View File
@@ -0,0 +1,160 @@
{
"total_test": 23039,
"thresholds": {
"0.0": {
"n_matches": 22227,
"pct": 96.5,
"markets": {
"ms": {
"hit_rate": 0.5363,
"avg_roi": -0.0046,
"total_roi": -103.02
},
"ou15": {
"hit_rate": 0.7463,
"avg_roi": 0.0144,
"total_roi": 319.02
},
"ou25": {
"hit_rate": 0.6111,
"avg_roi": -0.006,
"total_roi": -134.41
},
"ou35": {
"hit_rate": 0.7302,
"avg_roi": -0.014,
"total_roi": -310.51
},
"btts": {
"hit_rate": 0.5848,
"avg_roi": 0.0031,
"total_roi": 69.5
}
}
},
"0.1": {
"n_matches": 23033,
"pct": 100.0,
"markets": {
"ms": {
"hit_rate": 0.546,
"avg_roi": -0.0045,
"total_roi": -104.38
},
"ou15": {
"hit_rate": 0.7533,
"avg_roi": 0.0145,
"total_roi": 335.02
},
"ou25": {
"hit_rate": 0.6193,
"avg_roi": -0.0042,
"total_roi": -96.97
},
"ou35": {
"hit_rate": 0.7277,
"avg_roi": -0.0147,
"total_roi": -338.57
},
"btts": {
"hit_rate": 0.5886,
"avg_roi": 0.0025,
"total_roi": 57.21
}
}
},
"0.2": {
"n_matches": 23034,
"pct": 100.0,
"markets": {
"ms": {
"hit_rate": 0.5459,
"avg_roi": -0.0046,
"total_roi": -105.38
},
"ou15": {
"hit_rate": 0.7533,
"avg_roi": 0.0146,
"total_roi": 335.26
},
"ou25": {
"hit_rate": 0.6193,
"avg_roi": -0.0043,
"total_roi": -97.97
},
"ou35": {
"hit_rate": 0.7276,
"avg_roi": -0.0147,
"total_roi": -339.57
},
"btts": {
"hit_rate": 0.5887,
"avg_roi": 0.0025,
"total_roi": 57.62
}
}
},
"0.3": {
"n_matches": 23039,
"pct": 100.0,
"markets": {
"ms": {
"hit_rate": 0.546,
"avg_roi": -0.0045,
"total_roi": -103.45
},
"ou15": {
"hit_rate": 0.7534,
"avg_roi": 0.0146,
"total_roi": 335.6
},
"ou25": {
"hit_rate": 0.6194,
"avg_roi": -0.0042,
"total_roi": -97.44
},
"ou35": {
"hit_rate": 0.7277,
"avg_roi": -0.0147,
"total_roi": -339.26
},
"btts": {
"hit_rate": 0.5887,
"avg_roi": 0.0025,
"total_roi": 58.61
}
}
},
"0.5": {
"n_matches": 23039,
"pct": 100.0,
"markets": {
"ms": {
"hit_rate": 0.546,
"avg_roi": -0.0045,
"total_roi": -103.45
},
"ou15": {
"hit_rate": 0.7534,
"avg_roi": 0.0146,
"total_roi": 335.6
},
"ou25": {
"hit_rate": 0.6194,
"avg_roi": -0.0042,
"total_roi": -97.44
},
"ou35": {
"hit_rate": 0.7277,
"avg_roi": -0.0147,
"total_roi": -339.26
},
"btts": {
"hit_rate": 0.5887,
"avg_roi": 0.0025,
"total_roi": 58.61
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
[
{
"market": "MS-Ev",
"min_edge": 0.02,
"n":
+267
View File
@@ -0,0 +1,267 @@
{
"generated_at": "2026-05-15T21:40:57.995899",
"matches_processed": 3000,
"matches_skipped": 0,
"markets": {
"MS": {
"overall_accuracy": 54.97,
"total_matches": 3000,
"by_confidence_band": {
"<50%": {
"accuracy": 38.87,
"count": 759,
"mean_confidence": 45.58
},
"50-65%": {
"accuracy": 52.62,
"count": 1300,
"mean_confidence": 57.19
},
"65-75%": {
"accuracy": 66.99,
"count": 624,
"mean_confidence": 69.49
},
"75%+": {
"accuracy": 79.5,
"count": 317,
"mean_confidence": 80.69
}
},
"by_league": {
"Bundesliga": {
"accuracy": 46.77,
"count": 62
},
"Ligue 1": {
"accuracy": 58.73,
"count": 63
},
"Serie A": {
"accuracy": 56.25,
"count": 64
},
"Other": {
"accuracy": 55.03,
"count": 2811
}
},
"by_pick_direction": {
"1": {
"accuracy": 58.38,
"count": 1946,
"mean_confidence": 60.84
},
"2": {
"accuracy": 48.72,
"count": 1053,
"mean_confidence": 56.44
},
"X": {
"accuracy": 0.0,
"count": 1,
"mean_confidence": 56.07
}
}
},
"OU15": {
"overall_accuracy": 74.4,
"total_matches": 3000,
"by_confidence_band": {
"50-65%": {
"accuracy": 70.97,
"count": 62,
"mean_confidence": 59.63
},
"65-75%": {
"accuracy": 68.0,
"count": 275,
"mean_confidence": 71.1
},
"75%+": {
"accuracy": 75.14,
"count": 2663,
"mean_confidence": 89.44
}
},
"by_league": {
"Bundesliga": {
"accuracy": 67.74,
"count": 62
},
"Ligue 1": {
"accuracy": 76.19,
"count": 63
},
"Serie A": {
"accuracy": 70.31,
"count": 64
},
"Other": {
"accuracy": 74.6,
"count": 2811
}
},
"by_pick_direction": {
"Over": {
"accuracy": 74.4,
"count": 3000,
"mean_confidence": 87.14
}
}
},
"OU25": {
"overall_accuracy": 51.77,
"total_matches": 3000,
"by_confidence_band": {
"50-65%": {
"accuracy": 49.33,
"count": 1267,
"mean_confidence": 57.13
},
"65-75%": {
"accuracy": 54.53,
"count": 453,
"mean_confidence": 69.42
},
"75%+": {
"accuracy": 53.2,
"count": 1280,
"mean_confidence": 90.2
}
},
"by_league": {
"Bundesliga": {
"accuracy": 41.94,
"count": 62
},
"Ligue 1": {
"accuracy": 50.79,
"count": 63
},
"Serie A": {
"accuracy": 43.75,
"count": 64
},
"Other": {
"accuracy": 52.19,
"count": 2811
}
},
"by_pick_direction": {
"Over": {
"accuracy": 51.03,
"count": 2432,
"mean_confidence": 76.11
},
"Under": {
"accuracy": 54.93,
"count": 568,
"mean_confidence": 60.17
}
}
},
"BTTS": {
"overall_accuracy": 51.83,
"total_matches": 3000,
"by_confidence_band": {
"50-65%": {
"accuracy": 48.74,
"count": 2214,
"mean_confidence": 58.66
},
"65-75%": {
"accuracy": 60.42,
"count": 758,
"mean_confidence": 68.19
},
"75%+": {
"accuracy": 64.29,
"count": 28,
"mean_confidence": 77.44
}
},
"by_league": {
"Bundesliga": {
"accuracy": 54.84,
"count": 62
},
"Ligue 1": {
"accuracy": 50.79,
"count": 63
},
"Serie A": {
"accuracy": 57.81,
"count": 64
},
"Other": {
"accuracy": 51.65,
"count": 2811
}
},
"by_pick_direction": {
"No": {
"accuracy": 50.26,
"count": 2099,
"mean_confidence": 61.56
},
"Yes": {
"accuracy": 55.49,
"count": 901,
"mean_confidence": 60.51
}
}
}
},
"calibration": {
"ms_home": {
"brier_score": 0.2054,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.026574",
"mean_predicted": 0.4942,
"mean_actual": 0.46
},
"ms_draw": {
"brier_score": 0.1846,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.030886",
"mean_predicted": 0.149,
"mean_actual": 0.2493
},
"ms_away": {
"brier_score": 0.1726,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.033980",
"mean_predicted": 0.3567,
"mean_actual": 0.2907
},
"ou15": {
"brier_score": 0.1884,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.037204",
"mean_predicted": 0.8714,
"mean_actual": 0.744
},
"ou25": {
"brier_score": 0.247,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.041152",
"mean_predicted": 0.6924,
"mean_actual": 0.499
},
"btts": {
"brier_score": 0.2453,
"calibration_error": 0.0,
"sample_count": 3000,
"last_trained": "2026-05-15T21:40:58.044344",
"mean_predicted": 0.4506,
"mean_actual": 0.5147
}
},
"runtime_seconds": 94.1
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,8 @@
{ {
"trained_at": "2026-04-14 17:20:03", "trained_at": "2026-05-06 15:53:36",
"market_results": { "market_results": {
"MS": { "MS": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -107,19 +107,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.8938, "xgb_accuracy": 0.5437,
"xgb_logloss": 0.2263, "xgb_logloss": 0.9429,
"lgb_accuracy": 0.8938, "lgb_accuracy": 0.5436,
"lgb_logloss": 0.2214, "lgb_logloss": 0.9423,
"ensemble_accuracy": 0.8945, "ensemble_accuracy": 0.5442,
"ensemble_logloss": 0.2226, "ensemble_logloss": 0.9418,
"class_count": 3 "class_count": 3
}, },
"OU15": { "OU15": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -224,19 +224,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.9088, "xgb_accuracy": 0.753,
"xgb_logloss": 0.1758, "xgb_logloss": 0.5256,
"lgb_accuracy": 0.9067, "lgb_accuracy": 0.7523,
"lgb_logloss": 0.1783, "lgb_logloss": 0.5262,
"ensemble_accuracy": 0.9108, "ensemble_accuracy": 0.7533,
"ensemble_logloss": 0.1753, "ensemble_logloss": 0.5254,
"class_count": 2 "class_count": 2
}, },
"OU25": { "OU25": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -341,19 +341,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.9204, "xgb_accuracy": 0.6253,
"xgb_logloss": 0.1535, "xgb_logloss": 0.635,
"lgb_accuracy": 0.9224, "lgb_accuracy": 0.6246,
"lgb_logloss": 0.1523, "lgb_logloss": 0.6347,
"ensemble_accuracy": 0.9217, "ensemble_accuracy": 0.6262,
"ensemble_logloss": 0.1518, "ensemble_logloss": 0.6343,
"class_count": 2 "class_count": 2
}, },
"OU35": { "OU35": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -458,19 +458,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.9578, "xgb_accuracy": 0.7283,
"xgb_logloss": 0.1171, "xgb_logloss": 0.5463,
"lgb_accuracy": 0.9564, "lgb_accuracy": 0.7304,
"lgb_logloss": 0.1144, "lgb_logloss": 0.546,
"ensemble_accuracy": 0.9571, "ensemble_accuracy": 0.7297,
"ensemble_logloss": 0.1149, "ensemble_logloss": 0.5456,
"class_count": 2 "class_count": 2
}, },
"BTTS": { "BTTS": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -575,19 +575,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.9238, "xgb_accuracy": 0.5894,
"xgb_logloss": 0.1439, "xgb_logloss": 0.6636,
"lgb_accuracy": 0.9265, "lgb_accuracy": 0.5928,
"lgb_logloss": 0.143, "lgb_logloss": 0.6633,
"ensemble_accuracy": 0.9265, "ensemble_accuracy": 0.5897,
"ensemble_logloss": 0.1424, "ensemble_logloss": 0.6628,
"class_count": 2 "class_count": 2
}, },
"HT_RESULT": { "HT_RESULT": {
"samples": 9786, "samples": 103208,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -692,19 +692,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6850, "train_samples": 72245,
"val_samples": 1468, "val_samples": 15481,
"test_samples": 1468, "test_samples": 15482,
"xgb_accuracy": 0.5627, "xgb_accuracy": 0.4695,
"xgb_logloss": 0.8712, "xgb_logloss": 1.0174,
"lgb_accuracy": 0.5715, "lgb_accuracy": 0.4677,
"lgb_logloss": 0.8649, "lgb_logloss": 1.0166,
"ensemble_accuracy": 0.5811, "ensemble_accuracy": 0.4688,
"ensemble_logloss": 0.8649, "ensemble_logloss": 1.0164,
"class_count": 3 "class_count": 3
}, },
"HT_OU05": { "HT_OU05": {
"samples": 9786, "samples": 103208,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -809,19 +809,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6850, "train_samples": 72245,
"val_samples": 1468, "val_samples": 15481,
"test_samples": 1468, "test_samples": 15482,
"xgb_accuracy": 0.7221, "xgb_accuracy": 0.7011,
"xgb_logloss": 0.5122, "xgb_logloss": 0.5939,
"lgb_accuracy": 0.7268, "lgb_accuracy": 0.7002,
"lgb_logloss": 0.5092, "lgb_logloss": 0.5936,
"ensemble_accuracy": 0.7275, "ensemble_accuracy": 0.7009,
"ensemble_logloss": 0.5084, "ensemble_logloss": 0.5932,
"class_count": 2 "class_count": 2
}, },
"HT_OU15": { "HT_OU15": {
"samples": 9786, "samples": 103208,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -926,19 +926,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6850, "train_samples": 72245,
"val_samples": 1468, "val_samples": 15481,
"test_samples": 1468, "test_samples": 15482,
"xgb_accuracy": 0.752, "xgb_accuracy": 0.6723,
"xgb_logloss": 0.5252, "xgb_logloss": 0.6126,
"lgb_accuracy": 0.7595, "lgb_accuracy": 0.6736,
"lgb_logloss": 0.5213, "lgb_logloss": 0.6118,
"ensemble_accuracy": 0.7595, "ensemble_accuracy": 0.6734,
"ensemble_logloss": 0.5192, "ensemble_logloss": 0.6117,
"class_count": 2 "class_count": 2
}, },
"HTFT": { "HTFT": {
"samples": 9786, "samples": 103208,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -1043,19 +1043,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6850, "train_samples": 72245,
"val_samples": 1468, "val_samples": 15481,
"test_samples": 1468, "test_samples": 15482,
"xgb_accuracy": 0.5136, "xgb_accuracy": 0.3337,
"xgb_logloss": 1.1384, "xgb_logloss": 1.8208,
"lgb_accuracy": 0.5184, "lgb_accuracy": 0.3332,
"lgb_logloss": 1.1469, "lgb_logloss": 1.8203,
"ensemble_accuracy": 0.5143, "ensemble_accuracy": 0.3358,
"ensemble_logloss": 1.1339, "ensemble_logloss": 1.8186,
"class_count": 9 "class_count": 9
}, },
"ODD_EVEN": { "ODD_EVEN": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -1160,19 +1160,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.8863, "xgb_accuracy": 0.5296,
"xgb_logloss": 0.3565, "xgb_logloss": 0.6841,
"lgb_accuracy": 0.8802, "lgb_accuracy": 0.5359,
"lgb_logloss": 0.3338, "lgb_logloss": 0.6822,
"ensemble_accuracy": 0.8863, "ensemble_accuracy": 0.531,
"ensemble_logloss": 0.3423, "ensemble_logloss": 0.6826,
"class_count": 2 "class_count": 2
}, },
"CARDS_OU45": { "CARDS_OU45": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -1277,19 +1277,19 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.6283, "xgb_accuracy": 0.6009,
"xgb_logloss": 0.6174, "xgb_logloss": 0.6489,
"lgb_accuracy": 0.6413, "lgb_accuracy": 0.5988,
"lgb_logloss": 0.615, "lgb_logloss": 0.6487,
"ensemble_accuracy": 0.6372, "ensemble_accuracy": 0.6024,
"ensemble_logloss": 0.6142, "ensemble_logloss": 0.6479,
"class_count": 2 "class_count": 2
}, },
"HANDICAP_MS": { "HANDICAP_MS": {
"samples": 9791, "samples": 106428,
"features_used": [ "features_used": [
"home_overall_elo", "home_overall_elo",
"away_overall_elo", "away_overall_elo",
@@ -1394,15 +1394,15 @@
"home_goals_form", "home_goals_form",
"away_goals_form" "away_goals_form"
], ],
"train_samples": 6853, "train_samples": 74499,
"val_samples": 1469, "val_samples": 15964,
"test_samples": 1469, "test_samples": 15965,
"xgb_accuracy": 0.936, "xgb_accuracy": 0.6058,
"xgb_logloss": 0.1903, "xgb_logloss": 0.8691,
"lgb_accuracy": 0.9346, "lgb_accuracy": 0.608,
"lgb_logloss": 0.1843, "lgb_logloss": 0.8677,
"ensemble_accuracy": 0.936, "ensemble_accuracy": 0.6068,
"ensemble_logloss": 0.1861, "ensemble_logloss": 0.8677,
"class_count": 3 "class_count": 3
} }
} }
@@ -0,0 +1,692 @@
{
"trained_at": "2026-05-20 21:06:17",
"trainer": "v25_pro",
"optuna_trials": 50,
"total_features": 152,
"markets": {
"MS": {
"market": "MS",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.01813533005537975,
"subsample": 0.6006077004659048,
"colsample_bytree": 0.5355789322633149,
"min_child_weight": 6,
"gamma": 1.3630627811027698e-08,
"reg_lambda": 0.000883795032251414,
"reg_alpha": 0.8575321662422171
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.020992610123700125,
"feature_fraction": 0.5951238173352622,
"bagging_fraction": 0.6452606057786048,
"bagging_freq": 5,
"min_child_samples": 16,
"lambda_l1": 0.2277044617439506,
"lambda_l2": 1.1531219311866832e-05
},
"xgb_best_iteration": 329,
"lgb_best_iteration": 171,
"xgb_optuna_best_logloss": 0.9665,
"lgb_optuna_best_logloss": 0.9662,
"test_xgb_raw": {
"accuracy": 0.5152,
"logloss": 0.9923
},
"test_xgb_calibrated": {
"accuracy": 0.5172,
"logloss": 0.9983
},
"test_lgb_raw": {
"accuracy": 0.5145,
"logloss": 0.9916
},
"test_lgb_calibrated": {
"accuracy": 0.5131,
"logloss": 0.995
},
"test_ensemble_raw": {
"accuracy": 0.5131,
"logloss": 0.9917
},
"test_ensemble_calibrated": {
"accuracy": 0.5139,
"logloss": 0.9949
}
},
"OU15": {
"market": "OU15",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 4,
"eta": 0.010007100652565513,
"subsample": 0.6423987996355262,
"colsample_bytree": 0.5399591700794417,
"min_child_weight": 9,
"gamma": 5.180190831403823e-06,
"reg_lambda": 0.04589987363138381,
"reg_alpha": 0.3607359437854735
},
"lgb_best_params": {
"max_depth": 5,
"learning_rate": 0.014982384401894706,
"feature_fraction": 0.5359420477684117,
"bagging_fraction": 0.7746019204010285,
"bagging_freq": 7,
"min_child_samples": 14,
"lambda_l1": 8.500808557234126e-07,
"lambda_l2": 2.536478147708652e-05
},
"xgb_best_iteration": 374,
"lgb_best_iteration": 351,
"xgb_optuna_best_logloss": 0.4945,
"lgb_optuna_best_logloss": 0.4939,
"test_xgb_raw": {
"accuracy": 0.7732,
"logloss": 0.5214
},
"test_xgb_calibrated": {
"accuracy": 0.7641,
"logloss": 0.5324
},
"test_lgb_raw": {
"accuracy": 0.7729,
"logloss": 0.5224
},
"test_lgb_calibrated": {
"accuracy": 0.7707,
"logloss": 0.5427
},
"test_ensemble_raw": {
"accuracy": 0.7732,
"logloss": 0.5216
},
"test_ensemble_calibrated": {
"accuracy": 0.7705,
"logloss": 0.5298
}
},
"OU25": {
"market": "OU25",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 4,
"eta": 0.01865626593315191,
"subsample": 0.7105443580121279,
"colsample_bytree": 0.6135695058254393,
"min_child_weight": 4,
"gamma": 3.0546982020131495e-05,
"reg_lambda": 9.868745464853797,
"reg_alpha": 0.011307758649267466
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.09100328259258599,
"feature_fraction": 0.8534286719238086,
"bagging_fraction": 0.8916028672163949,
"bagging_freq": 6,
"min_child_samples": 8,
"lambda_l1": 7.374385355858303e-06,
"lambda_l2": 1.1036250149900698e-07
},
"xgb_best_iteration": 273,
"lgb_best_iteration": 61,
"xgb_optuna_best_logloss": 0.6481,
"lgb_optuna_best_logloss": 0.648,
"test_xgb_raw": {
"accuracy": 0.5937,
"logloss": 0.6629
},
"test_xgb_calibrated": {
"accuracy": 0.5937,
"logloss": 0.6805
},
"test_lgb_raw": {
"accuracy": 0.5954,
"logloss": 0.6633
},
"test_lgb_calibrated": {
"accuracy": 0.5935,
"logloss": 0.6976
},
"test_ensemble_raw": {
"accuracy": 0.5946,
"logloss": 0.6628
},
"test_ensemble_calibrated": {
"accuracy": 0.5937,
"logloss": 0.6709
}
},
"OU35": {
"market": "OU35",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.0143268502217739,
"subsample": 0.8172822498674736,
"colsample_bytree": 0.506201148705069,
"min_child_weight": 1,
"gamma": 1.8443203495219952e-05,
"reg_lambda": 0.0011170943053842998,
"reg_alpha": 0.00020927636698484225
},
"lgb_best_params": {
"max_depth": 5,
"learning_rate": 0.020117886227998167,
"feature_fraction": 0.6208946683116914,
"bagging_fraction": 0.6562518216073271,
"bagging_freq": 5,
"min_child_samples": 45,
"lambda_l1": 0.00036062002131658313,
"lambda_l2": 3.05269304967777e-05
},
"xgb_best_iteration": 529,
"lgb_best_iteration": 137,
"xgb_optuna_best_logloss": 0.6145,
"lgb_optuna_best_logloss": 0.6143,
"test_xgb_raw": {
"accuracy": 0.6898,
"logloss": 0.5998
},
"test_xgb_calibrated": {
"accuracy": 0.6871,
"logloss": 0.6091
},
"test_lgb_raw": {
"accuracy": 0.6904,
"logloss": 0.6001
},
"test_lgb_calibrated": {
"accuracy": 0.6904,
"logloss": 0.6008
},
"test_ensemble_raw": {
"accuracy": 0.6905,
"logloss": 0.5998
},
"test_ensemble_calibrated": {
"accuracy": 0.6907,
"logloss": 0.6007
}
},
"BTTS": {
"market": "BTTS",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.021664948542761242,
"subsample": 0.7099186087768442,
"colsample_bytree": 0.7590713567483665,
"min_child_weight": 6,
"gamma": 5.5491508895403115e-06,
"reg_lambda": 4.796666289138789,
"reg_alpha": 0.00025842183659199874
},
"lgb_best_params": {
"max_depth": 8,
"learning_rate": 0.01933635083345398,
"feature_fraction": 0.7004575834832659,
"bagging_fraction": 0.6329726219722871,
"bagging_freq": 5,
"min_child_samples": 15,
"lambda_l1": 0.002608953323098078,
"lambda_l2": 0.0036308084751911743
},
"xgb_best_iteration": 255,
"lgb_best_iteration": 160,
"xgb_optuna_best_logloss": 0.6715,
"lgb_optuna_best_logloss": 0.6717,
"test_xgb_raw": {
"accuracy": 0.5743,
"logloss": 0.6781
},
"test_xgb_calibrated": {
"accuracy": 0.5743,
"logloss": 0.6872
},
"test_lgb_raw": {
"accuracy": 0.5746,
"logloss": 0.677
},
"test_lgb_calibrated": {
"accuracy": 0.5733,
"logloss": 0.7059
},
"test_ensemble_raw": {
"accuracy": 0.5766,
"logloss": 0.6772
},
"test_ensemble_calibrated": {
"accuracy": 0.5768,
"logloss": 0.6789
}
},
"HT_RESULT": {
"market": "HT_RESULT",
"samples": 31774,
"train": 19064,
"val": 4766,
"cal": 3177,
"test": 4767,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.010169971619421797,
"subsample": 0.6203740676748326,
"colsample_bytree": 0.5851318310393765,
"min_child_weight": 7,
"gamma": 2.3628372099543945e-05,
"reg_lambda": 2.2038789911767867e-05,
"reg_alpha": 1.4397584578583542e-08
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.016888148464727217,
"feature_fraction": 0.5434647939749242,
"bagging_fraction": 0.7069497418977599,
"bagging_freq": 3,
"min_child_samples": 12,
"lambda_l1": 0.0014508638064857065,
"lambda_l2": 0.00023588865029986223
},
"xgb_best_iteration": 553,
"lgb_best_iteration": 237,
"xgb_optuna_best_logloss": 1.035,
"lgb_optuna_best_logloss": 1.0349,
"test_xgb_raw": {
"accuracy": 0.4504,
"logloss": 1.0411
},
"test_xgb_calibrated": {
"accuracy": 0.4495,
"logloss": 1.0541
},
"test_lgb_raw": {
"accuracy": 0.4491,
"logloss": 1.0417
},
"test_lgb_calibrated": {
"accuracy": 0.4458,
"logloss": 1.0579
},
"test_ensemble_raw": {
"accuracy": 0.4498,
"logloss": 1.0413
},
"test_ensemble_calibrated": {
"accuracy": 0.447,
"logloss": 1.0572
}
},
"HT_OU05": {
"market": "HT_OU05",
"samples": 31774,
"train": 19064,
"val": 4766,
"cal": 3177,
"test": 4767,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.017924063045686854,
"subsample": 0.8434007408500664,
"colsample_bytree": 0.6701120513607601,
"min_child_weight": 4,
"gamma": 9.836792363275528e-07,
"reg_lambda": 1.7187465006176214e-05,
"reg_alpha": 3.014327116647346e-07
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.10422189537713934,
"feature_fraction": 0.5888797409273676,
"bagging_fraction": 0.7090975058803146,
"bagging_freq": 3,
"min_child_samples": 17,
"lambda_l1": 7.810412584545967e-07,
"lambda_l2": 0.009428914603962961
},
"xgb_best_iteration": 202,
"lgb_best_iteration": 35,
"xgb_optuna_best_logloss": 0.5704,
"lgb_optuna_best_logloss": 0.5698,
"test_xgb_raw": {
"accuracy": 0.7134,
"logloss": 0.5902
},
"test_xgb_calibrated": {
"accuracy": 0.713,
"logloss": 0.6081
},
"test_lgb_raw": {
"accuracy": 0.7143,
"logloss": 0.5915
},
"test_lgb_calibrated": {
"accuracy": 0.7145,
"logloss": 0.593
},
"test_ensemble_raw": {
"accuracy": 0.7134,
"logloss": 0.5906
},
"test_ensemble_calibrated": {
"accuracy": 0.7141,
"logloss": 0.5921
}
},
"HT_OU15": {
"market": "HT_OU15",
"samples": 31774,
"train": 19064,
"val": 4766,
"cal": 3177,
"test": 4767,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.017001754132211097,
"subsample": 0.6180909155642152,
"colsample_bytree": 0.6626651653816322,
"min_child_weight": 4,
"gamma": 1.481809088646707e-06,
"reg_lambda": 0.287499823474079,
"reg_alpha": 7.145401117237584e-06
},
"lgb_best_params": {
"max_depth": 8,
"learning_rate": 0.01064422725257703,
"feature_fraction": 0.6662850991902617,
"bagging_fraction": 0.7721868286695308,
"bagging_freq": 3,
"min_child_samples": 30,
"lambda_l1": 0.9857429592173135,
"lambda_l2": 3.960422300486119e-06
},
"xgb_best_iteration": 429,
"lgb_best_iteration": 345,
"xgb_optuna_best_logloss": 0.6435,
"lgb_optuna_best_logloss": 0.6435,
"test_xgb_raw": {
"accuracy": 0.6381,
"logloss": 0.6445
},
"test_xgb_calibrated": {
"accuracy": 0.6381,
"logloss": 0.6525
},
"test_lgb_raw": {
"accuracy": 0.6419,
"logloss": 0.6439
},
"test_lgb_calibrated": {
"accuracy": 0.6402,
"logloss": 0.7399
},
"test_ensemble_raw": {
"accuracy": 0.6404,
"logloss": 0.6439
},
"test_ensemble_calibrated": {
"accuracy": 0.6394,
"logloss": 0.6495
}
},
"HTFT": {
"market": "HTFT",
"samples": 31774,
"train": 19064,
"val": 4766,
"cal": 3177,
"test": 4767,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.013345313595371834,
"subsample": 0.9479953293996353,
"colsample_bytree": 0.6615608030428078,
"min_child_weight": 9,
"gamma": 7.455111012483033e-07,
"reg_lambda": 8.89911456381185,
"reg_alpha": 5.090564000963078e-06
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.013089530126848842,
"feature_fraction": 0.7906053881599783,
"bagging_fraction": 0.7440871339717291,
"bagging_freq": 4,
"min_child_samples": 44,
"lambda_l1": 2.0129746481016167e-05,
"lambda_l2": 9.879192474391198
},
"xgb_best_iteration": 366,
"lgb_best_iteration": 252,
"xgb_optuna_best_logloss": 1.8581,
"lgb_optuna_best_logloss": 1.858,
"test_xgb_raw": {
"accuracy": 0.3168,
"logloss": 1.8817
},
"test_xgb_calibrated": {
"accuracy": 0.3168,
"logloss": 1.9133
},
"test_lgb_raw": {
"accuracy": 0.317,
"logloss": 1.882
},
"test_lgb_calibrated": {
"accuracy": 0.3178,
"logloss": 1.9643
},
"test_ensemble_raw": {
"accuracy": 0.3172,
"logloss": 1.8815
},
"test_ensemble_calibrated": {
"accuracy": 0.3184,
"logloss": 1.9473
}
},
"ODD_EVEN": {
"market": "ODD_EVEN",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.0229001065742241,
"subsample": 0.6062940462474563,
"colsample_bytree": 0.6480436065939664,
"min_child_weight": 3,
"gamma": 0.0030457541200722615,
"reg_lambda": 7.476931580627848e-05,
"reg_alpha": 0.0009042146623162754
},
"lgb_best_params": {
"max_depth": 7,
"learning_rate": 0.04326506132261678,
"feature_fraction": 0.5682671758256829,
"bagging_fraction": 0.7270328719038639,
"bagging_freq": 3,
"min_child_samples": 16,
"lambda_l1": 8.217437741607322e-08,
"lambda_l2": 0.3516451122877441
},
"xgb_best_iteration": 83,
"lgb_best_iteration": 68,
"xgb_optuna_best_logloss": 0.6925,
"lgb_optuna_best_logloss": 0.6922,
"test_xgb_raw": {
"accuracy": 0.5017,
"logloss": 0.6938
},
"test_xgb_calibrated": {
"accuracy": 0.5053,
"logloss": 0.6944
},
"test_lgb_raw": {
"accuracy": 0.5069,
"logloss": 0.6962
},
"test_lgb_calibrated": {
"accuracy": 0.5048,
"logloss": 0.722
},
"test_ensemble_raw": {
"accuracy": 0.5003,
"logloss": 0.6944
},
"test_ensemble_calibrated": {
"accuracy": 0.5055,
"logloss": 0.6942
}
},
"CARDS_OU45": {
"market": "CARDS_OU45",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.05954573029858921,
"subsample": 0.9384590363195691,
"colsample_bytree": 0.7061263810064461,
"min_child_weight": 3,
"gamma": 0.12648985601647333,
"reg_lambda": 0.0005539202839193393,
"reg_alpha": 5.4993381294041956e-08
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.03822726574649208,
"feature_fraction": 0.5171942605576092,
"bagging_fraction": 0.9637281608315128,
"bagging_freq": 2,
"min_child_samples": 35,
"lambda_l1": 3.1166541263980415e-06,
"lambda_l2": 0.0004793052550782129
},
"xgb_best_iteration": 185,
"lgb_best_iteration": 338,
"xgb_optuna_best_logloss": 0.5583,
"lgb_optuna_best_logloss": 0.5582,
"test_xgb_raw": {
"accuracy": 0.5917,
"logloss": 0.6455
},
"test_xgb_calibrated": {
"accuracy": 0.6014,
"logloss": 0.6466
},
"test_lgb_raw": {
"accuracy": 0.5925,
"logloss": 0.6454
},
"test_lgb_calibrated": {
"accuracy": 0.6034,
"logloss": 0.6677
},
"test_ensemble_raw": {
"accuracy": 0.5917,
"logloss": 0.6451
},
"test_ensemble_calibrated": {
"accuracy": 0.6038,
"logloss": 0.6463
}
},
"HANDICAP_MS": {
"market": "HANDICAP_MS",
"samples": 34335,
"train": 20601,
"val": 5150,
"cal": 3433,
"test": 5151,
"features_used": 152,
"xgb_best_params": {
"max_depth": 3,
"eta": 0.016879778725504085,
"subsample": 0.6294239018550948,
"colsample_bytree": 0.6694819528702206,
"min_child_weight": 5,
"gamma": 2.9804488909930113e-05,
"reg_lambda": 0.2590856513676408,
"reg_alpha": 7.468057455332845e-06
},
"lgb_best_params": {
"max_depth": 3,
"learning_rate": 0.03267347269820124,
"feature_fraction": 0.5009468855100953,
"bagging_fraction": 0.7336123380897622,
"bagging_freq": 1,
"min_child_samples": 10,
"lambda_l1": 1.3880195780862006e-06,
"lambda_l2": 0.7568094447483327
},
"xgb_best_iteration": 406,
"lgb_best_iteration": 231,
"xgb_optuna_best_logloss": 0.907,
"lgb_optuna_best_logloss": 0.9064,
"test_xgb_raw": {
"accuracy": 0.5838,
"logloss": 0.9235
},
"test_xgb_calibrated": {
"accuracy": 0.5865,
"logloss": 0.9347
},
"test_lgb_raw": {
"accuracy": 0.5797,
"logloss": 0.9257
},
"test_lgb_calibrated": {
"accuracy": 0.5832,
"logloss": 0.9467
},
"test_ensemble_raw": {
"accuracy": 0.5818,
"logloss": 0.9244
},
"test_ensemble_calibrated": {
"accuracy": 0.5836,
"logloss": 0.9387
}
}
}
}
@@ -0,0 +1,19 @@
{
"version": "v26.shadow.0",
"calibration_version": "v26.shadow.calib.0",
"train_rows": 6853,
"validation_rows": 1469,
"label_priors": {
"MS": 0.4404,
"OU25": 0.5214,
"BTTS": 0.5398,
"HT": 0.4275,
"HTFT": 0.26,
"CARDS": 0.6052
},
"artifact_path": "/Users/piton/Documents/GitHub/iddaai/iddaai-be/ai-engine/models/v26_shadow/market_profiles.json",
"notes": [
"v26.shadow runtime currently uses artifact-based calibration and ROI gating",
"market profile JSON remains the source of truth for runtime thresholds"
]
}
+1
View File
@@ -17,3 +17,4 @@ pyyaml>=6.0
# V2 async database # V2 async database
asyncpg>=0.29.0 asyncpg>=0.29.0
pydantic>=2.5.0 pydantic>=2.5.0
pytest>=8.0.0
+40
View File
@@ -0,0 +1,40 @@
"""
MatchData dataclass core data transfer object used throughout the engine.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
@dataclass
class MatchData:
match_id: str
home_team_id: str
away_team_id: str
home_team_name: str
away_team_name: str
match_date_ms: int
sport: str
league_id: Optional[str]
league_name: str
referee_name: Optional[str]
odds_data: Dict[str, float]
home_lineup: Optional[List[str]]
away_lineup: Optional[List[str]]
sidelined_data: Optional[Dict[str, Any]]
home_goals_avg: float
home_conceded_avg: float
away_goals_avg: float
away_conceded_avg: float
home_position: int
away_position: int
lineup_source: str
status: str = ""
state: Optional[str] = None
substate: Optional[str] = None
current_score_home: Optional[int] = None
current_score_away: Optional[int] = None
lineup_confidence: float = 0.0
source_table: str = "matches"
+292
View File
@@ -0,0 +1,292 @@
"""
Shared prediction dataclasses used across the AI engine.
These were originally defined in models/v20_ensemble.py and are extracted here
so they can be used without importing the full V20 ensemble.
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from core.calculators.score_calculator import ScorePrediction
@dataclass
class MarketPrediction:
"""Prediction for a single betting market."""
market_type: str
pick: str
probability: float
confidence: float
odds: float = 0.0
is_recommended: bool = False
is_value_bet: bool = False
edge: float = 0.0 # Expected edge over market
def to_dict(self) -> dict:
return {
"market_type": self.market_type,
"pick": self.pick,
"probability": round(self.probability * 100, 1),
"confidence": round(self.confidence, 1),
"odds": self.odds,
"is_recommended": self.is_recommended,
"is_value_bet": self.is_value_bet,
"edge": round(self.edge, 1)
}
@dataclass
class FullMatchPrediction:
"""Complete prediction for a match with ALL markets."""
match_id: str
home_team: str
away_team: str
match_date: str = ""
# === MAÇ SONUCU (1X2) ===
ms_home_prob: float = 0.33
ms_draw_prob: float = 0.33
ms_away_prob: float = 0.33
ms_pick: str = ""
ms_confidence: float = 0.0
# === ÇİFTE ŞANS ===
dc_1x_prob: float = 0.66
dc_x2_prob: float = 0.66
dc_12_prob: float = 0.66
dc_pick: str = ""
dc_confidence: float = 0.0
# === ALT/ÜST GOLLER ===
# 1.5
over_15_prob: float = 0.70
under_15_prob: float = 0.30
ou15_pick: str = ""
ou15_confidence: float = 0.0
# 2.5
over_25_prob: float = 0.50
under_25_prob: float = 0.50
ou25_pick: str = ""
ou25_confidence: float = 0.0
# 3.5
over_35_prob: float = 0.30
under_35_prob: float = 0.70
ou35_pick: str = ""
ou35_confidence: float = 0.0
# === KARŞILIKLI GOL (BTTS) ===
btts_yes_prob: float = 0.50
btts_no_prob: float = 0.50
btts_pick: str = ""
btts_confidence: float = 0.0
# === İLK YARI SONUCU ===
ht_home_prob: float = 0.30
ht_draw_prob: float = 0.40
ht_away_prob: float = 0.30
ht_pick: str = ""
ht_confidence: float = 0.0
# === SKOR TAHMİNLERİ ===
score: Optional[ScorePrediction] = None
predicted_ft_score: str = "1-1"
predicted_ht_score: str = "0-0"
ft_scores_top5: List[Dict] = field(default_factory=list)
# === xG (Expected Goals) ===
home_xg: float = 1.3
away_xg: float = 1.1
total_xg: float = 2.4
# === RISK DEĞERLENDİRMESİ ===
risk_level: str = "MEDIUM" # LOW, MEDIUM, HIGH, EXTREME
risk_score: float = 0.0
is_surprise_risk: bool = False
surprise_type: str = ""
risk_warnings: List[str] = field(default_factory=list)
ht_ft_probs: Dict[str, float] = field(default_factory=dict)
# === GLM-5 SÜRPRİZ SKORU ===
upset_score: int = 0 # 0-100 arası sürpriz skoru
upset_level: str = "LOW" # LOW, MEDIUM, HIGH, EXTREME
upset_reasons: List[str] = field(default_factory=list)
# === SÜRPRİZ PROFİLİ ===
surprise_score: float = 0.0 # 0-100 overall surprise risk score
surprise_comment: str = "" # Human-readable surprise commentary
surprise_reasons: List[str] = field(default_factory=list) # Flagged risk reasons
surprise_breakdown: List[Dict[str, Any]] = field(default_factory=list) # Per-factor {code, points, label}
# === ENGINE KATKILARI ===
team_confidence: float = 0.0
player_confidence: float = 0.0
odds_confidence: float = 0.0
referee_confidence: float = 0.0
# === KORNER & KART & DİĞER ===
total_corners_pred: float = 9.5
corner_pick: str = "9.5 Üst"
total_cards_pred: float = 4.5
card_pick: str = "4.5 Alt"
cards_over_prob: float = 0.50
cards_under_prob: float = 0.50
cards_confidence: float = 0.0
handicap_pick: str = ""
handicap_home_prob: float = 0.33
handicap_draw_prob: float = 0.34
handicap_away_prob: float = 0.33
handicap_confidence: float = 0.0
ht_over_05_prob: float = 0.65
ht_under_05_prob: float = 0.35
ht_over_15_prob: float = 0.30
ht_under_15_prob: float = 0.70
ht_ou_pick: str = "İY 0.5 Üst"
ht_ou15_pick: str = "İY 1.5 Alt"
odd_even_pick: str = "Çift"
odd_prob: float = 0.50 # Tek olasılığı
even_prob: float = 0.50 # Çift olasılığı
# === TAVSİYELER (RECOMMENDATIONS) ===
best_bet: Optional[MarketPrediction] = None
recommended_bets: List[MarketPrediction] = field(default_factory=list)
alternative_bet: Optional[MarketPrediction] = None
expert_recommendation: Dict[str, Any] = field(default_factory=dict)
# === DETAILED ANALYSIS ===
analysis_details: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict:
return {
"match_info": {
"match_id": self.match_id,
"home_team": self.home_team,
"away_team": self.away_team,
"match_date": self.match_date
},
"predictions": {
"match_result": {
"1": round(self.ms_home_prob * 100, 1),
"X": round(self.ms_draw_prob * 100, 1),
"2": round(self.ms_away_prob * 100, 1),
"pick": self.ms_pick,
"confidence": round(self.ms_confidence, 1)
},
"double_chance": {
"1X": round(self.dc_1x_prob * 100, 1),
"X2": round(self.dc_x2_prob * 100, 1),
"12": round(self.dc_12_prob * 100, 1),
"pick": self.dc_pick,
"confidence": round(self.dc_confidence, 1)
},
"over_under": {
"1.5": {
"over": round(self.over_15_prob * 100, 1),
"under": round(self.under_15_prob * 100, 1),
"pick": self.ou15_pick,
"confidence": round(self.ou15_confidence, 1)
},
"2.5": {
"over": round(self.over_25_prob * 100, 1),
"under": round(self.under_25_prob * 100, 1),
"pick": self.ou25_pick,
"confidence": round(self.ou25_confidence, 1)
},
"3.5": {
"over": round(self.over_35_prob * 100, 1),
"under": round(self.under_35_prob * 100, 1),
"pick": self.ou35_pick,
"confidence": round(self.ou35_confidence, 1)
}
},
"btts": {
"yes": round(self.btts_yes_prob * 100, 1),
"no": round(self.btts_no_prob * 100, 1),
"pick": self.btts_pick,
"confidence": round(self.btts_confidence, 1)
},
"first_half": {
"1": round(self.ht_home_prob * 100, 1),
"X": round(self.ht_draw_prob * 100, 1),
"2": round(self.ht_away_prob * 100, 1),
"pick": self.ht_pick,
"confidence": round(self.ht_confidence, 1),
"over_under_05": {
"over": round(self.ht_over_05_prob * 100, 1),
"under": round(self.ht_under_05_prob * 100, 1),
"pick": self.ht_ou_pick
},
"over_under_15": {
"over": round(self.ht_over_15_prob * 100, 1),
"under": round(self.ht_under_15_prob * 100, 1),
"pick": self.ht_ou15_pick
}
},
"scores": {
"predicted_ft": self.predicted_ft_score,
"predicted_ht": self.predicted_ht_score,
"top_5_ft_scores": self.ft_scores_top5
},
"others": {
"handicap": {
"pick": self.handicap_pick,
"confidence": round(self.handicap_confidence, 1),
"home": round(self.handicap_home_prob * 100, 1),
"draw": round(self.handicap_draw_prob * 100, 1),
"away": round(self.handicap_away_prob * 100, 1)
},
"corners": {
"total": round(self.total_corners_pred, 1),
"pick": self.corner_pick
},
"cards": {
"total": round(self.total_cards_pred, 1),
"pick": self.card_pick,
"confidence": round(self.cards_confidence, 1),
"over": round(self.cards_over_prob * 100, 1),
"under": round(self.cards_under_prob * 100, 1)
},
"odd_even": {
"pick": self.odd_even_pick,
"tek": round(self.odd_prob * 100, 1),
"cift": round(self.even_prob * 100, 1)
}
},
"xg": {
"home": round(self.home_xg, 2),
"away": round(self.away_xg, 2),
"total": round(self.total_xg, 2)
}
},
"risk": {
"level": self.risk_level,
"score": round(self.risk_score, 1),
"is_surprise_risk": self.is_surprise_risk,
"surprise_type": self.surprise_type,
"ht_ft_probs": {k: round(v * 100, 1) for k, v in self.ht_ft_probs.items()} if self.ht_ft_probs else {},
"warnings": self.risk_warnings
},
"upset_analysis": {
"score": self.upset_score,
"level": self.upset_level,
"reasons": self.upset_reasons
},
"engine_breakdown": {
"team_engine": round(self.team_confidence, 1),
"player_engine": round(self.player_confidence, 1),
"odds_engine": round(self.odds_confidence, 1),
"referee_engine": round(self.referee_confidence, 1)
},
"recommendations": {
"best_bet": self.best_bet.to_dict() if self.best_bet else None,
"all_recommended": [b.to_dict() for b in self.recommended_bets] if self.recommended_bets else [],
"alternative_bet": self.alternative_bet.to_dict() if self.alternative_bet else None
},
"analysis_details": self.analysis_details
}
+510
View File
@@ -0,0 +1,510 @@
"""
Calibration Backfill Script
============================
Runs V25 model against historical matches (using pre-computed ai_features + odds)
to generate calibration training data, then trains isotonic calibration models.
Usage:
python ai-engine/scripts/backfill_calibration.py
python ai-engine/scripts/backfill_calibration.py --limit 5000
python ai-engine/scripts/backfill_calibration.py --min-samples 50
"""
import argparse
import json
import os
import sys
import time
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
import psycopg2
from psycopg2.extras import RealDictCursor
from dotenv import load_dotenv
AI_ENGINE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, AI_ENGINE_DIR)
from models.v25_ensemble import V25Predictor
from models.calibration import get_calibrator
load_dotenv()
def _normalize_pick(pick) -> str:
return str(pick or "").strip().casefold()
def resolve_actual(market, pick, score_home, score_away, ht_home, ht_away):
if score_home is None or score_away is None:
return None
market = (market or "").upper()
p = _normalize_pick(pick)
total = score_home + score_away
ht_total = (ht_home or 0) + (ht_away or 0) if ht_home is not None else None
if market == "MS":
if p == "1": return int(score_home > score_away)
if p in {"x", "0"}: return int(score_home == score_away)
if p == "2": return int(score_away > score_home)
return None
if market in {"OU15", "OU25", "OU35"}:
line = {"OU15": 1.5, "OU25": 2.5, "OU35": 3.5}[market]
if "over" in p or "üst" in p or "ust" in p: return int(total > line)
if "under" in p or "alt" in p: return int(total < line)
return None
if market == "BTTS":
both = score_home > 0 and score_away > 0
if "yes" in p or "var" in p: return int(both)
if "no" in p or "yok" in p: return int(not both)
return None
if market == "HT":
if ht_home is None or ht_away is None: return None
if p == "1": return int(ht_home > ht_away)
if p in {"x", "0"}: return int(ht_home == ht_away)
if p == "2": return int(ht_away > ht_home)
return None
if market == "HTFT":
if ht_home is None or ht_away is None or "/" not in p: return None
ht_p, ft_p = p.split("/")
ht_actual = "1" if ht_home > ht_away else "2" if ht_away > ht_home else "x"
ft_actual = "1" if score_home > score_away else "2" if score_away > score_home else "x"
return int(ht_p.strip() == ht_actual and ft_p.strip() == ft_actual)
if market == "DC":
norm = p.replace("-", "").upper()
if norm == "1X": return int(score_home >= score_away)
if norm == "X2": return int(score_away >= score_home)
if norm == "12": return int(score_home != score_away)
return None
return None
def calibrator_key(market, pick):
m = (market or "").upper()
p = _normalize_pick(pick)
if m == "MS":
if p == "1": return "ms_home"
if p in {"x", "0"}: return "ms_draw"
if p == "2": return "ms_away"
return None
if m == "DC": return "dc"
if m == "OU15" and ("over" in p or "üst" in p): return "ou15"
if m == "OU25" and ("over" in p or "üst" in p): return "ou25"
if m == "OU35" and ("over" in p or "üst" in p): return "ou35"
if m == "BTTS" and ("yes" in p or "var" in p): return "btts"
if m == "HT":
if p == "1": return "ht_home"
if p in {"x", "0"}: return "ht_draw"
if p == "2": return "ht_away"
return None
if m == "HTFT": return "ht_ft"
return None
def get_conn():
db_url = os.getenv("DATABASE_URL", "")
if "?schema=" in db_url:
db_url = db_url.split("?schema=")[0]
if not db_url:
raise ValueError("DATABASE_URL not set")
return psycopg2.connect(db_url, cursor_factory=RealDictCursor)
ODD_CAT_MAP = {
"maç sonucu": {"1": "ms_h", "0": "ms_d", "x": "ms_d", "2": "ms_a"},
"1. yarı sonucu": {"1": "ht_ms_h", "0": "ht_ms_d", "x": "ht_ms_d", "2": "ht_ms_a"},
}
ODD_CAT_KEYWORD_MAP = {
"karşılıklı gol": {"var": "btts_y", "yok": "btts_n"},
"0,5 alt/üst": {"alt": "ou05_u", "üst": "ou05_o"},
"1,5 alt/üst": {"alt": "ou15_u", "üst": "ou15_o"},
"2,5 alt/üst": {"alt": "ou25_u", "üst": "ou25_o"},
"3,5 alt/üst": {"alt": "ou35_u", "üst": "ou35_o"},
"ilk yarı 0,5 alt/üst": {"alt": "ht_ou05_u", "üst": "ht_ou05_o"},
"ilk yarı 1,5 alt/üst": {"alt": "ht_ou15_u", "üst": "ht_ou15_o"},
}
def load_matches(cur, limit: int) -> List[Dict]:
cur.execute("""
SELECT m.id, m.score_home, m.score_away,
m.ht_score_home, m.ht_score_away
FROM matches m
JOIN football_ai_features f ON f.match_id = m.id
WHERE m.status = 'FT'
AND m.sport = 'football'
AND m.score_home IS NOT NULL
AND m.score_away IS NOT NULL
ORDER BY m.mst_utc DESC
LIMIT %s
""", (limit,))
return cur.fetchall()
def load_ai_features_batch(cur, match_ids: List[str]) -> Dict[str, Dict]:
if not match_ids:
return {}
ph = ",".join(["%s"] * len(match_ids))
cur.execute(f"""
SELECT match_id,
home_elo AS home_overall_elo,
away_elo AS away_overall_elo,
elo_diff,
home_home_elo, away_away_elo,
home_form_elo, away_form_elo,
(home_form_elo - away_form_elo) AS form_elo_diff,
home_goals_avg_5 AS home_goals_avg,
home_conceded_avg_5 AS home_conceded_avg,
away_goals_avg_5 AS away_goals_avg,
away_conceded_avg_5 AS away_conceded_avg,
home_clean_sheet_rate, away_clean_sheet_rate,
home_scoring_rate, away_scoring_rate,
home_win_streak AS home_winning_streak,
away_win_streak AS away_winning_streak,
0 AS home_unbeaten_streak,
0 AS away_unbeaten_streak,
h2h_total AS h2h_total_matches,
h2h_home_win_rate,
(1.0 - h2h_home_win_rate - 0.33) AS h2h_draw_rate,
h2h_avg_goals,
h2h_btts_rate, h2h_over25_rate,
home_avg_possession, away_avg_possession,
home_avg_shots_on_target, away_avg_shots_on_target,
home_shot_conversion, away_shot_conversion,
0.0 AS home_avg_corners, 0.0 AS away_avg_corners,
implied_home, implied_draw, implied_away,
league_avg_goals,
0.0 AS league_zero_goal_rate,
0.0 AS home_xga, 0.0 AS away_xga,
0.0 AS upset_atmosphere, 0.0 AS upset_motivation,
0.0 AS upset_fatigue, 0.0 AS upset_potential,
referee_home_bias, referee_avg_goals,
referee_avg_cards AS referee_cards_total,
0.0 AS referee_avg_yellow,
0.0 AS referee_experience,
0.0 AS home_momentum_score, 0.0 AS away_momentum_score,
0.0 AS momentum_diff,
0.0 AS home_squad_quality, 0.0 AS away_squad_quality,
0.0 AS squad_diff,
0 AS home_key_players, 0 AS away_key_players,
missing_players_impact AS home_missing_impact,
0.0 AS away_missing_impact,
home_goals_avg_5 AS home_goals_form,
away_goals_avg_5 AS away_goals_form
FROM football_ai_features
WHERE match_id IN ({ph})
""", match_ids)
return {str(row["match_id"]): dict(row) for row in cur.fetchall()}
def load_odds_batch(cur, match_ids: List[str]) -> Dict[str, Dict[str, float]]:
if not match_ids:
return {}
ph = ",".join(["%s"] * len(match_ids))
cur.execute(f"""
SELECT oc.match_id, oc.name AS cat_name,
os.name AS sel_name, os.odd_value
FROM odd_selections os
JOIN odd_categories oc ON os.odd_category_db_id = oc.db_id
WHERE oc.match_id IN ({ph})
""", match_ids)
odds: Dict[str, Dict[str, float]] = {}
for row in cur.fetchall():
mid = str(row["match_id"])
cat = (row["cat_name"] or "").lower().strip()
sel = (row["sel_name"] or "").strip()
val = float(row["odd_value"]) if row["odd_value"] else 0
if val <= 0:
continue
if mid not in odds:
odds[mid] = {}
if cat in ODD_CAT_MAP:
key = ODD_CAT_MAP[cat].get(sel.lower())
if key:
odds[mid][key] = val
else:
for cat_pattern, kw_map in ODD_CAT_KEYWORD_MAP.items():
if cat == cat_pattern:
for keyword, key in kw_map.items():
if keyword in sel.lower():
odds[mid][key] = val
break
return odds
MARKETS_TO_PREDICT = [
("MS", "1", lambda p: p[0]),
("MS", "X", lambda p: p[1]),
("MS", "2", lambda p: p[2]),
("OU25", "Over 2.5", lambda p: p[0]),
("BTTS", "Yes", lambda p: p[0]),
("OU15", "Over 1.5", lambda p: p[0]),
("OU35", "Over 3.5", lambda p: p[0]),
("HT", "1", lambda p: p[0]),
("HT", "X", lambda p: p[1]),
("HT", "2", lambda p: p[2]),
]
def run_backfill(args):
print("=" * 70)
print("CALIBRATION BACKFILL")
print("=" * 70)
conn = get_conn()
cur = conn.cursor(cursor_factory=RealDictCursor)
t0 = time.time()
print(f"Loading matches (limit={args.limit})...")
matches = load_matches(cur, args.limit)
print(f" Found {len(matches)} finished matches with ai_features")
match_ids = [str(m["id"]) for m in matches]
match_map = {str(m["id"]): m for m in matches}
print("Loading ai_features...")
features_map = load_ai_features_batch(cur, match_ids)
print(f" Loaded features for {len(features_map)} matches")
print("Loading odds...")
odds_map = load_odds_batch(cur, match_ids)
print(f" Loaded odds for {len(odds_map)} matches")
print(f"Data loading: {time.time() - t0:.1f}s")
print("\nLoading V25 model...")
predictor = V25Predictor()
predictor.load_models()
feature_cols = predictor.FEATURE_COLS
samples: List[Dict[str, Any]] = []
skipped = 0
processed = 0
print(f"\nRunning predictions on {len(match_ids)} matches...")
t1 = time.time()
for i, mid in enumerate(match_ids):
if mid not in features_map:
skipped += 1
continue
feat_row = features_map[mid]
odds_row = odds_map.get(mid, {})
match_row = match_map[mid]
feat_dict = {}
for col in feature_cols:
if col in feat_row and feat_row[col] is not None:
feat_dict[col] = float(feat_row[col])
elif col.startswith("odds_") and not col.endswith("_present"):
odds_key = col.replace("odds_", "")
feat_dict[col] = float(odds_row.get(odds_key, 0))
elif col.endswith("_present"):
base = col.replace("_present", "")
odds_key = base.replace("odds_", "")
feat_dict[col] = 1.0 if odds_row.get(odds_key, 0) > 0 else 0.0
else:
feat_dict[col] = 0.0
if odds_row.get("ms_h", 0) > 0:
feat_dict["odds_ms_h"] = odds_row["ms_h"]
if odds_row.get("ms_d", 0) > 0:
feat_dict["odds_ms_d"] = odds_row["ms_d"]
if odds_row.get("ms_a", 0) > 0:
feat_dict["odds_ms_a"] = odds_row["ms_a"]
ms_h = feat_dict.get("odds_ms_h", 0)
ms_d = feat_dict.get("odds_ms_d", 0)
ms_a = feat_dict.get("odds_ms_a", 0)
if ms_h > 0 and ms_d > 0 and ms_a > 0:
raw_sum = 1/ms_h + 1/ms_d + 1/ms_a
feat_dict["implied_home"] = (1/ms_h) / raw_sum
feat_dict["implied_draw"] = (1/ms_d) / raw_sum
feat_dict["implied_away"] = (1/ms_a) / raw_sum
sh = match_row["score_home"]
sa = match_row["score_away"]
ht_h = match_row.get("ht_score_home")
ht_a = match_row.get("ht_score_away")
try:
X = pd.DataFrame([{c: feat_dict.get(c, 0.0) for c in feature_cols}])
for market_name, model_key, market_list in [
("ms", "ms", ["MS"]),
("ou25", "ou25", ["OU25"]),
("btts", "btts", ["BTTS"]),
("ou15", "ou15", ["OU15"]),
("ou35", "ou35", ["OU35"]),
("ht_result", "ht_result", ["HT"]),
]:
if model_key not in predictor.models:
continue
probs = predictor.predict_market(model_key, feat_dict)
if probs is None:
continue
if model_key == "ms":
for pick, prob in [("1", probs[0]), ("X", probs[1]), ("2", probs[2])]:
actual = resolve_actual("MS", pick, sh, sa, ht_h, ht_a)
key = calibrator_key("MS", pick)
if actual is not None and key:
samples.append({
"match_id": mid,
"market": "MS",
"pick": pick,
"key": key,
"raw_prob": float(prob),
"actual": int(actual),
})
elif model_key == "ht_result":
if ht_h is None or ht_a is None:
continue
for pick, prob in [("1", probs[0]), ("X", probs[1]), ("2", probs[2])]:
actual = resolve_actual("HT", pick, sh, sa, ht_h, ht_a)
key = calibrator_key("HT", pick)
if actual is not None and key:
samples.append({
"match_id": mid,
"market": "HT",
"pick": pick,
"key": key,
"raw_prob": float(prob),
"actual": int(actual),
})
elif model_key in ("ou25", "ou15", "ou35"):
market_upper = model_key.upper()
over_prob = float(probs[0]) if len(probs) > 0 else 0.5
pick = f"Over"
actual = resolve_actual(market_upper, "Over", sh, sa, ht_h, ht_a)
key = calibrator_key(market_upper, "Over")
if actual is not None and key:
samples.append({
"match_id": mid,
"market": market_upper,
"pick": pick,
"key": key,
"raw_prob": over_prob,
"actual": int(actual),
})
elif model_key == "btts":
yes_prob = float(probs[0]) if len(probs) > 0 else 0.5
actual = resolve_actual("BTTS", "Yes", sh, sa, ht_h, ht_a)
key = calibrator_key("BTTS", "Yes")
if actual is not None and key:
samples.append({
"match_id": mid,
"market": "BTTS",
"pick": "Yes",
"key": key,
"raw_prob": yes_prob,
"actual": int(actual),
})
processed += 1
except Exception as e:
skipped += 1
if skipped <= 5:
print(f" Error on {mid}: {e}")
if (i + 1) % 5000 == 0:
elapsed = time.time() - t1
rate = (i + 1) / elapsed
print(f" Processed {i+1}/{len(match_ids)} ({rate:.0f} matches/s)")
elapsed = time.time() - t1
print(f"\nPrediction complete: {processed} matches, {skipped} skipped, {elapsed:.1f}s")
if not samples:
print("No calibration samples generated!")
cur.close()
conn.close()
return
df = pd.DataFrame(samples)
print(f"\nTotal calibration samples: {len(df)}")
print(f"Unique matches: {df['match_id'].nunique()}")
print(f"\nPer-key counts:")
for key, count in df["key"].value_counts().items():
print(f" {key:<14} {count}")
print(f"\nTraining isotonic calibration models (min_samples={args.min_samples})...")
calibrator = get_calibrator()
results: Dict[str, Any] = {}
keys = sorted(df["key"].unique())
for key in keys:
sub = df[df["key"] == key].copy()
sub = sub.drop_duplicates(subset=["match_id", "key"], keep="first")
sub = sub.dropna(subset=["raw_prob", "actual"])
sub = sub[(sub["raw_prob"] > 0.0) & (sub["raw_prob"] < 1.0)]
n = len(sub)
if n < args.min_samples:
results[key] = {"status": "skipped", "samples": n}
continue
metrics = calibrator.train_calibration(
df=sub,
market=key,
prob_col="raw_prob",
actual_col="actual",
min_samples=args.min_samples,
save=True,
)
results[key] = {
"status": "trained",
"samples": metrics.sample_count,
"brier": round(metrics.brier_score, 4),
"ece": round(metrics.calibration_error, 4),
"mean_predicted": round(metrics.mean_predicted, 4),
"mean_actual": round(metrics.mean_actual, 4),
}
print("\n" + "=" * 70)
print("CALIBRATION RESULTS")
print("=" * 70)
print(f"{'market':<14} {'status':<10} {'n':<8} {'brier':<9} {'ece':<8} {'pred_avg':<9} {'actual_avg'}")
print("-" * 70)
for key, info in sorted(results.items()):
if info["status"] == "trained":
print(
f"{key:<14} {'OK':<10} {info['samples']:<8} "
f"{info['brier']:<9.4f} {info['ece']:<8.4f} "
f"{info['mean_predicted']:<9.4f} {info['mean_actual']}"
)
else:
print(f"{key:<14} {'SKIP':<10} {info['samples']:<8}")
print("=" * 70)
total_time = time.time() - t0
print(f"\nTotal time: {total_time:.1f}s")
print(f"Calibration models saved to: {os.path.join(AI_ENGINE_DIR, 'models', 'calibration')}/")
cur.close()
conn.close()
def main():
parser = argparse.ArgumentParser(description="Backfill calibration from historical matches")
parser.add_argument("--limit", type=int, default=50000,
help="Max matches to process (default: 50000)")
parser.add_argument("--min-samples", type=int, default=100,
help="Min samples per market for calibration (default: 100)")
args = parser.parse_args()
run_backfill(args)
if __name__ == "__main__":
main()
+352
View File
@@ -0,0 +1,352 @@
"""
Tutarsızlık Bazlı Backtest
============================
Modeller arası tutarsızlığı ölçer, tutarlı maçlarda bahis açılsaydı
ROI ne olurdu hesaplar.
Mantık:
- Her maç için market'ler arası çelişkileri tespit et
- Tutarsız maçları filtrele
- Tutarlı maçlarda hit rate ve ROI hesapla
Usage:
python scripts/backtest_consistency.py
"""
import os, sys, json
import numpy as np
import pandas as pd
import xgboost as xgb
from sklearn.metrics import accuracy_score
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'data', 'training_data.csv')
MODELS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'models', 'v25')
SKIP_COLS = {
'match_id','home_team_id','away_team_id','league_id','mst_utc',
'score_home','score_away','total_goals','ht_score_home','ht_score_away','ht_total_goals',
'label_ms','label_ou05','label_ou15','label_ou25','label_ou35','label_btts',
'label_ht_result','label_ht_ou05','label_ht_ou15','label_ht_ft',
'label_odd_even','label_yellow_cards','label_cards_ou45','label_handicap_ms',
}
def load_model(market: str):
path = os.path.join(MODELS_DIR, f'xgb_v25_{market}.json')
if not os.path.exists(path):
return None
b = xgb.Booster()
b.load_model(path)
return b
def predict_proba(model, X: np.ndarray, feature_cols: list, n_class: int):
dmat = xgb.DMatrix(pd.DataFrame(X, columns=feature_cols))
raw = model.predict(dmat)
if n_class > 2:
return raw.reshape(-1, n_class)
return np.column_stack([1 - raw, raw])
def consistency_score(probs: dict) -> tuple[float, list]:
"""
Market'ler arası tutarsızlığı hesapla.
0 = tamamen tutarlı, 1 = tamamen çelişkili.
Kontrol edilen çelişkiler:
1. OU15 üst yüksek ama OU25 üst de yüksek ok
OU15 üst yüksek ama OU25 alt yüksek ÇELISKI (1 gol bekleniyor ama 2.5+ da bekleniyor?)
2. HT_OU05 üst yüksek ama HT sonucu draw yüksek ÇELISKI
3. OU35 üst yüksek ama BTTS düşük şüpheli
4. MS home yüksek ama HT away yüksek çelişkili
"""
conflicts = []
total_weight = 0
total_conflict = 0
# OU tutarlılığı: P(OU25>0.5) <= P(OU15>0.5) matematiksel zorunluluk
ou15_over = probs.get('ou15_over', 0.5)
ou25_over = probs.get('ou25_over', 0.5)
ou35_over = probs.get('ou35_over', 0.5)
# OU hiyerarşisi: ou35 <= ou25 <= ou15 olmalı
if ou25_over > ou15_over + 0.05:
gap = ou25_over - ou15_over
conflicts.append(f'OU25>{ou25_over:.0%} > OU15>{ou15_over:.0%} (imkansız)')
total_conflict += gap * 2
total_weight += 1
if ou35_over > ou25_over + 0.05:
gap = ou35_over - ou25_over
conflicts.append(f'OU35>{ou35_over:.0%} > OU25>{ou25_over:.0%} (imkansız)')
total_conflict += gap * 2
total_weight += 1
# HT_OU05 ve HT sonuç tutarlılığı
ht_ou05_over = probs.get('ht_ou05_over', 0.5)
ht_draw_prob = probs.get('ht_draw', 0.34)
# İlk yarıda gol bekleniyor ama beraberlik de bekleniyor (0-0 draw?)
# HT_OU05 >%70 ama HT draw >%50 → çelişkili (0-0 berabere çok?)
if ht_ou05_over > 0.70 and ht_draw_prob > 0.50:
conflict = min(ht_ou05_over - 0.5, ht_draw_prob - 0.4)
conflicts.append(f'HT_OU05>{ht_ou05_over:.0%} ama HT_Draw>{ht_draw_prob:.0%}')
total_conflict += conflict
total_weight += 1
# HT_OU05 ve HT_OU15 tutarlılığı
ht_ou15_over = probs.get('ht_ou15_over', 0.3)
if ht_ou15_over > ht_ou05_over + 0.05:
gap = ht_ou15_over - ht_ou05_over
conflicts.append(f'HT_OU15>{ht_ou15_over:.0%} > HT_OU05>{ht_ou05_over:.0%} (imkansız)')
total_conflict += gap * 2
total_weight += 1
# MS ve OU tutarlılığı
ms_home = probs.get('ms_home', 0.33)
ms_away = probs.get('ms_away', 0.33)
btts_yes = probs.get('btts_yes', 0.5)
# Tek takım galibiyeti kuvvetli ama BTTS yüksek → şüpheli
dominant = max(ms_home, ms_away)
if dominant > 0.65 and btts_yes > 0.65:
conflict = (dominant - 0.5) * (btts_yes - 0.5)
conflicts.append(f'MS dominant>{dominant:.0%} ama BTTS_Yes>{btts_yes:.0%}')
total_conflict += conflict * 0.5
total_weight += 1
# OU25 ve BTTS tutarlılığı
# BTTS yüksekse en az 2 gol → OU25 üst de yüksek olmalı
if btts_yes > 0.65 and ou25_over < 0.45:
conflict = btts_yes - ou25_over
conflicts.append(f'BTTS_Yes>{btts_yes:.0%} ama OU25>{ou25_over:.0%} düşük')
total_conflict += conflict
total_weight += 1
# OU35 üst yüksek ama BTTS düşük → şüpheli (3+ gol ama tek takım mı?)
if ou35_over > 0.45 and btts_yes < 0.40:
conflict = (ou35_over - 0.35) * (0.5 - btts_yes)
conflicts.append(f'OU35>{ou35_over:.0%} ama BTTS_Yes<{btts_yes:.0%}')
total_conflict += conflict
total_weight += 1
score = min(1.0, total_conflict / max(total_weight * 0.3, 0.1))
return score, conflicts
def main():
print('Loading data...')
df = pd.read_csv(DATA_PATH, low_memory=False)
# Son %20 = test seti (kronolojik)
df = df.sort_values('mst_utc')
n_test = int(len(df) * 0.20)
df_test = df.tail(n_test).copy()
print(f'Test seti: {len(df_test):,} maç')
feature_cols = [c for c in df.columns if c not in SKIP_COLS]
# Modelleri yükle
print('Modeller yükleniyor...')
models = {
'ms': (load_model('ms'), 3),
'ou15': (load_model('ou15'), 2),
'ou25': (load_model('ou25'), 2),
'ou35': (load_model('ou35'), 2),
'btts': (load_model('btts'), 2),
'ht_result':(load_model('ht_result'), 3),
'ht_ou05': (load_model('ht_ou05'), 2),
'ht_ou15': (load_model('ht_ou15'), 2),
}
models = {k: v for k, v in models.items() if v[0] is not None}
print(f'Yüklenen model: {list(models.keys())}')
X = df_test[feature_cols].fillna(0).values
# Tüm tahminleri al
print('Tahminler yapılıyor...')
preds = {}
for mkey, (model, n_class) in models.items():
p = predict_proba(model, X, feature_cols, n_class)
preds[mkey] = p
# Her maç için tutarsızlık skoru ve tahmin kararı
results = []
for i in range(len(df_test)):
row = df_test.iloc[i]
# Olasılıkları topla
probs = {}
if 'ms' in preds:
probs['ms_home'] = preds['ms'][i][0]
probs['ms_draw'] = preds['ms'][i][1]
probs['ms_away'] = preds['ms'][i][2]
if 'ou15' in preds:
probs['ou15_over'] = preds['ou15'][i][1]
if 'ou25' in preds:
probs['ou25_over'] = preds['ou25'][i][1]
if 'ou35' in preds:
probs['ou35_over'] = preds['ou35'][i][1]
if 'btts' in preds:
probs['btts_yes'] = preds['btts'][i][1]
if 'ht_result' in preds:
probs['ht_home'] = preds['ht_result'][i][0]
probs['ht_draw'] = preds['ht_result'][i][1]
probs['ht_away'] = preds['ht_result'][i][2]
if 'ht_ou05' in preds:
probs['ht_ou05_over'] = preds['ht_ou05'][i][1]
if 'ht_ou15' in preds:
probs['ht_ou15_over'] = preds['ht_ou15'][i][1]
c_score, conflicts = consistency_score(probs)
# Gerçek sonuçlar
actual = {
'ms': int(row.get('label_ms', -1)),
'ou15': int(row.get('label_ou15', -1)),
'ou25': int(row.get('label_ou25', -1)),
'ou35': int(row.get('label_ou35', -1)),
'btts': int(row.get('label_btts', -1)),
}
# Her market için tahmin ve doğruluk
market_results = {}
for mkt, label_key in [('ms','ms'),('ou15','ou15'),('ou25','ou25'),
('ou35','ou35'),('btts','btts')]:
if mkt not in preds or actual[label_key] < 0:
continue
pred_class = int(np.argmax(preds[mkt][i]))
correct = int(pred_class == actual[label_key])
# Odds (implied prob → odds = 1/prob)
pred_prob = float(preds[mkt][i][pred_class])
implied_odds = 1 / pred_prob if pred_prob > 0.01 else 10.0
# ROI hesabı: 1 birim bahis, kazanırsa (odds-1) kazanç, kaybederse -1
roi = (implied_odds - 1) * correct - (1 - correct)
market_results[mkt] = {
'pred': pred_class,
'actual': actual[label_key],
'correct': correct,
'prob': pred_prob,
'roi': roi,
}
results.append({
'idx': i,
'consistency_score': c_score,
'conflicts': conflicts,
'probs': probs,
'market_results': market_results,
})
df_results = pd.DataFrame([{
'consistency_score': r['consistency_score'],
'n_conflicts': len(r['conflicts']),
**{f'{m}_correct': r['market_results'].get(m, {}).get('correct', None)
for m in ['ms','ou15','ou25','ou35','btts']},
**{f'{m}_roi': r['market_results'].get(m, {}).get('roi', None)
for m in ['ms','ou15','ou25','ou35','btts']},
} for r in results])
# ── Analiz ──────────────────────────────────────────────────────────
print(f'\n{"="*70}')
print('TUTARSIZLIK ANALİZİ')
print(f'{"="*70}')
thresholds = [0.0, 0.1, 0.2, 0.3, 0.5]
markets = ['ms', 'ou15', 'ou25', 'ou35', 'btts']
for t in thresholds:
mask = df_results['consistency_score'] <= t
n = mask.sum()
if n < 50:
continue
print(f'\n[Tutarsızlık <= {t:.1f}] → {n:,} maç ({n/len(df_results)*100:.0f}%)')
print(f' {"Market":<8} {"HitRate":>8} {"ROI/bahis":>10} {"Toplam ROI":>12}')
print(f' {"-"*42}')
for m in markets:
col_c = f'{m}_correct'
col_r = f'{m}_roi'
if col_c not in df_results.columns:
continue
sub = df_results[mask][col_c].dropna()
roi_sub = df_results[mask][col_r].dropna()
if len(sub) < 20:
continue
hit = sub.mean()
avg_roi = roi_sub.mean()
total_roi = roi_sub.sum()
print(f' {m:<8} {hit:>7.1%} {avg_roi:>+9.3f} {total_roi:>+11.1f}')
# Çelişki türlerine göre breakdown
print(f'\n{"="*70}')
print('EN SIK ÇELIŞKILER')
print(f'{"="*70}')
all_conflicts = [c for r in results for c in r['conflicts']]
from collections import Counter
for conflict, cnt in Counter(all_conflicts).most_common(10):
print(f' {cnt:>5}x {conflict}')
# Tutarsızlık dağılımı
print(f'\n{"="*70}')
print('TUTARSIZLIK DAĞILIMI')
print(f'{"="*70}')
for label, lo, hi in [
('Tamamen tutarlı', 0.0, 0.05),
('Çok tutarlı', 0.05, 0.15),
('Orta', 0.15, 0.30),
('Tutarsız', 0.30, 0.50),
('Çok tutarsız', 0.50, 1.01),
]:
mask = (df_results['consistency_score'] >= lo) & (df_results['consistency_score'] < hi)
n = mask.sum()
ou25_hit = df_results[mask]['ou25_correct'].mean()
ms_hit = df_results[mask]['ms_correct'].mean()
print(f' {label:<20} {n:>6,} maç ({n/len(df_results)*100:>4.0f}%) | '
f'MS={ms_hit:.0%} OU25={ou25_hit:.0%}')
# Raporu kaydet
report = {
'total_test': len(df_results),
'thresholds': {},
}
for t in thresholds:
mask = df_results['consistency_score'] <= t
n = mask.sum()
report['thresholds'][str(t)] = {
'n_matches': int(n),
'pct': round(n/len(df_results)*100, 1),
'markets': {},
}
for m in markets:
col_c = f'{m}_correct'
col_r = f'{m}_roi'
if col_c not in df_results.columns:
continue
sub_c = df_results[mask][col_c].dropna()
sub_r = df_results[mask][col_r].dropna()
if len(sub_c) > 0:
report['thresholds'][str(t)]['markets'][m] = {
'hit_rate': round(float(sub_c.mean()), 4),
'avg_roi': round(float(sub_r.mean()), 4),
'total_roi': round(float(sub_r.sum()), 2),
}
out_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'reports', 'backtest_consistency.json')
with open(out_path, 'w') as f:
json.dump(report, f, indent=2)
print(f'\nRapor: {out_path}')
if __name__ == '__main__':
main()
+146
View File
@@ -0,0 +1,146 @@
import os
import sys
import psycopg2
from psycopg2.extras import RealDictCursor
# Path ayarları
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from services.single_match_orchestrator import SingleMatchOrchestrator
from services.feature_enrichment import FeatureEnrichmentService
DSN = "postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db"
def run_backtest(target_date="2026-05-03"):
conn = psycopg2.connect(DSN)
cur = conn.cursor(cursor_factory=RealDictCursor)
# 1. Hedef tarihteki bitmiş maçları ve takım isimlerini getir
cur.execute("""
SELECT m.id, m.score_home, m.score_away, m.mst_utc,
t1.name as home_name, t2.name as away_name
FROM matches m
LEFT JOIN teams t1 ON m.home_team_id = t1.id
LEFT JOIN teams t2 ON m.away_team_id = t2.id
WHERE m.status IN ('FT', 'AET', 'PEN')
AND to_timestamp(m.mst_utc / 1000.0)::date = %s::date
AND m.score_home IS NOT NULL
ORDER BY m.mst_utc ASC
""", (target_date,))
matches = cur.fetchall()
if not matches:
print(f"{target_date} tarihinde bitmiş maç bulunamadı.")
return
print(f"🚀 {target_date} için Orkestratör Backtesti Başlatılıyor... ({len(matches)} maç bulundu)")
print("-" * 60)
orchestrator = SingleMatchOrchestrator()
bets_placed = 0
won = 0
lost = 0
total_odds_won = 0.0
for match in matches:
# 3. Üst Akıl (Orkestratör) analizi yapar
try:
package = orchestrator.analyze_match(match['id'])
except Exception as e:
print(f"Hata ({match['id']}): {e}")
continue
if not package:
continue
package_data = package
# 4. Üst akıl bu maça bahis yapmaya karar verdi mi?
bet_advice = package_data.get("bet_advice", {})
if bet_advice.get("playable") == True:
bets_placed += 1
main_pick = package_data.get("main_pick", {})
market = main_pick.get("market")
pick = main_pick.get("pick")
odds = float(main_pick.get("odds", 0.0) or 0.0)
# Skora göre kazanıp kazanmadığını kontrol et
is_won = False
h = match['score_home']
a = match['score_away']
if market == "MS":
if pick == "1" and h > a: is_won = True
elif pick in ("X", "0") and h == a: is_won = True
elif pick == "2" and a > h: is_won = True
elif market == "OU25":
if pick == "Üst" and (h+a) > 2.5: is_won = True
elif pick == "Alt" and (h+a) < 2.5: is_won = True
elif market == "OU15":
if pick == "Üst" and (h+a) > 1.5: is_won = True
elif pick == "Alt" and (h+a) < 1.5: is_won = True
elif market == "BTTS":
if pick == "KG Var" and h > 0 and a > 0: is_won = True
elif pick == "KG Yok" and (h == 0 or a == 0): is_won = True
elif market == "DC":
if pick == "1X" and h >= a: is_won = True
elif pick == "12" and h != a: is_won = True
elif pick == "X2" and h <= a: is_won = True
if is_won:
won += 1
total_odds_won += odds
res = "✅ KAZANDI"
else:
lost += 1
res = "❌ KAYBETTİ"
print(f"[{res}] {match['home_name']} {h}-{a} {match['away_name']} | Tahmin: {market} {pick} (Oran: {odds})")
else:
main_pick = package_data.get("main_pick", {})
reasons = main_pick.get("reasons", ["Bilinmeyen Neden"]) if main_pick else ["No main pick"]
reason = " | ".join(reasons) if isinstance(reasons, list) else str(reasons)
market_board = package_data.get("market_board", {})
main_pick_market = main_pick.get('market', 'N/A') if main_pick else 'N/A'
main_pick_pick = main_pick.get('pick', 'N/A') if main_pick else 'N/A'
print(f"[PAS] {match['home_name']} {match['score_home']}-{match['score_away']} {match['away_name']} | Reddedilen: {main_pick_market} {main_pick_pick} -> Neden: {reason}")
if "market_passed_all_gates" in reason:
print(f" DEBUG: bet_advice = {bet_advice}")
v25_ms = market_board.get("MS", {}).get("probs", {})
v27_ms = {} # V27 is merged into V25 probabilities in market_board, or we don't have separate V27 access here
# Skora göre ms kontrolü
h = match['score_home']
a = match['score_away']
actual_ms = "1" if h > a else ("X" if h == a else "2")
v25_top = max(v25_ms, key=v25_ms.get) if v25_ms else "N/A"
v27_top = "N/A"
rejected_market = main_pick.get("market", "N/A") if main_pick else "N/A"
rejected_pick = main_pick.get("pick", "N/A") if main_pick else "N/A"
print(f"[PAS] {match['home_name']} {h}-{a} {match['away_name']} | Reddedilen: {rejected_market} {rejected_pick} -> Neden: {reason}")
print(f" [V25 MS Raw: {v25_top}] [Gerçek MS: {actual_ms}]")
# Sonuç Raporu
print("\n" + "=" * 60)
print(f"📊 BACKTEST SONUÇLARI ({target_date})")
print("=" * 60)
print(f"Toplam Maç Sayısı : {len(matches)}")
print(f"Oynanan Bahis Sayısı: {bets_placed} (Oynama Oranı: %{bets_placed/len(matches)*100:.1f})")
print(f"Riskli Bulunup Pas Geçilen: {len(matches) - bets_placed}")
if bets_placed > 0:
win_rate = won / bets_placed * 100
roi = ((total_odds_won - bets_placed) / bets_placed) * 100
print(f"Kazanılan : {won}")
print(f"Kaybedilen : {lost}")
print(f"İsabet Oranı : %{win_rate:.1f}")
print(f"Net Kar (ROI) : %{roi:.1f} {'📈' if roi > 0 else '📉'}")
if __name__ == "__main__":
run_backtest("2026-05-03")

Some files were not shown because too many files have changed in this diff Show More