1+ """Decision schedules for controlling backtest evaluation."""
2+
13import re
24from collections .abc import Callable , Iterable , Iterator
35from datetime import datetime , timedelta
@@ -71,6 +73,12 @@ def _parse_step(s: str) -> timedelta | relativedelta:
7173
7274
7375class DecisionSchedule [I : Comparable ]:
76+ """Iterable schedule of decision points.
77+
78+ Schedules can be built from explicit iterables of comparable values or from
79+ time-based schedules created via :func:`make_decision_schedule`.
80+ """
81+
7482 _schedule : Iterable [I ]
7583 _start : I
7684 _end : I | None
@@ -94,9 +102,16 @@ def __init__(
94102
95103 @property
96104 def schedule (self ) -> Iterable [I ]:
105+ """Return the underlying iterable of schedule values."""
97106 return self ._schedule
98107
99108 def __iter__ (self ) -> Iterator [I ]:
109+ """Iterate over schedule values within optional bounds.
110+
111+ The iterator skips values before ``start`` and stops at ``end`` (with
112+ optional inclusive behavior) while validating that the schedule is
113+ non-decreasing.
114+ """
100115 start = self ._start
101116 end = self ._end
102117 prev = None
@@ -180,6 +195,50 @@ def make_decision_schedule[I: Comparable](
180195 * ,
181196 inclusive_end : bool = True ,
182197) -> DecisionSchedule :
198+ """Create a decision schedule from an iterable or time-based string.
199+
200+ The schedule can be a re-iterable of comparable values or a string interval
201+ (e.g., ``"2h"``) or cron expression (e.g., ``"0 * * * *"``) when paired with
202+ a ``datetime`` start.
203+
204+ Example:
205+ >>> import datetime as dt
206+ >>> from backtest_lib.backtest.schedule import make_decision_schedule
207+ >>> schedule = make_decision_schedule(
208+ ... "2h",
209+ ... start=dt.datetime(2021, 11, 13),
210+ ... )
211+ >>> it = iter(schedule)
212+ >>> next(it)
213+ datetime.datetime(2021, 11, 13, 0, 0)
214+ >>> next(it)
215+ datetime.datetime(2021, 11, 13, 2, 0)
216+
217+ >>> cron_schedule = make_decision_schedule(
218+ ... "0 * * * *",
219+ ... start=dt.datetime(2025, 2, 1),
220+ ... )
221+ >>> cron_it = iter(cron_schedule)
222+ >>> next(cron_it)
223+ datetime.datetime(2025, 2, 1, 1, 0)
224+ >>> next(cron_it)
225+ datetime.datetime(2025, 2, 1, 2, 0)
226+
227+ >>> schedule = make_decision_schedule(
228+ ... [dt.datetime(2025, 1, 1), dt.datetime(2025, 1, 2)]
229+ ... )
230+ >>> list(schedule)
231+ [datetime.datetime(2025, 1, 1, 0, 0), datetime.datetime(2025, 1, 2, 0, 0)]
232+
233+ Args:
234+ schedule: Iterable of schedule values, interval string, or cron string.
235+ start: Start value (required for string schedules).
236+ end: Optional end value used to truncate the schedule.
237+ inclusive_end: Whether the end bound is inclusive.
238+
239+ Returns:
240+ DecisionSchedule for the provided specification.
241+ """
183242 if isinstance (schedule , str ):
184243 if not isinstance (start , DateTimeLike ):
185244 raise TypeError ("For string schedules, start must be a datetime." )
@@ -232,6 +291,29 @@ def decision_schedule_factory[I: Comparable](
232291 * ,
233292 inclusive_end : bool = True ,
234293) -> DecisionSchedule [I ]:
294+ """Create a decision schedule from a factory of iterators.
295+
296+ Example:
297+ >>> import datetime as dt
298+ >>> from backtest_lib.backtest.schedule import decision_schedule_factory
299+ >>> def factory():
300+ ... yield from [
301+ ... dt.datetime(2025, 1, 1),
302+ ... dt.datetime(2025, 1, 2),
303+ ... ]
304+ >>> schedule = decision_schedule_factory(factory)
305+ >>> list(schedule)
306+ [datetime.datetime(2025, 1, 1, 0, 0), datetime.datetime(2025, 1, 2, 0, 0)]
307+
308+ Args:
309+ factory: Callable that returns a fresh iterator of schedule values.
310+ start: Optional start value for bounds checking.
311+ end: Optional end value used to truncate the schedule.
312+ inclusive_end: Whether the end bound is inclusive.
313+
314+ Returns:
315+ DecisionSchedule built from the factory.
316+ """
235317 schedule = _IterFactoryIterable (factory )
236318
237319 if start is None :
0 commit comments