def status(): # load all active games in memory games = { game: json.loads(open(btcs.path('games/new', game), 'r').read()) for game in os.listdir(btcs.path('games/new', '')) } # load all active betslips slipids = os.listdir(btcs.path('bets/received', '')) slips = { slipid: json.loads(open(btcs.path('bets/received', slipid), 'r').read()) for slipid in slipids } for gameid, game in games.iteritems(): logging.info('Game: ' + str(game)) logging.info('------') # sum all results found in bets for slip in slips.values(): for bet in slip['bets']: if bet['game'] == gameid: logging.info('Bet ' + str(slip['email_address']))
def save_betslip(betslip): "Verify the betslip, create an address for it and return it" if not 'accountid' in betslip: raise web.internalerror('No accountid') if not re.match('^[a-zA-Z0-9]{6,20}$', betslip['accountid']): raise web.internalerror('Invalid accountid') if not 'bets' in betslip: raise web.internalerror('No bets') # verify individual bets for game in betslip['bets']: if not os.path.exists(btcs.path('games/new', int(game['game']))): raise web.internalerror('Game not found') if not re.match('^[0-5]-[0-5]$', game['result']): raise web.internalerror('Invalid result') if not (btcs.MIN_BET <= Decimal(game['amount']) <= btcs.MAX_BET): raise web.internalerror('Invalid amount') adr = wallet.getaddress(betslip['accountid']) btcs.writejson(btcs.path('bets/new', adr), betslip) return adr
def payout(bets, total, game, txtype): " payout the amount of total divided over bets " divider = sum_bets(bets) outputs = {} for bet in bets: amount = Decimal(bet['amount']) / divider * total bet['payout'] = "%.2f" % amount bet['txtype'] = txtype # could be betted twice thus add if bet['return_address'] in outputs: amount = amount + outputs[bet['return_address']] outputs[bet['return_address']] = amount # from mbtc to btcs for o in outputs: outputs[o] = outputs[o] / Decimal(1000) logging.info('Paying: %s', repr(outputs)) if not DRYRUN: txid = wallet.payout(outputs) btcs.writejson(btcs.path('tx/new', txid), { "type": txtype, "game": game, "outputs": outputs }) return txid else: return 'dryrun'
def render(template, data): "Renders a mustache template" renderer = pystache.Renderer() html = renderer.render_path('../templates/' + template, data) with open(btcs.path('var', template), "w") as f: f.write(html.encode('utf8')) logging.info('Written: ' + template)
def process_incoming(betslip): recv = wallet.getreceivedby(betslip, 0) if recv == 0: logging.info('Betslip %s: Nothing received' % (betslip)) return # read betslip with open(btcs.path('bets/new', betslip), 'r') as f: betslip_data = json.loads(f.read()) # Sum total in betslip total = Decimal('0') for bets in betslip_data['bets']: total += Decimal(bets['amount']) total = total / Decimal('1000') if recv == total: logging.info('Betslip %s: Received %s, moving to received' % (betslip, recv)) os.rename(btcs.path('bets/new', betslip), btcs.path('bets/received', betslip)) # attach game data for email if 'email_address' in betslip_data: for bet in betslip_data['bets']: try: with open(btcs.path('games/new', bet['game']), 'r') as f: bet['game_data'] = json.loads(f.read()) except Exception: # only for email; do not trip over it pass notify_email.sendmail('email_betslip.html', betslip_data, betslip_data['email_address'], 'Betslip payment received') else: logging.warning( 'Betslip %s: Invalid amount received: %s, expected %s' % (betslip, recv, total)) logging.warning('Return amount manually')
def get_matches(): """Grabs match data from server, processes the results and returns match objects""" start = (date.today() - timedelta(days=btcs.MAX_DAYS_BEFORE)).isoformat() end = (date.today() + timedelta(days=btcs.MAX_DAYS_AFTER)).isoformat() url = "%s/GetFixturesByDateInterval?apikey=%s&startDateString=%s&endDateString=%s" \ % (btcs.SOCCER_URL, btcs.SOCCER_KEY, start, end) match_file = btcs.path('input', 'matches.xml') if (not os.path.exists(match_file)) or (time.time() - os.path.getmtime(match_file) > INTERVAL_ALL_GAMES): load_from_xmlsoccer(url, match_file) url = "%s/GetLiveScore?apikey=%s" % (btcs.SOCCER_URL, btcs.SOCCER_KEY) match_file = btcs.path('input', 'matches_live.xml') if (not os.path.exists(match_file)) or (time.time() - os.path.getmtime(match_file) > INTERVAL_LIVE_GAMES): load_from_xmlsoccer(url, match_file)
def purge(): # 1 => move old bets/new to bets/cancelled # 2 => move old bets/received to bets/archived (summing to bets/totals) # 3 => move old tx/new to tx/archived (summing to tx/totals) slips = os.listdir(btcs.path("bets/new", "")) maxage = datetime.datetime.utcnow() - datetime.timedelta(minutes=btcs.BETSLIP_RETENTION_MINUTES) def ftime(f): return datetime.datetime.fromtimestamp(os.path.getmtime(btcs.path("bets/new", f))) slips = [f for f in slips if ftime(f) < maxage] for slip in slips: logging.info("Cancelled betslip: " + slip) os.rename(btcs.path("bets/new", slip), btcs.path("bets/cancelled", slip))
def process_incoming(betslip): recv = wallet.getreceivedby(betslip, 0) if recv == 0: logging.info('Betslip %s: Nothing received' % (betslip)) return # read betslip with open(btcs.path('bets/new', betslip),'r') as f: betslip_data = json.loads(f.read()) # Sum total in betslip total = Decimal('0') for bets in betslip_data['bets']: total += Decimal(bets['amount']) total = total / Decimal('1000') if recv == total: logging.info('Betslip %s: Received %s, moving to received' % (betslip, recv)) os.rename(btcs.path('bets/new', betslip), btcs.path('bets/received', betslip)) # attach game data for email if 'email_address' in betslip_data: for bet in betslip_data['bets']: try: with open(btcs.path('games/new', bet['game']), 'r') as f: bet['game_data'] = json.loads(f.read()) except Exception: # only for email; do not trip over it pass notify_email.sendmail('email_betslip.html', betslip_data, betslip_data['email_address'], 'Betslip payment received') else: logging.warning('Betslip %s: Invalid amount received: %s, expected %s' % (betslip, recv, total)) logging.warning('Return amount manually')
def purge(): # 1 => move old bets/new to bets/cancelled # 2 => move old bets/received to bets/archived (summing to bets/totals) # 3 => move old tx/new to tx/archived (summing to tx/totals) slips = os.listdir(btcs.path('bets/new', '')) maxage = datetime.datetime.utcnow() - datetime.timedelta( minutes=btcs.BETSLIP_RETENTION_MINUTES) def ftime(f): return datetime.datetime.fromtimestamp( os.path.getmtime(btcs.path('bets/new', f))) slips = [f for f in slips if ftime(f) < maxage] for slip in slips: logging.info('Cancelled betslip: ' + slip) os.rename(btcs.path('bets/new', slip), btcs.path('bets/cancelled', slip))
def status(): # load all active games in memory games = { game: json.loads(open(btcs.path('games/new',game),'r').read()) for game in os.listdir(btcs.path('games/new', ''))} # load all active betslips slipids = os.listdir(btcs.path('bets/received', '')) slips = { slipid: json.loads(open(btcs.path('bets/received',slipid),'r').read()) for slipid in slipids} for gameid, game in games.iteritems(): logging.info('Game: ' + str(game)) logging.info('------') # sum all results found in bets for slip in slips.values(): for bet in slip['bets']: if bet['game'] == gameid: logging.info('Bet ' + str(slip['email_address']))
def process_incoming_all(): logging.info('process-incoming-all started') for betslip in os.listdir(btcs.path('bets/new', '')): process_incoming(betslip)
def generate_pub(): # load all active games in memory games = { game: json.loads(open(btcs.path('games/new',game),'r').read()) for game in os.listdir(btcs.path('games/new', ''))} # load all active betslips slipids = os.listdir(btcs.path('bets/received', '')) slips = { slipid: json.loads(open(btcs.path('bets/received',slipid),'r').read()) for slipid in slipids} # setup stats stats = { 'balance': wallet.getbalance() * 1000, 'balance_dispatch': wallet.getbalancedispatch() * 1000, 'total_bets_open': 0, 'total_bets_open_mbtc': 0, 'total_bets': 0, 'total_bets_mbtc': 0 } for gameid, game in games.iteritems(): # game result in a format suitable for template rendering game['results'] = [ { "away": a, "cols": [ { "score": 0 } for h in range(6) ] } for a in range(6) ] game['total'] = 0 # sum all results found in bets for slip in slips.values(): for bet in slip['bets']: if bet['game'] == gameid: h,a = bet['result'].split('-') h,a = int(h), int(a) am = int(bet['amount']) game['results'][a]['cols'][h]['score'] = ( game['results'][a]['cols'][h]['score'] + am) game['total'] = game['total'] + am stats['total_bets_open']+=1 stats['total_bets_open_mbtc'] += am # calculate multply for a in range(6): for h in range(6): s = game['results'][a]['cols'][h]['score'] # calculate estimated multiplier # the multiplier is total+bet / betted_here+bet minbet = btcs.MIN_BET * 1000 mult = (game['total'] + minbet) / (minbet + s) # round depends on size if mult<2: mult = 'x' + str(math.floor(mult*10)/10) else: mult = 'x' + str(math.floor(mult)) if mult[-2:] == '.0': mult = mult[:-2] game['results'][a]['cols'][h]['multiply'] = mult # walk again through slips to generate account info # and get totals accounts = {} for slipid in slips: slip = slips[slipid] if not slip['accountid'] in accounts: accounts[slip['accountid']] = { 'slips': [] } for bet in slip['bets']: if not bet['game'] in accounts[slip['accountid']]: accounts[slip['accountid']][bet['game']] = [] accounts[slip['accountid']][bet['game']].append(bet) stats['total_bets']+=1 stats['total_bets_mbtc'] += int(bet['amount']) accounts[slip['accountid']]['slips'].append(slipid) # write account-details for (accountid, account) in accounts.iteritems(): btcs.writejson(btcs.path('var', accountid), account) # sort by game date games = sorted(games.values(), key=lambda game: game['date']) # walk through games to generate data for templates for game in games: game['status'] = game['time'] if 'result' in game: (game['home_score'], game['away_score']) = game['result'].split('-') for f in ['home_goal_details', 'away_goal_details']: if f in game: if game[f]: game[f] = game[f].replace(';',"\n") else: game[f] = ' ' # split in live/today/later now = datetime.utcnow() #if os.path.exists(btcs.path('input', 'matches_live.xml')): # now = modification_date(btcs.path('input', 'matches_live.xml')) maxtime_live = (now + timedelta(minutes = btcs.DEADLINE_MINS)).isoformat() maxtime_today = datetime(now.year, now.month, now.day, 23,59,59,0, None).isoformat() live = [ game for game in games if game['date'] < maxtime_live] today = [ game for game in games if game['date'] >= maxtime_live and game['date'] < maxtime_today] later = [ game for game in games if game['date'] >= maxtime_today] alldata = { 'games': { 'live': live, 'today': today, 'later': later } } render('games.html', alldata) # now we're gonne generate stats # find latest txids from disk txids = os.listdir(btcs.path('tx/new', '')) # load their data txs = [ { "txid": txid, "info": json.loads(open(btcs.path('tx/new',txid),'r').read()) } for txid in txids] def sumtx(txtype, txs): total = 0 for tx in txs: if tx['info']['type'] == txtype: total += sum(tx['info']['outputs'].values()) return total * 1000 stats['total_tx_winnings'] = sumtx('winnings', txs) stats['total_tx_allwrong'] = sumtx('allwrong', txs) stats['total_tx_invalid'] = sumtx('invalid', txs) with open('../log/stats.log', 'a') as f: f.write(now.isoformat() + ',' + ','.join([str(f) for f in stats.values()])+"\n") txs.sort(key= lambda x: x['info']['game']['date']) txs.reverse() txs = txs[:btcs.TX_HIST_COUNT] # we should transform tx output dict to array for mustache rendering for tx in txs: tx['outputs'] = [ {"address": k, "amount":v*1000} for k,v in tx['info']['outputs'].items()] tx['game'] = tx['info']['game'] tx['game']['date'] = tx['game']['date'][:10] tx['type'] = tx['info']['type'].replace('allwrong', 'refund') stats['txs'] = txs render('stats.html', stats)
def process_outgoing_all(): for game in os.listdir(btcs.path('games/finished', '')): process_outgoing(game)
def process_outgoing(gameid): # read betslip with open(btcs.path('games/finished', gameid), 'r') as f: game = json.loads(f.read()) if game['time'] in ['Abandonded', 'Postponed']: result = game['time'] # this will result in everyone is wrong elif game['time'] == ['Finished AP']: # penalties, only count winner result = winner(game['result']) logging.info('Finished with penalties; winner=%s' % result) elif game['time'] in ['Finished', 'Finished AET']: result = game['result'] else: raise Exception('Unknown result type: %s' % game['time']) logging.info('Processing game %s; Result is %s', gameid, result) # collect all bets for this game correctbet = [] wrongbet = [] invalidbet = [] for betslipid in os.listdir(btcs.path('bets/received', '')): with open(btcs.path('bets/received', betslipid), 'r') as f: betslip = json.loads(f.read()) gamebets = [bet for bet in betslip['bets'] if bet['game'] == gameid] if not gamebets: continue # find the latest transaction for this slip tx = wallet.getlatesttx(betslipid) logging.info('TX = %s' % repr(tx)) # add latest transaction and return_address and email_address to bets for bet in gamebets: bet['latesttx'] = tx if 'return_address' in betslip: bet['return_address'] = betslip['return_address'] else: bet['return_address'] = wallet.findreturnaddress(tx) if 'email_address' in betslip: bet['email_address'] = betslip['email_address'] if within_deadline(game, bet): if bet['result'] == result or winner(bet['result']) == result: correctbet.append(bet) else: wrongbet.append(bet) else: invalidbet.append(bet) wallet.movetodispatch(betslipid, Decimal(bet['amount']) / Decimal('1000')) # move to process for atomicity if not DRYRUN: os.rename(btcs.path('games/finished', gameid), btcs.path('games/process', gameid)) # process payouts if invalidbet: logging.info('Invalid: ' + repr(invalidbet)) payout_tx = payout(invalidbet, sum_bets(invalidbet), game, 'invalid') if len(correctbet) > 0: logging.info('Correct: ' + repr(correctbet)) logging.info('Wrong : ' + repr(wrongbet)) total = sum_bets(wrongbet) * (Decimal('1') - btcs.BTCS_FEE) + sum_bets(correctbet) logging.info('Total wrong %s, total correct %s payout %s' % (repr( sum_bets(wrongbet)), repr(sum_bets(correctbet)), repr(total))) payout_tx = payout(correctbet, total, game, 'winnings') elif len(wrongbet) > 0: logging.info('Only wrong bets: ' + repr(wrongbet)) payout_tx = payout(wrongbet, sum_bets(wrongbet), game, 'allwrong') # grab all email_addresses (ignore invalids) allbets = wrongbet + correctbet allemails = set( [bet['email_address'] for bet in allbets if 'email_address' in bet]) for email in allemails: data = { "email": email, "game": game, "txid": payout_tx, "bets": [ bet for bet in allbets if 'email_address' in bet and bet['email_address'] == email ] } for bet in data['bets']: if not 'txtype' in bet: bet['txtype'] = 'lost' elif bet['txtype'] == 'allwrong': bet['txtype'] = 'refund' else: bet['txtype'] = 'won' subj = 'Result: %s - %s %s' % (game['home'], game['away'], game['result']) notify_email.sendmail('email_result.html', data, email, subj) if not DRYRUN: os.rename(btcs.path('games/process', gameid), btcs.path('games/archive', gameid))
def ftime(f): return datetime.datetime.fromtimestamp( os.path.getmtime(btcs.path('bets/new', f)))
def ftime(f): return datetime.datetime.fromtimestamp(os.path.getmtime(btcs.path("bets/new", f)))
def process_outgoing(gameid): # read betslip with open(btcs.path('games/finished', gameid),'r') as f: game = json.loads(f.read()) if game['time'] in ['Abandonded', 'Postponed']: result = game['time'] # this will result in everyone is wrong elif game['time'] == ['Finished AP']: # penalties, only count winner result = winner(game['result']) logging.info('Finished with penalties; winner=%s' % result) elif game['time'] in ['Finished', 'Finished AET']: result = game['result'] else: raise Exception('Unknown result type: %s' % game['time']) logging.info('Processing game %s; Result is %s', gameid, result) # collect all bets for this game correctbet = [] wrongbet = [] invalidbet = [] for betslipid in os.listdir(btcs.path('bets/received', '')): with open(btcs.path('bets/received', betslipid),'r') as f: betslip = json.loads(f.read()) gamebets = [bet for bet in betslip['bets'] if bet['game'] == gameid] if not gamebets: continue # find the latest transaction for this slip tx = wallet.getlatesttx(betslipid) logging.info('TX = %s' % repr(tx)) # add latest transaction and return_address and email_address to bets for bet in gamebets: bet['latesttx'] = tx if 'return_address' in betslip: bet['return_address'] = betslip['return_address'] else: bet['return_address'] = wallet.findreturnaddress(tx) if 'email_address' in betslip: bet['email_address'] = betslip['email_address'] if within_deadline(game, bet): if bet['result'] == result or winner(bet['result']) == result: correctbet.append(bet) else: wrongbet.append(bet) else: invalidbet.append(bet) wallet.movetodispatch(betslipid, Decimal(bet['amount']) / Decimal('1000')) # move to process for atomicity if not DRYRUN: os.rename(btcs.path('games/finished', gameid), btcs.path('games/process', gameid)) # process payouts if invalidbet: logging.info('Invalid: ' + repr(invalidbet)) payout_tx = payout(invalidbet, sum_bets(invalidbet), game, 'invalid') if len(correctbet) >0: logging.info('Correct: ' + repr(correctbet)) logging.info('Wrong : ' + repr(wrongbet)) total = sum_bets(wrongbet) * (Decimal('1') - btcs.BTCS_FEE) + sum_bets(correctbet) logging.info('Total wrong %s, total correct %s payout %s' % ( repr(sum_bets(wrongbet)), repr(sum_bets(correctbet)), repr(total))) payout_tx = payout(correctbet, total, game, 'winnings' ) elif len(wrongbet) > 0: logging.info('Only wrong bets: ' + repr(wrongbet)) payout_tx = payout(wrongbet, sum_bets(wrongbet), game, 'allwrong' ) # grab all email_addresses (ignore invalids) allbets = wrongbet + correctbet allemails = set([ bet['email_address'] for bet in allbets if 'email_address' in bet]) for email in allemails: data = { "email": email, "game": game, "txid": payout_tx, "bets": [ bet for bet in allbets if 'email_address' in bet and bet['email_address'] == email] } for bet in data['bets']: if not 'txtype' in bet: bet['txtype'] = 'lost' elif bet['txtype'] == 'allwrong': bet['txtype'] = 'refund' else: bet['txtype'] = 'won' subj = 'Result: %s - %s %s' % (game['home'], game['away'], game['result']) notify_email.sendmail('email_result.html', data, email, subj) if not DRYRUN: os.rename(btcs.path('games/process', gameid), btcs.path('games/archive', gameid))
def process_incoming_all(): logging.info('process-incoming-all started') for betslip in os.listdir(btcs.path('bets/new','')): process_incoming(betslip)
def process_xml(matches_data, match_file): matches_xml = ElementTree.fromstring(matches_data) new, updated, finished = 0, 0, 0 matches = [] for xml in matches_xml.findall('Match'): try: match = { 'id': xml.find('Id').text, 'home_id': xml.find('HomeTeam_Id').text , 'away_id': xml.find('AwayTeam_Id').text , 'league': xml.find('League').text, 'time': '' if xml.find('Time') is None else xml.find('Time').text } if xml.find('HomeTeam') is None: match['home'] = xml.find('Hometeam').text else: match['home'] = xml.find('HomeTeam').text if xml.find('AwayTeam') is None: match['away'] = xml.find('Awayteam').text else: match['away'] = xml.find('AwayTeam').text if not xml.find('HomeGoalDetails') is None: match['home_goal_details'] = xml.find('HomeGoalDetails').text if not xml.find('AwayGoalDetails') is None: match['away_goal_details'] = xml.find('AwayGoalDetails').text if not xml.find('HomeGoals') is None: match['result'] = xml.find('HomeGoals').text + '-' + xml.find('AwayGoals').text localtime = xml.find('Date').text match['date'] = dateutil.parser.parse(localtime).astimezone(dateutil.tz.tzutc()).isoformat() except: logging.error('Error processing game # %s in file %s' % (xml.find('Id').text, match_file)) raise matchid = int(match['id']) if not match['league'] in btcs.LEAGUES: continue paths = {state: btcs.path('games/'+state,matchid) for state in ['new', 'finished', 'process', 'archive']} # if game is alread done or processed, ignore it if os.path.exists(paths['finished']) or \ os.path.exists(paths['process']) or \ os.path.exists(paths['archive']): continue # if game is done now, we need to remove it from new if match['time'] in ['Finished', 'Abandonded', 'Cancelled', 'Postponed', 'Finished AET', 'Finished AP']: path = paths['finished'] finished += 1 if os.path.exists(paths['new']): os.remove(paths['new']) else: path = paths['new'] if os.path.exists(path): updated += 1 else: new += 1 with open(path, 'w') as f: f.write(json.dumps(match, sort_keys=True, indent=4, separators=(',', ': '))) #home = int(xml.find('HomeGoals').text) #away = int(xml.find('AwayGoals').text) #self.score = "%d-%d" % (home,away) logging.info('matches processed %d new, %d updated, %d finished' % (new, updated, finished))
def process_outgoing_all(): for game in os.listdir(btcs.path('games/finished','')): process_outgoing(game)