Surebet Analyzer

Salt okunur bir analiz aracı. Altı anonim kaynaktan herkese açık maç öncesi oranları topluyor, hangi kayıtların aynı maç olduğunu buluyor ve bir bütçenin bu kaynaklar arasında nasıl bölüneceğini kuruşuna kadar hesaplıyor.

Rol
Kişisel proje: toplayıcılar, eşleştirme, dağılım çözücü, API ve PWA
Durum
Kodu GitHub'da açık; demo modu sentetik veriyle çalışıyor
Teknolojiler
Python 3.12, FastAPI, OR-Tools CP-SAT, SQLite, React 18, TypeScript
Kod
github.com/ozgurlook/surebet-analyzer, MIT lisansı

Ekranlar demo modundan: oranlar sentetik, kaynaklar Kaynak A–F olarak adlandırılıyor.

Demo modunda bulunan fırsatlar: bütçe, filtreler ve bir maç için net farkı gösteren kart
Demo modunda kaynak sağlığı: akış türleriyle Kaynak C, B, A ve D; Kaynak C devre dışı
Demo modunda fırsat ayrıntısı: bir maç için hesaplanan net fark; Kaynak B, A ve E'den seçilen oranlar ve her birinin tutarı

Kısaca

Sorun
Altı kaynak aynı maçları farklı takım adları, lig adları ve başlama saatleriyle listeliyor; oranlar da bir toplamadan diğerine değişiyor. İki oranı karşılaştırmadan önce sistemin bunların aynı maça ait olduğunu bilmesi, ardından bütçeyi tam kuruşlarla, hatasız bölmesi gerekiyor.
Benim payım
Baştan sona kendim geliştirdiğim kişisel bir proje: üç akış protokolü için toplayıcılar, ad normalleştirme ve eşleştirme, kuruş dağıtıcısı ve CP-SAT modeli, FastAPI backend'i, React PWA ve Telegram uyarıları.
Sonuç ve durum
MIT lisansıyla GitHub'da açık. Her push ve pull request'te CI'da ruff, strict modda mypy, 1.209 backend testi ve PWA'nın Vitest testleri çalışıyor. Açık kodda hiçbir kaynak adresi yok; demo modu uygulamanın tamamını sentetik veriyle çalıştırıyor.

Ekran ekran uygulama

PWA'nın demo modundaki altı ekranı. Oranlar sentetik: çoğu maç normal bir piyasa gibi fiyatlanıyor, birkaçına ise taramanın işaretleyeceği bir şey olsun diye bilerek hatalı fiyat veriliyor.

Ekran 1, toplam 6

Nasıl çalışıyor

Önce akıştan uyarıya giden yol, ardından bir sayıya güvenilip güvenilemeyeceğini belirleyen iki kısma yakından bakış: kayıtları kaynaklar arasında eşleştirmek ve bütçeyi bölmek.

Akıştan uyarıya

Varsayılan olarak 30 saniyede bir, tek bir asyncio döngüsü tüm kaynakları aynı anda topluyor. Her parti SQLite'a anlık kayıt olarak yazılıyor; WAL modu sayesinde toplayıcılar yazarken API okumaya devam ediyor. Kayıtlar maçlara eşleştiriliyor, dağılımlar için taranıyor ve sonuçlar hem PWA'nın arkasındaki REST API'ye hem de kullanıcıya özel Telegram uyarılarına gidiyor.

Veri yolu

  1. Oran akışlarıWebSocket üzerinden JSON ve WAMP, HTTPS üzerinden JSON
  2. Toplayıcılarasyncio, 30 sn'de bir, kaynak başına ayrı hata sınırı
  3. Anlık kayıtlarWAL modunda SQLite, kaynak başına sağlık
  4. Lig eşlemesağlayıcı kimliklerinden kanonik liglere
  5. Maç eşleştiricinormalleştirilmiş adlar, başlama ±3 sa
  6. Dağılım çözücütam sayı kuruş, kapalı form + tek kuruşluk onarım
  7. PWA + TelegramREST API, kullanıcıya özel uyarı

Tek maç, birçok ad

Kaynaklar bir maçı tanımlayan neredeyse her şeyde ayrışıyor. Biri lig adına sponsoru ekliyor, öteki ülke önekini; biri Fenerbahçe SK yazıyor, öteki Fenerbahce; başlama saatleri de her akışın saat dilimini nasıl işlediğine göre kayıyor. Yanlış bir eşleşme, kaçırılan bir eşleşmeden kötü: iki farklı maçı tek maç sanmak, aslında olmayan bir fiyat farkı gösterir. Bu yüzden eşleştirici bilerek temkinli.

  • Önce ligler. Her kaynağın ligi tek bir kanonik lige eşleniyor: sağlayıcı kimliği ya da elle derlenen kayıt şüphe bırakmıyorsa otomatik, bırakıyorsa inceleme kuyruğundan geçerek. Eşlenmemiş bir lig kaynaklar arasında asla eşleşmiyor.
  • Adlar karşılaştırılmadan önce normalleştiriliyor: İ ve ı dahil Türkçe harfler sadeleştiriliyor; SK, FC, A.Ş. gibi ekler atılıyor; Türkçe şehir adları eşleniyor, Münih de München de munich oluyor; bilinen takma adlar açılıyor.
  • İki kayıt yalnızca aynı lig içinde, başlama saatleri en fazla 3 saat farklıysa ve hem ev sahibi hem deplasman adları uyuşuyorsa eşleşiyor: aynı, biri diğerinin içinde, birinin kelimeleri diğerininkilerin alt kümesi ya da bulanık benzerlik en az 0,84. Başlama saatleri kayabilen bir kaynak için futbolda pencere 24 saate çıkıyor; o zaman adların aynı olması ya da birinin diğerini içermesi gerekiyor.
  • Eşleşmiş bir maç her kaynaktan en fazla bir kayıt içeriyor. Kimliği lig, normalleştirilmiş adlar ve başlama tarihinden üretilen bir hash; böylece bir döngüden diğerine değişmiyor.
  • Kaynak C

    Gelen lig adı
    Trendyol Süper Lig
    Gelen maç adı
    Galatasaray A.Ş. – Fenerbahçe SK
    Karşılaştırılan hâli
    galatasaray – fenerbahce
  • Kaynak B

    Gelen lig adı
    Super Lig
    Gelen maç adı
    Galatasaray – Fenerbahce
    Karşılaştırılan hâli
    galatasaray – fenerbahce
  • Kaynak A

    Gelen lig adı
    Türkiye Süper Lig
    Gelen maç adı
    Galatasaray Istanbul – Fenerbahce SK
    Karşılaştırılan hâli
    galatasaray – fenerbahce
  • aynı kanonik lig
  • başlama farkı en fazla 3 saat
  • ev sahibi ve deplasman adları uyuşuyor

Tek maç galatasaray – fenerbahce

Kaynak A, B ve C'nin oranları yan yana

Eşleştiricinin kendi testinden üç kayıt, tek maçta birleşiyor.

Bütçeyi kuruşuna kadar bölmek

2,90'lık ondalık oran 1 ÷ 2,90, yani yaklaşık %34,48 olasılık ima ediyor. Her sonuç için bir oran alıp ima edilen olasılıkları topluyorsunuz. Normal bir piyasada toplam %100'ü geçiyor; aradaki fark piyasanın marjı. Toplam %100'ün altındaysa her sonuca 1 ÷ oran ile orantılı tutar koymak, hangi sonuç gelirse gelsin aynı tutarın ödenmesini sağlıyor: bütçenin bu toplama bölümü.

Bu, gerçek sayılarla verilen cevap. Gerçek tutarlar tam kuruş, ödeme kuruşa aşağı yuvarlanıyor ve her kaynağın kendi en düşük tutarı, en yüksek tutarı ve bakiyesi var. Eşit bölüşüm, aramanın yalnızca başladığı yer.

Örnek sayılar

Bir dağılım deneyin

Varsayılan oranlar projenin kendi testlerinden. Herhangi birini değiştirin; dağılım tarayıcınızda canlı taramanın başlangıç ve onarım adımlarıyla yeniden hesaplanıyor, yalnızca taramanın birkaç kaynak için yaptığı düzeltme dışarıda kalıyor. Hiçbir oran çekmiyor, hiçbir yere bir şey göndermiyor.

Tutarlar TL cinsinden. Kesin pay, dört ondalıkta kesilmiş gerçek sayı bölüşümü; tutar tam kuruş; ödeme, tutar × oranın kuruşa aşağı yuvarlanmış hâli.

Bir dağılım deneyin
Ev sahibi (1)Beraberlik (X)Deplasman (2)
Oran
Olasılık%34,48%27,78%32,26
Kesin pay364,8251293,8868341,2880
Tutar364,82293,89341,29
Kazanırsa ödeme1.057,971.058,001.057,99
Ödeme − bütçe+57,97 (en kötü sonuç)+58,00+57,99

İma edilen olasılıkların toplamı %94,52, yani %100'ün altında.

Yuvarlama olmasaydı her sonuçta ödeme 1.057,9928 TL olurdu. Tam kuruşla en küçük ödeme 1.057,97 TL.

Hiçbir tek kuruşluk kaydırma, en büyük kalan yöntemiyle kurulan başlangıcı iyileştirmedi.

850 TL'de başlangıç en kötü sonucu olabileceğinden bir kuruş düşük bırakıyor; onarım adımı bir kuruş kaydırarak bunu düzeltiyor.

Canlı taramada

Her döngü, eşleşen her maç için tüm kaynak kombinasyonlarını deniyor. Tarama, kombinasyon başına bir CP-SAT modeli yerine kapalı formu kullanıyor: her sonuca bir kuruş ayrılıyor, kalan 1 ÷ oran ile orantılı paylaştırılıp aşağı yuvarlanıyor, artan kuruşlar en büyük kalanlara veriliyor, ardından en kötü sonucu iyileştirdiği sürece sonuçlar arasında birer kuruş kaydırılıyor. Testleri sonucu, küçük bütçelerde tüm olasılıkları deneyen bir aramayla karşılaştırıyor. Birkaç kaynak için tarama bu payı kaynağa özgü koşullara göre ayrıca düzeltiyor; yukarıdaki hesaplayıcı aynı adımları bu düzeltme olmadan çalıştırıyor.

Kesin modelde

Elle analiz için bir OR-Tools CP-SAT modeli problemi kapalı formun dışarıda bıraktığı her şeyle çözüyor: tam sayı tutarlar, tutar × oran ÷ 10.000 olarak aşağı yuvarlanan ödemeler (tam sayıya ölçeklenmiş oranlar üzerinde bir bölme kısıtı), kaynak başına en düşük ve en yüksek tutar, bakiye ve ödeme tavanları. Önce en kötü sonucu iyileştiriyor, sonra toplam tutarı küçültüyor, sonra ev sahibi, beraberlik ve deplasmanı sırayla sabitliyor; tek iş parçacığı ve sabit tohumla çalıştığı için aynı girdi her zaman aynı dağılımı veriyor. Cevap kabul edilmeden önce bağımsız bir tam sayı kontrolü her sonucu yeniden hesaplıyor.

Hesabın sınırı

  • Hesap yalnızca okunduğu andaki oranlar için geçerli; oranlar iki toplama arasında değişiyor.
  • Bir kaynak tutarı reddedebilir ya da sınırlayabilir. Araç hiçbir zaman bahis yapmıyor; yalnızca herkese açık oranları okuyor.
  • Aynı altyapıyı kullanan iki marka tek operatör sayılıyor; tarama, bütçeyi yalnızca tek operatörün markaları arasında dağıtan kombinasyonları atlıyor.

Bir kaynak sorun çıkardığında

Toplayıcılar tek bir asyncio döngüsünde eşzamanlı çalışıyor, her biri kendi hata sınırı içinde. Zaman aşımı ya da reddedilen bağlantı o kaynağın kaydına yazılıyor, diğerleri devam ediyor. Her kaynağın sağlığı küçük bir durum makinesiyle izleniyor.

  • Her oran okunduğu zamanı taşıyor; bayatlama sınırından eski olanlar taramaya alınmıyor.
  • Maçlar başlama saatinden 15 dakika sonra kayıtlardan çıkarılıyor; araç yalnızca maç öncesi oranlara bakıyor.
  • Uygulamadan yenileme en fazla 10 saniyede bir çalışıyor; arayüz kaynaklara yüklenemiyor.
  • Hesaplar yalnızca davetle açılıyor. Parolalar Argon2id ile saklanıyor, her yazma isteği bir CSRF token'ı taşıyor ve girişler hız sınırına tabi.

Erişilemiyor

Hiç veri partisi getirmedi. Adresi tanımlanmamış bir kaynak burada kalıyor.

Erişilemiyor durumundan Sağlıklı durumuna: ilk sağlam parti

Sağlıklı

Son çalışma başarılı; kullanılan veri onun partisi.

Sağlıklı durumundan Bozulmuş durumuna: başarısız çalışma

Bozulmuş durumundan yeniden Sağlıklı durumuna: sonraki sağlam parti

Bozulmuş

Son çalışma başarısız. Son sağlam parti saklanıyor, bu kaynağa bağlı uyarılar bekliyor.

Durum makinesi olarak kaynak sağlığı

Her fırsat için tek Telegram mesajı

Uyarılar kullanıcıya özel: herkes kendi eşiğini ve takip edeceği ligleri seçiyor. Her fırsat, oranlar değiştikçe düzenlenen tek bir mesajda kalıyor; ardı ardına yeni mesajlar gitmiyor. Kartın parmak izi, kartta görünen yüzdeyi 25 baz puana yuvarlıyor ve sürekli artan oran yaşını dışarıda bırakıyor; böylece küçük oynamalar düzenleme tetiklemiyor, aynı mesajın iki düzenlemesi arasında da en az 60 saniye oluyor. PWA aynı fırsatları gösteriyor, ana ekrana eklenebiliyor ve çevrimdışı olduğunda bunu söylüyor.

Yeni
Kullanıcının eşiğinin üstünde ilk kez görüldü; kart gönderiliyor.
Aktif
Hâlâ eşiğin üstünde; aynı mesaj düzenleniyor, kartın yüzdesi yeterince değişirse altına bir yanıt düşülüyor.
İzlemede
Eşiğin altında ama eşiğin %80'i olan kapanış tabanının üstünde.
Bayat
Arkasındaki bir kaynak sağlıklı değil; kart kapanmak yerine bekliyor.
Kapandı
180 saniye boyunca kesintisiz kapanış tabanının altında kaldı ya da maç başladı. 15 dakika içinde geri gelirse aynı mesaj yeniden açılıyor.

Rakamlarla

1.209
pytest'in topladığı backend testi: birim, özellik, entegrasyon ve kabul
21
PWA için Vitest test dosyası
6
kaynak, Kaynak A–F olarak anonimleştirilmiş
3
saat: iki kaydın aynı maç sayılması için başlama penceresi
10.000
oranların çarpıldığı katsayı; böylece her ödeme tam sayı aritmetiği
60
saniye: aynı Telegram mesajının iki düzenlemesi arasındaki en kısa süre

Kararlar ve bedelleri

Bu proje hakkında

Surebet Analyzer, veri eşleştirme ve kesin aritmetik üzerine teknik bir çalışma. Yalnızca herkese açık oranları okuyor: hiçbir hesaba giriş yapmıyor, bahis oynamıyor, para hareket ettirmiyor. Kaynaklar Kaynak A–F olarak anonimleştirildi, açık kodda hiçbir sağlayıcı adresi yok ve bu sayfa hiçbir bahis sitesinin adını vermiyor, bağlantısını içermiyor. Buradaki hiçbir şey tavsiye ya da herhangi bir kazanç vaadi değil.

Sıradaki projeBlindyİki üniversitelinin yüz görmeden önce yedi dakika anonim sohbet ettiği bir tanışma uygulaması.