Skip to content

Add contract days_to_expiry/dte and interpret Python date/datetime type errors - #9665

Closed
jhonabreul wants to merge 1 commit into
QuantConnect:masterfrom
jhonabreul:feature-unify-date-datetime-expiry
Closed

Add contract days_to_expiry/dte and interpret Python date/datetime type errors#9665
jhonabreul wants to merge 1 commit into
QuantConnect:masterfrom
jhonabreul:feature-unify-date-datetime-expiry

Conversation

@jhonabreul

Copy link
Copy Markdown
Collaborator

Description

Python algorithms constantly mix .NET-converted datetime values (self.time, contract.id.date, expiries) with pure Python date objects, and die on DTE math — the single most repeated runtime arithmetic failure across user deployments:

Trying to perform a summation, subtraction, multiplication or division between 'datetime.datetime' and 'datetime.date' objects throws a TypeError exception...
can't compare datetime.datetime to datetime.date
tzinfo argument must be None or of a tzinfo subclass, not type 'CachedDateTimeZone'
type(s) expected

The change:

  • BaseContract.DaysToExpiry() / DaysToExpiry(DateTime reference) and BaseContract.DTE on option and future contracts (contract.days_to_expiry(reference) / contract.dte in Python): whole days between the reference date (default: the contract's current Time) and Expiry, date parts only. From Python the reference accepts both datetime and date instances (pythonnet converts either), removing the need for the failing manual arithmetic. DTE is [PandasIgnore]d so dataframe shapes don't change.
  • UnsupportedOperandPythonExceptionInterpreter now also interprets the can't compare datetime.datetime to datetime.date TypeError, and when the offending operands are datetime.datetime vs datetime.date (in either flavor) it appends a hint pointing at .date() alignment and contract.days_to_expiry(reference) / contract.dte.
  • New TzInfoPythonExceptionInterpreter: datetime.now(TimeZones.NEW_YORK) now explains that Lean time zones are NodaTime DateTimeZone objects and points at datetime.now(ZoneInfo("America/New_York")) and ZoneInfo(str(TimeZones.NEW_YORK)). It only fires when the rejected tzinfo type is a DateTimeZone.
  • AlgorithmImports now imports ZoneInfo (stdlib zoneinfo), so the hinted fix works without an extra import — this also preempts the observed follow-up failure of reaching for pytz, which is not imported either.
  • New GenericTypeParameterPythonExceptionInterpreter: pythonnet's bare type(s) expected TypeError (e.g. RollingWindow[datetime](30)) now explains that Python types cannot parameterize .NET generics and points at RollingWindow[DateTime], the int/float/bool/str aliases, and the untyped RollingWindow(n).

Related work: QuantConnect/pythonnet#143 makes converted DateTime values coerce against pure date operands at the interop layer; the interpreter hints here remain useful for pure-Python mixes (e.g. a stored plain datetime vs self.time.date()) and until that package rolls out.

Deferred (needs interop-layer changes, no Lean-side path):

  • Making RollingWindow[datetime] actually work: requires mapping Python datetime/date types to System.DateTime in pythonnet's Converter.GetTypeByAlias (used by Runtime.PythonArgsToTypeArray and ClassObject.type_subscript), including the clr_datetime subclass type introduced by Support subtraction and ordering between converted DateTime values and datetime.date pythonnet#143.
  • Making TimeZones.NEW_YORK directly usable as a Python tzinfo: a CLR-wrapped NodaTime DateTimeZone cannot subclass Python's tzinfo; the new interpreter + ZoneInfo import is the Lean-side answer.

Related Issue

Part of QuantConnect/Agents#305 (improvement #6: unify date/datetime across the expiry/time surface)

Motivation and Context

DTE filtering is line one of nearly every options/futures selector users write; fleet evidence shows the same failing line reinvented across many deployments, plus a three-error timezone chain (TimeZones as tzinfo → pytz not defined → naive-vs-aware compare) that burned three backtests per step.

Requires Documentation Change

Yes: document days_to_expiry(reference) / dte on option and future contracts, and that ZoneInfo is available in algorithms.

How Has This Been Tested?

  • ContractDaysToExpiryRegressionAlgorithm (C# + Python twins): validates DaysToExpiry() / DTE against manual expiry math on every chain contract; the Python twin additionally passes datetime, date and keyword references. Both language runs pass with identical statistics.
  • OptionContractTests / FuturesContractTests: DaysToExpiry with default and explicit references, plus a Python-engine test binding days_to_expiry with date, datetime and reference= arguments.
  • UnsupportedOperandPythonExceptionInterpreterTests: real Python TypeErrors for subtraction and comparison shapes get the hint; non-datetime operands and naive-vs-aware compares do not.
  • New TzInfoPythonExceptionInterpreterTests / GenericTypeParameterPythonExceptionInterpreterTests: real exceptions from datetime.now(TimeZones.NEW_YORK) and RollingWindow[datetime](10); a string tzinfo is not interpreted.
  • Full QuantConnect.Tests.Common.Exceptions + QuantConnect.Tests.Common.Data.Market namespaces: 211 passed, 0 failed.
  • End-to-end Launcher runs of the three fleet shapes before/after: verbatim fleet errors reproduced on clean master; new messages and the ZoneInfo path verified after.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

… errors

- BaseContract.DaysToExpiry(reference)/DTE on option and future contracts;
  from Python the reference accepts both datetime and date instances
- UnsupportedOperandPythonExceptionInterpreter also interprets the
  datetime-vs-date comparison TypeError and appends a hint pointing at
  .date() alignment and days_to_expiry/dte for the datetime-date shapes
- New TzInfoPythonExceptionInterpreter for TimeZones values passed as
  Python tzinfo, pointing at zoneinfo; AlgorithmImports imports ZoneInfo
- New GenericTypeParameterPythonExceptionInterpreter for pythonnet's
  'type(s) expected' when parameterizing generics with Python types
- ContractDaysToExpiryRegressionAlgorithm (C# + Python) and unit tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jhonabreul

Copy link
Copy Markdown
Collaborator Author

Superseded: this surface is already addressed by pythonnet PR #143 (datetime/date coercion at the interop layer). Closing per review.

@jhonabreul jhonabreul closed this Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant