Split stock market open/close code into own file
This commit is contained in:
		
							parent
							
								
									2ea79c12d9
								
							
						
					
					
						commit
						74e135e9c3
					
				| 
						 | 
				
			
			@ -5,17 +5,15 @@ import operator
 | 
			
		|||
import os
 | 
			
		||||
import typing
 | 
			
		||||
import warnings
 | 
			
		||||
from datetime import date, datetime, time, timedelta, timezone
 | 
			
		||||
from datetime import date, datetime, time, timedelta
 | 
			
		||||
from time import time as unixtime
 | 
			
		||||
 | 
			
		||||
import dateutil
 | 
			
		||||
import dateutil.parser
 | 
			
		||||
import dateutil.tz
 | 
			
		||||
import exchange_calendars
 | 
			
		||||
import holidays
 | 
			
		||||
import httpx
 | 
			
		||||
import lxml
 | 
			
		||||
import pandas
 | 
			
		||||
import pytz
 | 
			
		||||
import yaml
 | 
			
		||||
from dateutil.easter import easter
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +27,8 @@ from . import (
 | 
			
		|||
    fx,
 | 
			
		||||
    gwr,
 | 
			
		||||
    markets,
 | 
			
		||||
    stock_market,
 | 
			
		||||
    subscription,
 | 
			
		||||
    sun,
 | 
			
		||||
    thespacedevs,
 | 
			
		||||
    travel,
 | 
			
		||||
| 
						 | 
				
			
			@ -158,60 +158,6 @@ def uk_financial_year_end(input_date: date) -> date:
 | 
			
		|||
    return uk_financial_year_end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def timedelta_display(delta: timedelta) -> str:
 | 
			
		||||
    """Format timedelta as a human readable string."""
 | 
			
		||||
    total_seconds = int(delta.total_seconds())
 | 
			
		||||
    days, remainder = divmod(total_seconds, 24 * 60 * 60)
 | 
			
		||||
    hours, remainder = divmod(remainder, 60 * 60)
 | 
			
		||||
    mins, secs = divmod(remainder, 60)
 | 
			
		||||
 | 
			
		||||
    return " ".join(
 | 
			
		||||
        f"{v:>3} {label}"
 | 
			
		||||
        for v, label in ((days, "days"), (hours, "hrs"), (mins, "mins"))
 | 
			
		||||
        if v
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stock_markets() -> list[str]:
 | 
			
		||||
    """Stock markets open and close times."""
 | 
			
		||||
    # The trading calendars code is slow, maybe there is a faster way to do this
 | 
			
		||||
    # Or we could cache the result
 | 
			
		||||
    now = pandas.Timestamp.now(timezone.utc)
 | 
			
		||||
    now_local = pandas.Timestamp.now(here)
 | 
			
		||||
    markets = [
 | 
			
		||||
        ("XLON", "London"),
 | 
			
		||||
        ("XNYS", "US"),
 | 
			
		||||
    ]
 | 
			
		||||
    reply = []
 | 
			
		||||
    for code, label in markets:
 | 
			
		||||
        cal = exchange_calendars.get_calendar(code)
 | 
			
		||||
 | 
			
		||||
        if cal.is_open_on_minute(now_local):
 | 
			
		||||
            next_close = cal.next_close(now).tz_convert(here)
 | 
			
		||||
            next_close = next_close.replace(minute=round(next_close.minute, -1))
 | 
			
		||||
            delta_close = timedelta_display(next_close - now_local)
 | 
			
		||||
 | 
			
		||||
            prev_open = cal.previous_open(now).tz_convert(here)
 | 
			
		||||
            prev_open = prev_open.replace(minute=round(prev_open.minute, -1))
 | 
			
		||||
            delta_open = timedelta_display(now_local - prev_open)
 | 
			
		||||
 | 
			
		||||
            msg = (
 | 
			
		||||
                f"{label:>6} market opened {delta_open} ago, "
 | 
			
		||||
                + f"closes in {delta_close} ({next_close:%H:%M})"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            ts = cal.next_open(now)
 | 
			
		||||
            ts = ts.replace(minute=round(ts.minute, -1))
 | 
			
		||||
            ts = ts.tz_convert(here)
 | 
			
		||||
            delta = timedelta_display(ts - now_local)
 | 
			
		||||
            msg = f"{label:>6} market opens in {delta}" + (
 | 
			
		||||
                f" ({ts:%H:%M})" if (ts - now_local) < timedelta(days=1) else ""
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        reply.append(msg)
 | 
			
		||||
    return reply
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_us_holidays(start_date: date, end_date: date) -> list[Event]:
 | 
			
		||||
    """Date and name of next US holiday."""
 | 
			
		||||
    found: list[Event] = []
 | 
			
		||||
| 
						 | 
				
			
			@ -272,7 +218,7 @@ def as_date(d: date | datetime) -> date:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def get_accommodation(filepath: str) -> list[Event]:
 | 
			
		||||
    """Get birthdays from config."""
 | 
			
		||||
    """Get accomodation from config."""
 | 
			
		||||
    with open(filepath) as f:
 | 
			
		||||
        return [
 | 
			
		||||
            Event(
 | 
			
		||||
| 
						 | 
				
			
			@ -343,7 +289,7 @@ async def get_data(now: datetime) -> typing.Mapping[str, str | object]:
 | 
			
		|||
        "bank_holiday": bank_holiday,
 | 
			
		||||
        "us_holiday": get_us_holidays(last_year, next_year),
 | 
			
		||||
        "next_us_presidential_election": next_us_presidential_election,
 | 
			
		||||
        "stock_markets": stock_markets(),
 | 
			
		||||
        "stock_markets": stock_market.open_and_close(),
 | 
			
		||||
        "uk_clock_change": timezone_transition(
 | 
			
		||||
            minus_365, plus_365, "uk_clock_change", "Europe/London"
 | 
			
		||||
        ),
 | 
			
		||||
| 
						 | 
				
			
			@ -381,7 +327,13 @@ async def get_data(now: datetime) -> typing.Mapping[str, str | object]:
 | 
			
		|||
    reply["sunset"] = sun.sunset(observer)
 | 
			
		||||
 | 
			
		||||
    for key, value in xmas_last_posting_dates.items():
 | 
			
		||||
        events.append(Event(name=f"xmas_last_{key}", date=value))
 | 
			
		||||
        events.append(
 | 
			
		||||
            Event(
 | 
			
		||||
                name=f"xmas_last_{key}",
 | 
			
		||||
                date=value,
 | 
			
		||||
                url="https://www.postoffice.co.uk/last-posting-dates",
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    my_data = config["data"]["personal-data"]
 | 
			
		||||
    events += birthday.get_birthdays(last_year, os.path.join(my_data, "entities.yaml"))
 | 
			
		||||
| 
						 | 
				
			
			@ -390,6 +342,8 @@ async def get_data(now: datetime) -> typing.Mapping[str, str | object]:
 | 
			
		|||
    events += conference.get_list(os.path.join(my_data, "conferences.yaml"))
 | 
			
		||||
    events += backwell_bins + bristol_bins
 | 
			
		||||
 | 
			
		||||
    events += subscription.get_events(os.path.join(my_data, "subscriptions.yaml"))
 | 
			
		||||
 | 
			
		||||
    next_up_series = Event(
 | 
			
		||||
        date=date(2026, 6, 1),
 | 
			
		||||
        title="70 Up",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										63
									
								
								agenda/stock_market.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								agenda/stock_market.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
"""Stock market open and close times."""
 | 
			
		||||
 | 
			
		||||
from datetime import timedelta, timezone
 | 
			
		||||
 | 
			
		||||
import dateutil.tz
 | 
			
		||||
import exchange_calendars
 | 
			
		||||
import pandas
 | 
			
		||||
 | 
			
		||||
here = dateutil.tz.tzlocal()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def timedelta_display(delta: timedelta) -> str:
 | 
			
		||||
    """Format timedelta as a human readable string."""
 | 
			
		||||
    total_seconds = int(delta.total_seconds())
 | 
			
		||||
    days, remainder = divmod(total_seconds, 24 * 60 * 60)
 | 
			
		||||
    hours, remainder = divmod(remainder, 60 * 60)
 | 
			
		||||
    mins, secs = divmod(remainder, 60)
 | 
			
		||||
 | 
			
		||||
    return " ".join(
 | 
			
		||||
        f"{v:>3} {label}"
 | 
			
		||||
        for v, label in ((days, "days"), (hours, "hrs"), (mins, "mins"))
 | 
			
		||||
        if v
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def open_and_close() -> list[str]:
 | 
			
		||||
    """Stock markets open and close times."""
 | 
			
		||||
    # The trading calendars code is slow, maybe there is a faster way to do this
 | 
			
		||||
    # Or we could cache the result
 | 
			
		||||
    now = pandas.Timestamp.now(timezone.utc)
 | 
			
		||||
    now_local = pandas.Timestamp.now(here)
 | 
			
		||||
    markets = [
 | 
			
		||||
        ("XLON", "London"),
 | 
			
		||||
        ("XNYS", "US"),
 | 
			
		||||
    ]
 | 
			
		||||
    reply = []
 | 
			
		||||
    for code, label in markets:
 | 
			
		||||
        cal = exchange_calendars.get_calendar(code)
 | 
			
		||||
 | 
			
		||||
        if cal.is_open_on_minute(now_local):
 | 
			
		||||
            next_close = cal.next_close(now).tz_convert(here)
 | 
			
		||||
            next_close = next_close.replace(minute=round(next_close.minute, -1))
 | 
			
		||||
            delta_close = timedelta_display(next_close - now_local)
 | 
			
		||||
 | 
			
		||||
            prev_open = cal.previous_open(now).tz_convert(here)
 | 
			
		||||
            prev_open = prev_open.replace(minute=round(prev_open.minute, -1))
 | 
			
		||||
            delta_open = timedelta_display(now_local - prev_open)
 | 
			
		||||
 | 
			
		||||
            msg = (
 | 
			
		||||
                f"{label:>6} market opened {delta_open} ago, "
 | 
			
		||||
                + f"closes in {delta_close} ({next_close:%H:%M})"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            ts = cal.next_open(now)
 | 
			
		||||
            ts = ts.replace(minute=round(ts.minute, -1))
 | 
			
		||||
            ts = ts.tz_convert(here)
 | 
			
		||||
            delta = timedelta_display(ts - now_local)
 | 
			
		||||
            msg = f"{label:>6} market opens in {delta}" + (
 | 
			
		||||
                f" ({ts:%H:%M})" if (ts - now_local) < timedelta(days=1) else ""
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        reply.append(msg)
 | 
			
		||||
    return reply
 | 
			
		||||
		Loading…
	
		Reference in a new issue