Skip to content

Commit

Permalink
Add functions
Browse files Browse the repository at this point in the history
* calculate_order_book_imbalance

* get_market_time_as_datetime
* get_seconds_to_market_time

* get_price_size_by_depth
* get_second_best_price_size
* get_second_best_price
  • Loading branch information
mberk committed Dec 28, 2022
1 parent 714ed1f commit a4c98b3
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 1 deletion.
102 changes: 102 additions & 0 deletions betfairutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,25 @@ def calculate_market_book_diff(
return MarketBookDiff(diff)


def calculate_order_book_imbalance(
runner_book: Union[Dict[str, Any], RunnerBook]
) -> Optional[float]:
best_back_price_size = get_best_price_size(runner_book, Side.BACK)
if best_back_price_size is not None:
best_lay_price_size = get_best_price_size(runner_book, Side.LAY)
if best_lay_price_size is not None:
if type(best_back_price_size) is PriceSize:
back_size = best_back_price_size.size
lay_size = best_lay_price_size.size
else:
back_size = best_back_price_size["size"]
lay_size = best_lay_price_size["size"]

numerator = back_size - lay_size
denominator = back_size + lay_size
return numerator / denominator


def calculate_price_difference(a: Union[int, float], b: Union[int, float]) -> int:
"""
Calculate the price difference between two prices as the number of steps on the Betfair price ladder. For example, the difference between 1.03 and 1.01 is 2. Conversely, the difference between 1.01 and 1.03 is -2
Expand Down Expand Up @@ -1768,6 +1787,34 @@ def get_best_price(
return best_price_size["price"]


def get_price_size_by_depth(
runner: Union[Dict[str, Any], RunnerBook], side: Side, depth: int
) -> Optional[Union[Dict[str, Union[int, float]], PriceSize]]:
if type(runner) is RunnerBook:
available = getattr(runner.ex, side.ex_attribute)
else:
available = runner.get("ex", {}).get(side.ex_key, [])

if len(available) > depth:
return available[depth]


def get_second_best_price_size(
runner: Union[Dict[str, Any], RunnerBook], side: Side
) -> Optional[Union[Dict[str, Union[int, float]], PriceSize]]:
return get_price_size_by_depth(runner=runner, side=side, depth=1)


def get_second_best_price(
runner: Union[Dict[str, Any], RunnerBook], side: Side
) -> Optional[Union[int, float]]:
second_best_price_size = get_second_best_price_size(runner, side)
if type(second_best_price_size) is PriceSize:
return second_best_price_size.price
elif type(second_best_price_size) is dict:
return second_best_price_size["price"]


def get_best_price_with_rollup(
runner: Union[Dict[str, Any], RunnerBook], side: Side, rollup: Union[int, float]
) -> Optional[Union[int, float]]:
Expand Down Expand Up @@ -1964,6 +2011,61 @@ def get_win_market_id_from_race_card(
return market_id


def get_market_time_as_datetime(
market_book: Union[Dict[str, Any], MarketBook]
) -> datetime.datetime:
"""
Extract the market time - i.e. the time at which the market is due to start - as a TIMEZONE AWARE datetime object
:param market_book: The market book either as a dictionary or betfairlightweight MarketBook object from which to
extract the market (start) time
:return: The market (start) time as a TIMEZONE AWARE datetime object
"""
if type(market_book) is MarketBook:
market_time_datetime = market_book.market_definition.market_time.replace(
tzinfo=datetime.timezone.utc
)
else:
market_time_string = market_book["marketDefinition"]["marketTime"]
market_time_datetime = datetime.datetime.strptime(
market_time_string, "%Y-%m-%dT%H:%M:%S.000Z"
).replace(tzinfo=datetime.timezone.utc)

return market_time_datetime


def get_seconds_to_market_time(
market_book: Union[Dict[str, Any], MarketBook],
current_time: Optional[Union[int, datetime.datetime]] = None,
) -> float:
"""
Given a market book and an optional notional current time, extract the market (start) time from the market book and
calculate the difference in seconds between it and the current time. If current_time is not provided then the
publish time of the market book will be used
:param market_book: A market book either as a dictionary or betfairlightweight MarketBook object
:param current_time: An optional notional current time, either as an integer number of milliseconds since the Unix
epoch or a datetime object
:return: The number of seconds between the market time and current_time if provided, otherwise the number of seconds
between the market time and the publish time of the market book
"""
market_time = get_market_time_as_datetime(market_book)

if current_time is None:
if type(market_book) is MarketBook:
current_time = market_book.publish_time.replace(
tzinfo=datetime.timezone.utc
)
else:
current_time = market_book["publishTime"]

if type(current_time) is int:
current_time = publish_time_to_datetime(current_time)

seconds_to_market_time = (market_time - current_time).total_seconds()
return seconds_to_market_time


def decrement_price(price: Union[int, float]) -> Optional[Union[int, float]]:
"""
Given a price return the next lower price on the Betfair price ladder
Expand Down
99 changes: 98 additions & 1 deletion tests/test_non_prices.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import json
from copy import deepcopy
from pathlib import Path
Expand All @@ -14,6 +15,7 @@

from betfairutil import calculate_book_percentage
from betfairutil import calculate_market_book_diff
from betfairutil import calculate_order_book_imbalance
from betfairutil import calculate_total_matched
from betfairutil import convert_yards_to_metres
from betfairutil import DataFrameFormatEnum
Expand All @@ -26,11 +28,15 @@
from betfairutil import get_event_id_from_string
from betfairutil import get_market_books_from_prices_file
from betfairutil import get_market_id_from_string
from betfairutil import get_market_time_as_datetime
from betfairutil import get_minimum_book_percentage_market_books_from_prices_file
from betfairutil import get_pre_event_volume_traded_from_prices_file
from betfairutil import get_race_change_from_race_file
from betfairutil import get_race_id_from_string
from betfairutil import get_runner_book_from_market_book
from betfairutil import get_second_best_price
from betfairutil import get_second_best_price_size
from betfairutil import get_seconds_to_market_time
from betfairutil import get_selection_id_to_runner_name_map_from_market_catalogue
from betfairutil import get_race_distance_in_metres_from_race_card
from betfairutil import get_win_market_id_from_race_card
Expand Down Expand Up @@ -255,7 +261,10 @@ def test_does_market_book_contain_runner_names(market_book: Dict[str, Any]):
assert does_market_book_contain_runner_names(market_book)
assert not does_market_book_contain_runner_names(MarketBook(**market_book))
assert does_market_book_contain_runner_names(
MarketBook(**market_book, market_definition=market_book["marketDefinition"])
MarketBook(
**market_book,
market_definition=MarketDefinition(**market_book["marketDefinition"]),
)
)


Expand Down Expand Up @@ -958,3 +967,91 @@ def test_get_minimum_book_percentage_market_books_from_prices_file(
)[(0, market_book["publishTime"] + 50)]
is not None
)


def test_calculate_order_book_imbalance(market_book: Dict[str, Any]):
runner_book = market_book["runners"][0]
runner_book["ex"]["availableToLay"].append({"price": 1.99, "size": 2})

assert calculate_order_book_imbalance(runner_book) == -1.0 / 3.0
assert calculate_order_book_imbalance(RunnerBook(**runner_book)) == -1.0 / 3.0


def test_get_second_best_price_size(market_book: Dict[str, Any]):
runner_book = market_book["runners"][0]

assert get_second_best_price_size(runner_book, Side.BACK) is None
assert get_second_best_price_size(RunnerBook(**runner_book), Side.BACK) is None

runner_book["ex"]["availableToBack"].append({"price": 1.97, "size": 2})

assert get_second_best_price_size(runner_book, Side.BACK) == {
"price": 1.97,
"size": 2,
}

second_best_price_size = get_second_best_price_size(
RunnerBook(**runner_book), Side.BACK
)
assert second_best_price_size.price == 1.97
assert second_best_price_size.size == 2


def test_get_second_best_price(market_book: Dict[str, Any]):
runner_book = market_book["runners"][0]

assert get_second_best_price(runner_book, Side.BACK) is None
assert get_second_best_price(RunnerBook(**runner_book), Side.BACK) is None

runner_book["ex"]["availableToBack"].append({"price": 1.97, "size": 2})

assert get_second_best_price(runner_book, Side.BACK) == 1.97
assert get_second_best_price(RunnerBook(**runner_book), Side.BACK) == 1.97


def test_get_market_time_as_datetime(market_book: Dict[str, Any]):
assert get_market_time_as_datetime(market_book) == datetime.datetime(
year=2022,
month=4,
day=3,
hour=14,
minute=0,
second=0,
tzinfo=datetime.timezone.utc,
)
assert get_market_time_as_datetime(
MarketBook(
**market_book,
market_definition=MarketDefinition(**market_book["marketDefinition"]),
)
) == datetime.datetime(
year=2022,
month=4,
day=3,
hour=14,
minute=0,
second=0,
tzinfo=datetime.timezone.utc,
)


def test_get_seconds_to_market_time(market_book: Dict[str, Any]):
current_time = datetime.datetime.strptime(
"2022-01-01T00:00:00.000Z", "%Y-%m-%dT%H:%M:%S.000Z"
).replace(tzinfo=datetime.timezone.utc)
assert get_seconds_to_market_time(market_book, current_time) == 7999200.0
assert (
get_seconds_to_market_time(market_book, int(current_time.timestamp() * 1000))
== 7999200.0
)

assert get_seconds_to_market_time(market_book) == 0.0
assert (
get_seconds_to_market_time(
MarketBook(
**market_book,
market_definition=MarketDefinition(**market_book["marketDefinition"]),
)
)
== 0.0
)

0 comments on commit a4c98b3

Please sign in to comment.