ClockRuntime¶
Module: chronopype.runtime.clock_runtime
Manages clock lifecycle: initialization, async start/stop, and threaded mode. Can be used as an async context manager or with explicit start()/stop() calls.
Constructor¶
ClockRuntime(
config: ClockRuntimeConfig | None = None,
clock: BaseClock | None = None,
)
| Parameter | Description |
|---|---|
config |
Runtime configuration. Defaults to ClockRuntimeConfig() (realtime, 1s tick). |
clock |
Pre-built clock instance. Takes precedence over the config's clock_config for clock construction. |
Properties¶
config¶
@property
def config(self) -> ClockRuntimeConfig
Return the runtime configuration.
clock¶
@property
def clock(self) -> BaseClock
Return the underlying clock instance.
is_running¶
@property
def is_running(self) -> bool
True if the clock is running. Checks the actual state of async tasks, threads, and context --- a crashed task correctly returns False.
task_error¶
@property
def task_error(self) -> BaseException | None
Return the exception from a failed clock task (realtime async mode), or None if the task is healthy or not started.
Methods¶
start()¶
async def start(self) -> None
Start the clock. Behavior depends on the clock type (determined via isinstance, not the config):
- BacktestClock: Enters the clock context (initializes processors) but does not create a background task. Drive the clock manually with
backtest_til(). - RealtimeClock: Enters the clock context and creates an async task that runs
clock.run().
Idempotent --- calling start() on an already-started runtime is a no-op.
stop(timeout=None)¶
async def stop(self, timeout: float | None = None) -> None
Stop the clock. Cancels any async task and exits the clock context. Safe to call without a prior start(), and safe to call multiple times.
| Parameter | Description |
|---|---|
timeout |
Max seconds to wait for task cancellation. Defaults to config.thread_stop_timeout_seconds. |
backtest_til(target_time)¶
async def backtest_til(self, target_time: float) -> None
Advance a backtest clock to target_time. When the clock context is active, delegates to BacktestClock.step_to(). Without context, updates the raw timestamp (with end-time validation).
Raises:
ClockRuntimeErrorif the clock is not aBacktestClock.ClockRuntimeErroriftarget_timeexceedsend_time(without context).ClockErroriftarget_timeexceedsend_time(with context, viastep_to).
start_threaded(on_error_callback=None)¶
def start_threaded(
self,
on_error_callback: Callable[[str], None] | None = None,
) -> None
Start the clock in a dedicated daemon thread with its own event loop. Only supported for RealtimeClock. The event loop is available via get_clock_loop() immediately after this method returns.
Idempotent --- calling on an already-running thread is a no-op.
Raises: ClockRuntimeError if the clock is not a RealtimeClock.
stop_threaded()¶
def stop_threaded(self) -> None
Stop the clock thread. Signals the stop event and joins the thread with a timeout. Logs a warning if the thread does not stop within the timeout. Safe to call without a prior start_threaded().
get_clock_loop()¶
def get_clock_loop(self) -> asyncio.AbstractEventLoop | None
Return the clock thread's event loop when in threaded mode, else None. Use with asyncio.run_coroutine_threadsafe() to schedule work on the clock thread.
Async Context Manager¶
async with ClockRuntime(config=config) as rt:
# clock is running
...
# clock is stopped
Equivalent to calling start() on entry and stop() on exit.
Example¶
import asyncio
from chronopype import (
ClockConfig, ClockMode, ClockRuntime, ClockRuntimeConfig,
)
from chronopype.processors import TickProcessor
class Logger(TickProcessor):
async def async_tick(self, timestamp: float) -> None:
print(f"tick {timestamp:.1f}")
async def main():
config = ClockRuntimeConfig(
clock_config=ClockConfig(
clock_mode=ClockMode.BACKTEST,
tick_size=1.0,
start_time=100.0,
end_time=200.0,
)
)
async with ClockRuntime(config=config) as rt:
rt.clock.add_processor(Logger())
await rt.backtest_til(110.0)
print(f"Current: {rt.clock.current_timestamp}")
asyncio.run(main())