Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
fail-fast: false

steps:
Expand Down
7 changes: 7 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
Release History
---------------

2.11.0 (2025-12-11)
+++++++++++++++++++

**Libraries**

- Add python 3.13 & 3.14

2.10.2 (2025-11-10)
+++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ flumine is an open-source, event-based trading framework for sports betting, des

![Backtesting Analysis](docs/images/jupyterloggingcontrol-screenshot.png?raw=true "Jupyter Logging Control Screenshot")

Tested on Python 3.9, 3.10, 3.11 and 3.12.
Tested on Python 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14.

## installation

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ flumine patches `utcnow` when simulating, if you require the 'real datetime' thi
import datetime

with framework.simulated_datetime.real_time():
print(datetime.datetime.utcnow())
print(datetime.datetime.now(datetime.timezone.utc))
```

## Config
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ flumine is an open-source, event-based trading framework for sports betting, des

[join betcode slack group (2k+ members!)](https://join.slack.com/t/betcode-org/shared_invite/zt-2uer9n451-w1QOehxDcG_JXqQfjoMvQA)

Tested on Python 3.9, 3.10, 3.11 and 3.12.
Tested on Python 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14.

## installation

Expand Down
2 changes: 2 additions & 0 deletions examples/middleware/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def add_market(self, market) -> None:
"client_username": client.username,
},
)
if current_order.customer_order_ref is None:
continue
order = self._create_order_from_current(client, current_order, market)
if order:
order.update_current_order(current_order)
Expand Down
3 changes: 2 additions & 1 deletion examples/workers/terminate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def terminate(
markets_today = [
m
for m in markets
if m.market_start_datetime.date() == datetime.datetime.utcnow().date()
if m.market_start_datetime.date()
== datetime.datetime.now(datetime.timezone.utc).date()
and (
m.elapsed_seconds_closed is None
or (m.elapsed_seconds_closed and m.elapsed_seconds_closed < seconds_closed)
Expand Down
2 changes: 1 addition & 1 deletion flumine/__version__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__title__ = "flumine"
__description__ = "Betting trading framework"
__url__ = "https://github.com/betcode-org/flumine"
__version__ = "2.10.2"
__version__ = "2.11.0"
__author__ = "Liam Pauling"
__license__ = "MIT"
4 changes: 2 additions & 2 deletions flumine/controls/clientcontrols.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _validate(self, order: BaseOrder, package_type: OrderPackageType) -> None:
)

def _check_hour(self) -> None:
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
next_hour = now + datetime.timedelta(hours=1)
if self._next_hour is None:
self._set_next_hour()
Expand All @@ -78,7 +78,7 @@ def _check_hour(self) -> None:
self._set_next_hour()

def _set_next_hour(self) -> None:
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
self._next_hour = (now + datetime.timedelta(hours=1)).replace(
minute=0, second=0, microsecond=0
)
Expand Down
6 changes: 4 additions & 2 deletions flumine/events/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ class BaseEvent:
__slots__ = ["_time_created", "event", "exchange", "callback"]

def __init__(self, event, exchange: ExchangeType = ExchangeType.BETFAIR):
self._time_created = datetime.datetime.utcnow()
self._time_created = datetime.datetime.now(datetime.timezone.utc)
self.event = event
self.exchange = exchange

@property
def elapsed_seconds(self):
return (datetime.datetime.utcnow() - self._time_created).total_seconds()
return (
datetime.datetime.now(datetime.timezone.utc) - self._time_created
).total_seconds()

def __str__(self):
return "<{0} [{1}]>".format(self.EVENT_TYPE.name, self.QUEUE_TYPE.name)
Expand Down
14 changes: 9 additions & 5 deletions flumine/markets/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(
self.flumine = flumine
self.market_id = market_id
self.closed = False
self.date_time_created = datetime.datetime.utcnow()
self.date_time_created = datetime.datetime.now(datetime.timezone.utc)
self.date_time_closed = None
self.market_book = market_book
self.market_catalogue = market_catalogue
Expand Down Expand Up @@ -57,7 +57,7 @@ def open_market(self) -> None:

def close_market(self) -> None:
self.closed = True
self.date_time_closed = datetime.datetime.utcnow()
self.date_time_closed = datetime.datetime.now(datetime.timezone.utc)
logger.info(
"Market %s closed",
self.market_id,
Expand Down Expand Up @@ -177,12 +177,16 @@ def market_type(self) -> str:

@property
def seconds_to_start(self) -> float:
return (self.market_start_datetime - datetime.datetime.utcnow()).total_seconds()
return (
self.market_start_datetime - datetime.datetime.now(datetime.timezone.utc)
).total_seconds()

@property
def elapsed_seconds_closed(self) -> Optional[float]:
if self.closed and self.date_time_closed:
return (datetime.datetime.utcnow() - self.date_time_closed).total_seconds()
return (
datetime.datetime.now(datetime.timezone.utc) - self.date_time_closed
).total_seconds()

@property
def market_start_datetime(self):
Expand All @@ -191,7 +195,7 @@ def market_start_datetime(self):
elif self.market_catalogue:
return self.market_catalogue.market_start_time
else:
return datetime.datetime.utcfromtimestamp(0)
return datetime.datetime.fromtimestamp(0, datetime.timezone.utc)

@property
def market_start_hour_minute(self) -> Optional[str]:
Expand Down
18 changes: 11 additions & 7 deletions flumine/order/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ def __init__(
self.market_version = None # marketBook.version
self.async_ = None

self.date_time_created = datetime.datetime.utcnow()
self.date_time_created = datetime.datetime.now(datetime.timezone.utc)
self.date_time_execution_complete = None
self.date_time_status_update = datetime.datetime.utcnow()
self.date_time_status_update = datetime.datetime.now(datetime.timezone.utc)

self.cleared_order = None

Expand All @@ -119,7 +119,7 @@ def __init__(
def _update_status(self, status: OrderStatus) -> None:
self.status_log.append(status)
self.status = status
self.date_time_status_update = datetime.datetime.utcnow()
self.date_time_status_update = datetime.datetime.now(datetime.timezone.utc)
self.complete = self._is_complete()
if logger.isEnabledFor(logging.INFO):
logger.info("Order status update: %s" % self.status.value, extra=self.info)
Expand All @@ -135,7 +135,7 @@ def executable(self) -> None:

def execution_complete(self) -> None:
self._update_status(OrderStatus.EXECUTION_COMPLETE)
self.date_time_execution_complete = datetime.datetime.utcnow()
self.date_time_execution_complete = datetime.datetime.now(datetime.timezone.utc)
self.update_data.clear()

def cancelling(self) -> None:
Expand Down Expand Up @@ -248,13 +248,17 @@ def size_voided(self):
def elapsed_seconds(self) -> Optional[float]:
date_time_placed = self.responses.date_time_placed
if date_time_placed:
return (datetime.datetime.utcnow() - date_time_placed).total_seconds()
return (
datetime.datetime.now(datetime.timezone.utc) - date_time_placed
).total_seconds()
else:
return

@property
def elapsed_seconds_created(self) -> float:
return (datetime.datetime.utcnow() - self.date_time_created).total_seconds()
return (
datetime.datetime.now(datetime.timezone.utc) - self.date_time_created
).total_seconds()

@property
def elapsed_seconds_executable(self) -> Optional[float]:
Expand All @@ -266,7 +270,7 @@ def elapsed_seconds_executable(self) -> Optional[float]:
@property
def elapsed_seconds_status_update(self) -> float:
return (
datetime.datetime.utcnow() - self.date_time_status_update
datetime.datetime.now(datetime.timezone.utc) - self.date_time_status_update
).total_seconds()

@property
Expand Down
4 changes: 2 additions & 2 deletions flumine/order/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Responses:
"""Order responses"""

def __init__(self):
self.date_time_created = datetime.datetime.utcnow()
self.date_time_created = datetime.datetime.now(datetime.timezone.utc)
self.current_order = None # resources.CurrentOrder
self.place_response = None # resources.PlaceOrderInstructionReports
self.cancel_responses = []
Expand All @@ -18,7 +18,7 @@ def placed(self, response=None, dt: bool = True) -> None:
if response:
self.place_response = response
if dt:
self._date_time_placed = datetime.datetime.utcnow()
self._date_time_placed = datetime.datetime.now(datetime.timezone.utc)

def cancelled(self, response) -> None:
self.cancel_responses.append(response)
Expand Down
4 changes: 2 additions & 2 deletions flumine/order/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
self.pending_orders = pending_orders
self.status_log = []
self.status = TradeStatus.LIVE
self.date_time_created = datetime.datetime.utcnow()
self.date_time_created = datetime.datetime.now(datetime.timezone.utc)
self.date_time_complete = None

# status
Expand All @@ -65,7 +65,7 @@ def _update_status(self, status: TradeStatus) -> None:

def complete_trade(self) -> None:
self._update_status(TradeStatus.COMPLETE)
self.date_time_complete = datetime.datetime.utcnow()
self.date_time_complete = datetime.datetime.now(datetime.timezone.utc)
# reset strategy context
runner_context = self.strategy.get_runner_context(
self.market_id, self.selection_id, self.handicap
Expand Down
4 changes: 2 additions & 2 deletions flumine/simulation/simulatedorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def _create_place_response(
bet_id=str(bet_id) if bet_id else bet_id,
average_price_matched=self.average_price_matched,
size_matched=self.size_matched,
placed_date=datetime.datetime.utcnow(),
placed_date=datetime.datetime.now(datetime.timezone.utc),
error_code=error_code,
)

Expand All @@ -284,7 +284,7 @@ def cancel(self, market_book: MarketBook) -> SimulatedCancelResponse:
return SimulatedCancelResponse(
status="SUCCESS", # todo handle errors
size_cancelled=_size_cancelled,
cancelled_date=datetime.datetime.utcnow(),
cancelled_date=datetime.datetime.now(datetime.timezone.utc),
)
else:
return SimulatedCancelResponse(
Expand Down
8 changes: 6 additions & 2 deletions flumine/simulation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class NewDateTime(datetime.datetime):
def utcnow(cls):
return config.current_time

@classmethod
def now(cls, tz=None):
return config.current_time


class SimulatedDateTime:
def __init__(self):
Expand All @@ -18,7 +22,7 @@ def __call__(self, pt: datetime.datetime):
config.current_time = pt

def reset_real_datetime(self):
config.current_time = self._real_datetime.utcnow()
config.current_time = self._real_datetime.now(datetime.timezone.utc)

@contextmanager
def real_time(self):
Expand All @@ -29,7 +33,7 @@ def real_time(self):
datetime.datetime = NewDateTime

def __enter__(self):
config.current_time = datetime.datetime.utcnow()
config.current_time = datetime.datetime.now(datetime.timezone.utc)
self._real_datetime = datetime.datetime
datetime.datetime = NewDateTime
return datetime.datetime
Expand Down
8 changes: 4 additions & 4 deletions flumine/strategy/runnercontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ def __init__(self, selection_id: int):

def place(self, trade_id) -> None:
self.invested = True
self.datetime_last_placed = datetime.datetime.utcnow()
self.datetime_last_placed = datetime.datetime.now(datetime.timezone.utc)
if trade_id not in self.trades:
self.trades.append(trade_id)
if trade_id not in self.live_trades:
self.live_trades.append(trade_id)

def reset(self, trade_id) -> None:
self.datetime_last_reset = datetime.datetime.utcnow()
self.datetime_last_reset = datetime.datetime.now(datetime.timezone.utc)
try:
self.live_trades.remove(trade_id)
except ValueError:
Expand Down Expand Up @@ -53,12 +53,12 @@ def live_trade_count(self) -> int:
def placed_elapsed_seconds(self) -> Optional[float]:
if self.datetime_last_placed:
return (
datetime.datetime.utcnow() - self.datetime_last_placed
datetime.datetime.now(datetime.timezone.utc) - self.datetime_last_placed
).total_seconds()

@property
def reset_elapsed_seconds(self) -> Optional[float]:
if self.datetime_last_reset:
return (
datetime.datetime.utcnow() - self.datetime_last_reset
datetime.datetime.now(datetime.timezone.utc) - self.datetime_last_reset
).total_seconds()
6 changes: 4 additions & 2 deletions flumine/streams/historicalstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _process(self, data: list, publish_time: int) -> bool:
if not _definition_in_play:
active = False
elif self._listener.seconds_to_start:
_now = datetime.datetime.utcfromtimestamp(publish_time / 1e3)
_now = datetime.datetime.fromtimestamp(publish_time / 1e3)
_market_time = BaseResource.strip_datetime(_definition_market_time)
seconds_to_start = (_market_time - _now).total_seconds()
if seconds_to_start > self._listener.seconds_to_start:
Expand Down Expand Up @@ -158,7 +158,9 @@ def _process(self, race_updates: list, publish_time: int) -> bool:
# filter after start time
diff = (
race_cache.start_time
- datetime.datetime.utcfromtimestamp(publish_time / 1e3)
- datetime.datetime.fromtimestamp(
publish_time / 1e3, datetime.timezone.utc
)
).total_seconds()
# handle US races starting early by checking the update
if diff <= 0 or (
Expand Down
4 changes: 3 additions & 1 deletion flumine/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ def get_event_ids(markets: list, event_type_id: str) -> list:


def create_time(publish_time: int, id_: str) -> datetime.datetime:
pt_datetime = datetime.datetime.utcfromtimestamp(publish_time / 1e3)
pt_datetime = datetime.datetime.fromtimestamp(
publish_time / 1e3, datetime.timezone.utc
)
event_id, start_time = id_.split(".")
hour, minute = int(start_time[:2]), int(start_time[2:])
return pt_datetime.replace(hour=hour, minute=minute, second=0, microsecond=0)
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"betfairlightweight==2.22.0",
Expand Down
6 changes: 3 additions & 3 deletions tests/test_clientcontrols.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_validate(self, mock_check_hour):

@mock.patch("flumine.controls.clientcontrols.MaxTransactionCount._set_next_hour")
def test_check_hour(self, mock_set_next_hour):
self.trading_control._next_hour = datetime.datetime.utcnow()
self.trading_control._next_hour = datetime.datetime.now(datetime.timezone.utc)
self.trading_control._check_hour()

now = datetime.datetime.now()
Expand All @@ -94,7 +94,7 @@ def test_check_hour_prev_time(self):
self.trading_control._set_next_hour()
self.trading_control.current_transaction_count = 5069
self.trading_control.current_failed_transaction_count = 5069
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
now_1 = (now + datetime.timedelta(days=1)).replace(
minute=0, second=0, microsecond=0
)
Expand All @@ -110,7 +110,7 @@ def test_set_next_hour(self):
self.trading_control._next_hour = None

self.trading_control._set_next_hour()
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
now_1 = (now + datetime.timedelta(hours=1)).replace(
minute=0, second=0, microsecond=0
)
Expand Down
Loading