Ctrl K
ComunidadGuías…

app/markettime.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Trade times, and the two clocks they can be read on.

Storage is always the exchange's own clock. `models.trade_order` sequences a
day's trades for FIFO, and a stored time that moved with whoever was looking
would reorder a same-day buy and sell — which decides a capital gain.

So this converts for DISPLAY only, and the conversion needs the trade's date
rather than just its time: an ASX trade in January and one in July are an hour
apart, because the offset is a property of the moment, not of the zone.
"""

from __future__ import annotations

import datetime as dt
from zoneinfo import ZoneInfo

from .pricefeed import MARKETS


def market_zone(exchange: str) -> ZoneInfo | None:
    """The exchange's timezone, or None where it has no session (crypto)."""
    market = MARKETS.get((exchange or "").upper())
    if market is None or market.zone is None:
        return None
    return ZoneInfo(market.zone)


def aware(date: dt.date, clock: dt.time | None, exchange: str) -> dt.datetime | None:
    """A trade's moment, on the exchange's clock, with its offset attached.

    None where the exchange has no timezone — an offset invented for a market
    that never closes would be a fact this app does not have.
    """
    zone = market_zone(exchange)
    if zone is None:
        return None
    from .models import MARKET_OPEN  # noqa: PLC0415 - avoids a circular import

    return dt.datetime.combine(date, clock or MARKET_OPEN, tzinfo=zone)


def to_market(clock: dt.time, date: dt.date, from_zone: str,
              exchange: str) -> tuple[dt.date, dt.time]:
    """A wall-clock date and time somewhere else, read on the exchange's clock.

    For an export that stamps trades in the account holder's own timezone
    rather than the market's. The date comes too: US hours are the small hours
    of the next day in Sydney, and a converted time on the unconverted date is
    a day out. Returns both unchanged when either zone is unknown, because a
    half-applied conversion is worse than none.
    """
    zone = market_zone(exchange)
    if zone is None or not from_zone:
        return date, clock
    try:
        origin = ZoneInfo(from_zone)
    except Exception:  # noqa: BLE001 - an unknown zone name is user input
        return date, clock
    moment = dt.datetime.combine(date, clock, tzinfo=origin).astimezone(zone)
    return moment.date(), moment.time()