diff --git a/__pycache__/saxo_auth.cpython-314.pyc b/__pycache__/saxo_auth.cpython-314.pyc index 132e84d..987b606 100644 Binary files a/__pycache__/saxo_auth.cpython-314.pyc and b/__pycache__/saxo_auth.cpython-314.pyc differ diff --git a/logs/dashboard.log b/logs/dashboard.log index 6de8f93..363227f 100644 --- a/logs/dashboard.log +++ b/logs/dashboard.log @@ -1,13 +1,13 @@ -nohup: ignorerer inddata +Traceback (most recent call last): + File "/home/hjess/Projects/MoneyMaker/dashboard.py", line 779, in + main() + ~~~~^^ + File "/home/hjess/Projects/MoneyMaker/dashboard.py", line 773, in main + init_schema() + ~~~~~~~~~~~^^ + File "/home/hjess/Projects/MoneyMaker/db.py", line 375, in init_schema + conn = psycopg2.connect(DATABASE_URL) + File "/home/hjess/Projects/MoneyMaker/.venv/lib64/python3.14/site-packages/psycopg2/__init__.py", line 122, in connect + conn = _connect(dsn, connection_factory=connection_factory, **kwasync) +psycopg2.OperationalError: could not translate host name "int.i80.dk" to address: Temporary failure in name resolution - MoneyMaker Dashboard -> http://localhost:5001 - - * Serving Flask app 'dashboard' - * Debug mode: off -WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5001 - * Running on http://192.168.15.40:5001 -Press CTRL+C to quit -127.0.0.1 - - [26/May/2026 22:52:33] "GET /health HTTP/1.1" 200 - -127.0.0.1 - - [26/May/2026 22:52:39] "GET / HTTP/1.1" 200 - diff --git a/mmd.nomad b/mmd.nomad index a879a44..af34a01 100644 --- a/mmd.nomad +++ b/mmd.nomad @@ -85,6 +85,7 @@ ANTHROPIC_API_KEY="{{ key "mmd/anthropic_api_key" }}" SAXO_APP_KEY="{{ key "mmd/SAXO_APP_KEY" }}" SAXO_APP_SECRET_1="{{ key "mmd/SAXO_APP_SECRET_1" }}" SAXO_BASE="{{ key "mmd/SAXO_BASE" }}" +SAXO_TOKEN="{{ key "mmd/SAXO_APP_24_HOUR" }}" HARBOR_ROBOT_TOKEN="{{ key "harbor/robot/token" }}" EOH destination = "secrets/app.env" @@ -132,6 +133,7 @@ ANTHROPIC_API_KEY="{{ key "mmd/anthropic_api_key" }}" SAXO_APP_KEY="{{ key "mmd/SAXO_APP_KEY" }}" SAXO_APP_SECRET_1="{{ key "mmd/SAXO_APP_SECRET_1" }}" SAXO_BASE="{{ key "mmd/SAXO_BASE" }}" +SAXO_TOKEN="{{ key "mmd/SAXO_APP_24_HOUR" }}" EOH destination = "secrets/worker.env" env = true diff --git a/scheduler.py b/scheduler.py index 6e5744f..699c69b 100644 --- a/scheduler.py +++ b/scheduler.py @@ -32,6 +32,23 @@ SCHEDULE = [ (17, 0, frozenset(range(5)), False, True), ] +# Saxo's access token lasts ~20 min and its refresh token rolls on each refresh +# but lapses if unused for ~1h. Trade windows are 2-3h apart, so we roll the +# token forward here independently to keep the refresh chain alive between runs. +SAXO_KEEPALIVE_SEC = 300 + + +def _saxo_keepalive() -> None: + """Roll the Saxo OAuth token forward so its refresh token never lapses in the + gaps between scheduled runs. No-op when there's no stored OAuth token yet + (e.g. only the 24h env token is present).""" + try: + from saxo_auth import _load, get_token + if _load().get("refresh_token"): + get_token() # auto-refreshes when near expiry; rolls the refresh token + except Exception as exc: + print(f"[scheduler] saxo keep-alive: {exc}", flush=True) + def _log_path() -> Path: today = datetime.now(timezone.utc).strftime("%Y-%m-%d") @@ -60,10 +77,17 @@ def _run_job(analyze_only: bool, is_report: bool) -> None: def main() -> None: last_run: dict = {} + last_keepalive = 0.0 print(f"[scheduler] started — LOG_DIR={LOG_DIR}", flush=True) while True: now = datetime.now(timezone.utc) + + # Keep the Saxo refresh-token chain alive between trade windows. + if time.time() - last_keepalive >= SAXO_KEEPALIVE_SEC: + last_keepalive = time.time() + _saxo_keepalive() + dow = now.weekday() for hour, minute, days, analyze_only, is_report in SCHEDULE: