How I Built This: A Sports Betting Exchange (Part 3: The Exchange)

 

Hurdle 5: How Do I Go About Building an Exchange?

Understanding Exchange Logic




  • “bid” to “back” or to bet for a team
  • “ask” to “lay” or to bet against a team
  • “price” to “odds”
  • “size” to “amount”

Requirements

  • Ingest, store, and distribute sporting event updates in near real-time
  • Execute and distribute market and limit orders in real-time
  • Cancel all non-executed orders in the order book when an event ends

Ingest, store, and distribute sporting event updates in near real-time

  • Real-time or near real-time event updates
  • Full-season schedules
  • Free to use or a low-cost subscription
  • An easy to use API or SDK
  • Supported football, basketball, baseball, and hockey
  • Airflow’s concept of Operators and DAG’s, or directed acyclic graphs, to modularize code, especially in orchestration of processing data
  • Kinesis or Kafka’s concept of using an intermediary pub/sub or queueing device to separate the processing of one component from another

Execute and distribute market and limit orders in real-time

  • Can handle up to thousands of sports games or events
  • Can handle up to thousands of orders or bets per second
  • Can persist partially executed or non-executed orders in a priority queueing mechanism

Additionally I could separate the exchange from the handling of incoming orders through the following abstraction:

from sortedcontainers import SortedSet
class MockRedis:
def __init__(self):
self.kv_store = {}
def zadd(self, name: str, mapping: map):
for data, score in mapping.items():
if name in self.kv_store:
self.kv_store[name].add((data.encode("utf-8"), score))
else:
self.kv_store[name] = SortedSet([(data.encode("utf-8"), score)], key=lambda value: value[1])
def zpopmin(self, name: str):
if name in self.kv_store:
try:
return [self.kv_store[name].pop(index=0)]
except IndexError:
return []
else:
return []
def zpopmax(self, name: str):
if name in self.kv_store:
try:
return [self.kv_store[name].pop(index=-1)]
except IndexError:
return []
else:
return []
def get(self, name: str):
return self.kv_store.get(name)
def delete(self, *names):
for name in names:
if name in self.kv_store:
del self.kv_store[name]
def set(self, name: str, value: str):
self.kv_store[name] = value.encode("utf-8")
def zrangebyscore(self, name: str, min: any, max: any, withscores: bool = False):
if name in self.kv_store:
values = list(self.kv_store[name].irange((None, min), (None, max)))
if withscores:
return values
return list(map(lambda x: x[0], values))
return []
def zrem(self, name: str, *values: any):
if name in self.kv_store:
for value in values:
matching_value = None
for sorted_set_val in self.kv_store[name]:
if value.encode("utf-8") == sorted_set_val[0]:
matching_value = sorted_set_val
break
if matching_value:
self.kv_store[name].remove(matching_value)
                                                          Redis Commander to help view and manage Redis

Cancel all non-executed orders in the order book when an event ends

The Architecture




Post a Comment

0 Comments