Betman Odds · Quantitative Value Analysis
배트맨 공식 배당에서 마진(vig)을 걷어내 시장의 내재 확률을 구하고, 포아송·정규분포 기반 통계 모델의 확률과 비교합니다. 둘의 차이가 곧 기댓값(EV) — 이 도구는 EV가 양수인 픽만 골라냅니다.
현재 모든 기능 무료 제공
How It Works
블랙박스 픽방이 아닙니다. 어떤 숫자가 들어가 어떤 계산을 거치는지 전 과정이 화면에 그대로 표시됩니다.
배당의 역수 합은 항상 1을 넘습니다 — 그 초과분이 마진(vig)입니다. de-vig 정규화로 마진을 걷어내 시장의 순수한 내재 확률을 복원합니다.
야구는 FIP·wRC+·GB%·ISO·구장·날씨 보정을 거친 포아송/정규 모델, 농구는 ORtg·DRtg·페이스 기반 마진 정규분포, 축구는 xG 포아송 모델로 경기별 확률을 계산합니다.
모델 확률이 내재 확률을 이길 때만 EV가 양수가 됩니다. 권장 비중은 쿼터 켈리(¼ Kelly)에 뱅크롤 2% 상한을 걸어 보수적으로 산출합니다.
Tier System
EV 크기에 따라 5개 등급으로 나눕니다. 등급이 낮으면 낮다고, 우위가 없으면 없다고 말하는 것이 이 도구의 규칙입니다.
Coverage · 리그 선택
카드를 누르면 해당 리그 분석 화면으로 이동합니다. 종목의 득점 구조가 다르면 확률 모델도 달라야 합니다.
승패 · 핸디캡 · 승1패 · 언더오버 + 전반 3마켓. 선발 FIP와 팀 타격 지표, 구장 팩터, 날씨 보정까지 반영하는 가장 깊은 모델.
풀게임 7마켓MLB StatsAPI 연동으로 선발·팀 스탯이 자동으로 채워집니다. 경기 선택 후 배당만 입력하면 끝.
4마켓 · 자동채움ORtg·DRtg·페이스 기반 마진 모델. 백투백 자동 판정, 3점 스타일 매치업, 상대전적 보정 포함.
3마켓 · 자동채움EPL · 라리가 · 세리에A · 분데스리가. xG 기반 포아송 모델로 승무패·핸디캡·언더오버를 평가합니다.
3마켓 · 자동채움일본프로야구. KBO 모델 이식 — 선발 FIP·타선 wRC+·ISO 보정, 텍스트 붙여넣기 일괄분석. 초기 미검증 모델로 보수적 등급을 적용합니다.
4마켓 · 일괄ROADMAP → 픽 성과 대시보드(적중률·ROI 공개) · 프리미엄 기능 단계적 도입 예정
Principles
EV는 장기 기댓값이지 오늘의 결과 예언이 아닙니다. +EV 픽도 절반 가까이는 집니다 — 이기는 건 확률이 아니라 반복입니다.
우위가 없는 날엔 관망을 출력합니다. 억지로 픽을 뽑는 도구는 도구가 아니라 유혹입니다.
내재확률, 모델확률, EV, 켈리 비중 — 모든 중간값이 표로 공개됩니다. 판단의 주체는 언제나 사용자입니다.
오늘 벳맨(betman.co.kr) 프로토 승부식의 일본프로야구(NPB) 배당을 아래 형식으로만 정리해줘. 다른 설명은 필요 없어. 경기마다 블록 하나씩. 팀명은 아래 12개 한글 표기만 써: 요미우리 한신 DeNA 히로시마 야쿠르트 주니치 소프트뱅크 니혼햄 오릭스 라쿠텐 롯데 세이부. 배당은 홈팀 먼저, 핸디캡은 홈팀 라인(+/- 부호 포함) 먼저, 언더오버는 기준점 먼저·언더 먼저. 4줄 모두 있어야 계산되니 빠진 마켓이 있으면 그 경기는 통째로 빼줘. 블록 제목에는 벳맨에 적힌 경기 시각(한국시간)을 ` · MM.DD HH:MM` 형태로 꼭 붙여줘. 같은 두 팀이 하루에 두 경기(더블헤더)면 두 블록 모두 시각을 넣고, 벳맨에 1차전/2차전 표기가 있으면 제목 끝에 (1차전)/(2차전)도 붙여줘. [벳맨 배당 - 주니치 vs 한신 · 08.18 18:00] 승패: 2.40 1.39 승1패: 4.20 2.75 1.83 핸디: +2.5 1.37 2.46 언더오버: 5.5 1.80 1.72
function doGet(e){
var action=e.parameter.action;
if(action==='ping')return json_({ok:true,v:4});
if(action==='getPicks')return json_({picks:readAll_()});
if(action==='getTeamDB')return json_(readTeamDB_());
if(action==='getKbo')return json_(readKbo_());
if(action==='getNpb')return json_(readNpb_());
if(action==='getCloseOdds')return json_(readCloseOdds_());
return json_({ok:false,error:'unknown action'});
}
function doPost(e){
var lock=LockService.getScriptLock();
lock.waitLock(20000);
try{
var body=JSON.parse(e.postData.contents);
if(body.action==='appendPicks'||body.action==='upsertPicks'){upsertPicks_(body.picks||[],false);return json_({ok:true,v:3});}
if(body.action==='syncFinalPicks'){upsertPicks_(body.picks||[],true);return json_({ok:true,v:3});}
if(body.action==='deletePicks'){deletePicks_(body.keys||[]);return json_({ok:true,v:3});}
if(body.action==='saveTeamDB'){var t=saveTeamDB_(body.teamDB||{});return json_({ok:true,v:2,savedAt:t});}
if(body.action==='saveKbo'){var tk=saveKbo_(String(body.text||''));return json_({ok:true,savedAt:tk});}
if(body.action==='saveNpb'){var tn=saveNpb_(String(body.text||''));return json_({ok:true,savedAt:tn});}
if(body.action==='saveCloseOdds'){var tc=saveCloseOdds_(String(body.league||''),String(body.text||''));return json_({ok:true,savedAt:tc});}
return json_({ok:false,error:'unknown action'});
}finally{lock.releaseLock();}
}
// 새 열은 반드시 배열 '끝'에만 추가 — 중간 삽입 시 기존 시트의 데이터 행과 열 위치가 어긋남
var HEADERS=['savedAt','date','league','match','market','pick','P','fair','ev','odds','stake','score','tier','expTotal','expMargin','actHome','actAway','actH1Home','actH1Away','hFIP','aFIP','hWRC','aWRC','hBP','aBP','hGB','aGB','PF','weather','wWeight','edge','result','archived','closeOdds','staked','closeLine'];
function sheet_(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('Picks');
if(!sh){ sh=ss.insertSheet('Picks'); sh.appendRow(HEADERS); }
if(sh.getMaxColumns()<HEADERS.length)sh.insertColumnsAfter(sh.getMaxColumns(),HEADERS.length-sh.getMaxColumns());
if(sh.getRange(1,HEADERS.length).getValue()!==HEADERS[HEADERS.length-1])sh.getRange(1,1,1,HEADERS.length).setValues([HEADERS]);
return sh;
}
var TEAMDB_HEADERS=['team','wrc','wrcR','slg','avg','savedAt'];
function teamDbSheet_(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('TeamDB');
if(!sh){ sh=ss.insertSheet('TeamDB'); sh.appendRow(TEAMDB_HEADERS); }
return sh;
}
function saveTeamDB_(teamDB){
var sh=teamDbSheet_();
var savedAt=new Date().toISOString(); // 저장 시각은 서버가 찍음 — 기기 시계가 어긋나도 순서 보장
sh.clearContents();
sh.appendRow(TEAMDB_HEADERS);
Object.keys(teamDB).forEach(function(team){
var t=teamDB[team]||{};
sh.appendRow([team, t.wrc==null?'':t.wrc, t.wrcR==null?'':t.wrcR, t.slg==null?'':t.slg, t.avg==null?'':t.avg, savedAt]);
});
return savedAt;
}
function readTeamDB_(){
var sh=teamDbSheet_();
var data=sh.getDataRange().getValues();
var db={}, savedAt='';
for(var i=1;i<data.length;i++){
var row=data[i]; if(!row[0])continue;
db[row[0]]={wrc:row[1]===''?null:row[1], wrcR:row[2]===''?null:row[2], slg:row[3]===''?null:row[3], avg:row[4]===''?null:row[4]};
if(row[5])savedAt=row[5] instanceof Date?row[5].toISOString():String(row[5]);
}
return {teamDB:db, savedAt:savedAt};
}
// KBO 하루치 스탯 텍스트(경기·팀타격·선발 블록)를 한 칸에 통째로 저장 — 집에서 올리고 폰에서 그대로 불러옴
function kboSheet_(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('KBO');
if(!sh){ sh=ss.insertSheet('KBO'); sh.appendRow(['text','savedAt']); }
return sh;
}
function saveKbo_(text){
var sh=kboSheet_();
var savedAt=new Date().toISOString(); // 저장 시각은 서버가 찍음
sh.clearContents();
sh.appendRow(['text','savedAt']);
sh.appendRow([text, savedAt]);
return savedAt;
}
function readKbo_(){
var sh=kboSheet_();
var data=sh.getDataRange().getValues();
if(data.length<2)return {text:'',savedAt:''};
var row=data[1];
var savedAt=row[1] instanceof Date?row[1].toISOString():String(row[1]||'');
return {text:String(row[0]||''), savedAt:savedAt};
}
// NPB 하루치 스탯 텍스트([경기N] 블록)를 한 칸에 통째로 저장 — KBO와 같은 방식, 시트만 분리
function npbSheet_(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('NPB');
if(!sh){ sh=ss.insertSheet('NPB'); sh.appendRow(['text','savedAt']); }
return sh;
}
function saveNpb_(text){
var sh=npbSheet_();
var savedAt=new Date().toISOString(); // 저장 시각은 서버가 찍음
sh.clearContents();
sh.appendRow(['text','savedAt']);
sh.appendRow([text, savedAt]);
return savedAt;
}
function readNpb_(){
var sh=npbSheet_();
var data=sh.getDataRange().getValues();
if(data.length<2)return {text:'',savedAt:''};
var row=data[1];
var savedAt=row[1] instanceof Date?row[1].toISOString():String(row[1]||'');
return {text:String(row[0]||''), savedAt:savedAt};
}
// 마감배당(배트맨 고정배당) 텍스트 — 리그마다 한 행. 리그별로 나눠야 MLB 수집이 KBO 값을 지우지 않는다.
function closeOddsSheet_(){
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName('CloseOdds');
if(!sh){ sh=ss.insertSheet('CloseOdds'); sh.appendRow(['league','text','savedAt']); }
return sh;
}
function saveCloseOdds_(league,text){
var sh=closeOddsSheet_();
var savedAt=new Date().toISOString(); // 저장 시각은 서버가 찍음
var data=sh.getDataRange().getValues();
for(var i=1;i<data.length;i++){
if(String(data[i][0])===league){ sh.getRange(i+1,1,1,3).setValues([[league,text,savedAt]]); return savedAt; }
}
sh.appendRow([league,text,savedAt]);
return savedAt;
}
function readCloseOdds_(){
var sh=closeOddsSheet_();
var data=sh.getDataRange().getValues();
var out=[];
for(var i=1;i<data.length;i++){
if(!data[i][0])continue;
var savedAt=data[i][2] instanceof Date?data[i][2].toISOString():String(data[i][2]||'');
out.push({league:String(data[i][0]), text:String(data[i][1]||''), savedAt:savedAt});
}
return {entries:out};
}
function json_(obj){
return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}
// 픽 식별키: 저장시각(초 단위 정규화)|리그|경기|마켓|픽 — 사이트 쪽 pickKey와 동일 규칙.
// v3: league·match를 키에 추가(이전엔 savedAt|market|pick뿐이라, 같은 배치 저장(=savedAt 동일)에
// 여러 경기가 "승패/기준팀승"처럼 같은 마켓·픽 문구를 공유하면 서로 다른 경기인데도 같은 행으로 겹쳐써졌음).
function keyOf_(savedAt,date,league,match,market,pick){
var s=savedAt instanceof Date?savedAt.toISOString():String(savedAt||date||'');
var t=Date.parse(s);
var norm=isNaN(t)?s:new Date(t).toISOString().slice(0,19);
return norm+'|'+(league||'')+'|'+(match||'')+'|'+market+'|'+pick;
}
// 같은 키의 픽이 있으면 그 행을 갱신, 없으면 추가. finalize=true면 아카이브 표시(모든 기기 목록에서 제외)
function upsertPicks_(picks,finalize){
var sh=sheet_();
var data=sh.getDataRange().getValues();
var rowByKey={};
for(var i=1;i<data.length;i++){rowByKey[keyOf_(data[i][0],data[i][1],data[i][2],data[i][3],data[i][4],data[i][5])]=i;}
picks.forEach(function(p){
if(finalize)p.archived='1';
var key=keyOf_(p.savedAt,p.date,p.league,p.match,p.market,p.pick);
var i=rowByKey[key];
if(i===-1)return;
if(i!==undefined){
var row=HEADERS.map(function(h,c){ var v=p[h]!==undefined?p[h]:data[i][c]; return v===undefined?'':v; });
sh.getRange(i+1,1,1,HEADERS.length).setValues([row]);
data[i]=row;
} else {
sh.appendRow(HEADERS.map(function(h){ return p[h]!==undefined?p[h]:''; }));
rowByKey[key]=-1;
}
});
}
function deletePicks_(keys){
var sh=sheet_();
var data=sh.getDataRange().getValues();
var set={}; keys.forEach(function(k){set[k]=true;});
for(var i=data.length-1;i>=1;i--){
if(set[keyOf_(data[i][0],data[i][1],data[i][2],data[i][3],data[i][4],data[i][5])])sh.deleteRow(i+1);
}
}
function readAll_(){
var sh=sheet_();
var data=sh.getDataRange().getValues();
var picks=[];
for(var i=1;i<data.length;i++){
var row=data[i]; if(!row[0]&&!row[1])continue;
var obj={}; HEADERS.forEach(function(h,idx){ obj[h]=row[idx]!==undefined?row[idx]:''; });
picks.push(obj);
}
return picks;
}
| 날짜 | 리그 | 경기 | 픽 | P | 배당 | 마감배당 | 픽점수(/100) | 실제 홈 | 실제 원정 | 삭제 |
|---|
이 구글 스프레드시트는 KBO·MLB 베팅 EV 예측 모델의 픽 기록입니다. 각 열 의미: - market: 마켓종류 | pick: 최선픽 | P_model: 모델예측확률 | devig: 배당내재확률 | ev: 기댓값 - hFIP/aFIP: 홈·원정 선발FIP | hWRC/aWRC: 홈·원정 wRC+ | PF: 구장계수 - actHome/actAway: 최종점수 | actH1Home/actH1Away: 5회말 기준 전반점수 | result: W/L/P - closeOdds: 마감배당(경기 직전 배당) — CLV(잡은 배당/마감배당−1) 분석용, 빈칸이면 미기록 - closeLine: 마감 라인 — 배트맨의 홈(기준팀) 기준 원값 그대로(핸디 -2.5 / 언더오버 9.5). 부호를 뒤집지 않은 원자료입니다. 픽의 라인(pick에 적힌 값)과 비교하면 시장이 내 픽 쪽으로 움직였는지 알 수 있습니다. 기준팀 핸디·언더 픽은 (픽 라인 − closeLine), 상대팀 핸디·오버 픽은 그 반대 부호가 양수일 때 '시장 동의'입니다. 라인이 움직인 픽은 마감배당이 다른 상품이라 closeOdds가 비어 있어도 closeLine은 채워집니다. 아래 순서로 분석해주세요: 1. 마켓별 적중률·ROI 요약표 2. P_model 캘리브레이션 — 예측확률 구간별 실제 적중률 비교 3. FIP·wRC+ 등 입력변수와 오차의 상관관계 (과대평가·과소평가 패턴) 4. 계수 보정 제안 (fipCoefKBO·wrcFactor·sigF 등 조정 방향) 5. 마켓별 전략 개선점 (전반 마켓 포함) 6. 위 분석을 바탕으로 계수·모델 개선 로드맵을 우선순위와 함께 제시