Skip to content
Open RaamTrade

RC2_TIMEZONE_UNKNOWN

astimezone(...) needs exactly one IANA timezone string literal, and must be chained straight to .hour, .minute, .month, or .weekday().

What it means

self.trade.timestamp.astimezone("<IANA>") re-expresses the bar’s timestamp in another region’s wall clock. Three rules make it checkable at compile time rather than at bar 40,000 of a backtest:

  • One argument, an IANA id, as a literal. The id is validated while you type, so "America/New_York" compiles and a typo does not.
  • No bare astimezone(). In Python that means “the machine’s local timezone” — which on a server is a property of the deployment, not of the strategy. A strategy whose entries move when it is scheduled onto a different node is not a strategy.
  • Chained, not stored. astimezone(...) is an access path, not a value: it must be followed immediately by .hour, .minute, .month, or .weekday().

Why it happens

A near-miss on the zone id — underscores and exact spelling matter:

class WrongZone(Strategy):
trade = Symbol()
sizing = FixedCapital()
def execute(self):
if self.trade.timestamp.astimezone("America/New York").hour >= 10 and self.trade.is_flat:
self.buy(self.trade)

Python’s no-argument form, meaning “local time”:

class LocalTime(Strategy):
trade = Symbol()
sizing = FixedCapital()
def execute(self):
if self.trade.timestamp.astimezone().hour >= 10 and self.trade.is_flat:
self.buy(self.trade)

Or the converted time saved to a variable to be reused — the natural refactor, and the one the language doesn’t allow:

class StoredZone(Strategy):
trade = Symbol()
sizing = FixedCapital()
def execute(self):
ny_now = self.trade.timestamp.astimezone("America/New_York")
if ny_now.hour >= 10 and self.trade.is_flat:
self.buy(self.trade)

Passing the zone through a variable (zone = "America/New_York" … astimezone(zone)) is refused for the same reason: a variable defeats the compile-time check.

How to fix it

Write the id as a literal and read the field off the end of the chain. Repeat the chain if you need two fields — it is a path, and paths are cheap.

class SessionClock(Strategy):
trade = Symbol()
sizing = FixedCapital()
def execute(self):
ny_hour = self.trade.timestamp.astimezone("America/New_York").hour
if ny_hour >= 10 and self.trade.timestamp.weekday() < 5 and self.trade.is_flat:
self.buy(self.trade)

The field can be stored in a local (ny_hour above is an ordinary number); only the conversion itself cannot.

If all you need is the bar’s own clock, you don’t need astimezone at all — self.trade.timestamp is already expressed in the slot’s session timezone:

class SessionLocal(Strategy):
trade = Symbol(session=USEquityHourly)
sizing = FixedCapital()
def execute(self):
if self.trade.timestamp.hour >= 10 and self.trade.is_flat:
self.buy(self.trade)

It costs you the fast path

astimezone (like value_at) drops a strategy off the analyzed graph path onto the slower passthrough one. Reading the slot’s own day or month with self.trade.timestamp.weekday() or self.trade.timestamp.month does not — it keeps the fast path. That is invisible in a single backtest and very visible in an optimization or a walk-forward run — worth knowing before you reach for it in a hot condition.