Skip to content

Releases: steynovich/ha-saxo-portfolio

v2.9.0-beta.2: Security, correctness, and dependency hygiene

17 Apr 07:25
v2.9.0-beta.2
4ad9bae

Choose a tag to compare

Continuation of the 2.9.0 line; follow-up to v2.9.0-beta.1. See CHANGELOG for the full entry.

Highlights

Fixes

  • Market-close boundary is now exclusive — 17:00:00 on the dot reads as "After Hours" instead of "Open".
  • Proactive token-refresh math runs in UTC; DST transitions can no longer distort elapsed time on a live refresh token.
  • Unexpected (non-timeout) failures in _fetch_performance_data_safely now log at WARNING so real bugs surface through the graceful-degradation path.

Security

  • SaxoTokenExpirySensor no longer publishes the absolute expires_at ISO timestamp to the HA state machine. expires_in_seconds, is_expired, and needs_refresh remain.
  • Diagnostics REDACT_KEYS expanded to cover all token-lifecycle fields (expires_at*, current_time_iso, token_issued_at, token_type) so downloaded diagnostics don't expose token-rotation timing.

Cleanup

  • Deleted ~300 LoC of unused dataclasses/helpers from models.py (plus ~830 LoC of matching tests). mask_sensitive_data and mask_url_for_logging — the real consumers — are retained.
  • Dropped the saxo-openapi dependency entirely. It hadn't been imported anywhere; end users were installing a 2019-vintage unmaintained package for nothing.

Build / CI

  • requires-python is now >=3.14 (matches the except A, B: tuple syntax the codebase already uses); CI matrix dropped 3.13.
  • uv.lock is now tracked for reproducible contributor environments.
  • Honest dep floors: homeassistant>=2025.1, aiohttp>=3.11, dev-deps bumped toward what uv.lock resolves today.

From the previously-unreleased block (HA Quality Scale Platinum Compliance, strict typing, shared websession, runtime_data migration, entity/icon translations, test-before-configure, exception translations, and the test suite buildout) — see the full CHANGELOG entry for details.

Full Changelog

v2.9.0-beta.1...v2.9.0-beta.2

v2.9.0-beta.1: Proactive Refresh Token Rotation

15 Apr 09:07
v2.9.0-beta.1
0afe6a9

Choose a tag to compare

Added

  • Proactive Refresh Token Rotation: Survives brief Saxo outages without forcing reauthentication (#11)
    • New _proactive_refresh_token() in coordinator.py forces refresh once the refresh token passes 50% of its lifetime, capping worst-case token age
    • For a typical 3600s refresh-token lifetime, the integration can now absorb up to ~30 minutes of Saxo downtime
    • Transient failures (network errors, timeouts, 5xx) during proactive refresh are logged and swallowed; only 400/401 trigger ConfigEntryAuthFailed
    • New REFRESH_TOKEN_REFRESH_AT_FRACTION = 0.5 constant in const.py
  • Extended Token Refresh Retries: Increased from 3 to 5 attempts in application_credentials.py with exponential backoff (1s, 2s, 4s, 8s, 16s) to absorb brief Saxo hiccups

Changed

  • _ensure_token_valid() strategy: Replaced hard client-side refresh-token expiry check with half-life proactive refresh; the preemptive ConfigEntryAuthFailed is removed — Saxo now decides when a token is truly invalid

Pre-release note: This is a beta. Please test and report issues. Once validated, a stable v2.9.0 will follow.

Full changelog: v2.8.0...v2.9.0-beta.1

v2.8.0

20 Mar 14:55
v2.8.0
b9bbfd0

Choose a tag to compare

What's Changed

Added

  • Python 3.14 Support: Full compatibility with Python 3.14 and Home Assistant 2026.3
  • Token Refresh Resilience: Added timeout, retry, and error handling to OAuth token refresh
    • 15-second timeout on token requests (prevents hanging within 60s coordinator timeout)
    • 3-attempt retry with exponential backoff (1s, 2s) for transient failures (5xx, timeouts, network errors)
    • Immediate failure on 400/401 auth errors (no retry — credentials are bad)
    • Specific aiohttp.ClientError handling in coordinator with actionable log messages

Fixed

  • Auth error retry bug: aiohttp.ClientResponseError from 400/401 responses was incorrectly caught by the generic aiohttp.ClientError retry handler; now re-raised immediately

Changed

  • Replaced async_timeout with asyncio.timeout (stdlib since Python 3.11)

Full Changelog: v2.7.1...v2.8.0

v2.8.0-beta.1: Python 3.14 compatibility for HA 2026.3

04 Mar 14:48
v2.8.0-beta.1
995c0ec

Choose a tag to compare

Python 3.14 Compatibility for Home Assistant 2026.3

Added

  • Python 3.14 Support: Full compatibility with Python 3.14 and Home Assistant 2026.3
    • Updated ruff target-version to py314
    • Updated mypy python_version to 3.14
    • Added Python 3.14 classifier to pyproject.toml
    • Added Python 3.14 to CI test matrix
    • Updated quality workflow to Python 3.14

Changed

  • Replaced async_timeout with asyncio.timeout: Removed third-party async_timeout dependency
    • asyncio.timeout is available in stdlib since Python 3.11
    • Updated coordinator.py and api/saxo_client.py
  • Code consistency: Added from __future__ import annotations to const.py
  • Formatting: Applied ruff formatting to diagnostics.py and models.py

Full Changelog: v2.7.1...v2.8.0-beta.1

v2.7.1 - Fix Home Assistant Validation Error

10 Feb 12:29
v2.7.1
63fc558

Choose a tag to compare

🐛 Bug Fix

Fixed Home Assistant Validation Error

This patch release fixes a Home Assistant validation error that occurred with the Accumulated Profit/Loss sensor in v2.7.0.

Issue: Home Assistant displayed the warning:

Entity sensor.saxo_*_accumulated_profit_loss is using state class 'measurement' 
which is impossible considering device class ('monetary') it is using

Root Cause: Home Assistant doesn't allow device_class='monetary' with state_class='measurement'. The monetary device class only supports state_class='total', which requires monotonically increasing values. However, profit/loss can be negative (losses), so we need state_class='measurement' for proper long-term statistics support.

Solution: Removed the device_class='monetary' from SaxoAccumulatedProfitLossSensor. The sensor still:

  • ✅ Displays the currency unit (EUR, USD, etc.)
  • ✅ Includes currency in sensor attributes
  • ✅ Supports long-term statistics with state_class='measurement'
  • ✅ Handles both positive and negative values correctly

Impact

  • No user action required - the fix is automatic upon upgrade
  • Removes the validation warning from Home Assistant logs
  • Maintains all functionality from v2.7.0

Full Changelog: https://github.com/steynovich/ha-saxo-portfolio/blob/main/CHANGELOG.md#271---2026-02-10

v2.7.0 - Long-Term Statistics Support

10 Feb 12:25
v2.7.0
877f133

Choose a tag to compare

🎉 What's New

Long-Term Statistics Support

Performance sensors now support Home Assistant's long-term statistics system, enabling historical tracking and trend analysis beyond the default 10-day retention period.

Key Benefits:

  • 📊 Historical data retention beyond default 10-day recorder purge
  • 📈 Trend visualization in History and Energy panels
  • 📉 Statistical analysis with min, max, and mean values
  • ✅ Support for both positive and negative performance values (gains and losses)

Affected Sensors:

  • Investment Performance (all-time)
  • YTD Investment Performance
  • Month Investment Performance
  • Quarter Investment Performance
  • Accumulated Profit/Loss

Technical Details

  • Changed state_class from "total" to "measurement" for all performance sensors
  • Added suggested_display_precision=2 for consistent display formatting
  • Updated SaxoPerformanceSensorBase and SaxoAccumulatedProfitLossSensor classes
  • No breaking changes - existing functionality preserved

Documentation

  • Updated README.md with long-term statistics information
  • Enhanced CLAUDE.md development guidelines
  • Comprehensive CHANGELOG entry with technical details

Full Changelog: https://github.com/steynovich/ha-saxo-portfolio/blob/main/CHANGELOG.md#270---2026-02-10

Closes: #10

v2.6.0

03 Feb 07:46
v2.6.0
a5b96b1

Choose a tag to compare

What's New

Position Sensors (Opt-in)

  • Individual Position Sensors: One sensor per portfolio position from /port/v1/netpositions/me endpoint
    • Current price as sensor state with long-term statistics support (state_class: measurement)
    • Rich attributes: symbol, description, asset_type, amount, market_value, profit_loss, uic, currency
    • Entity ID pattern: sensor.saxo_{client_id}_position_{symbol}_{asset_type}
    • Dynamic sensor creation when new positions open
    • Sensors become unavailable when positions are closed
    • Opt-in via integration options (default: disabled)

Market Data Access Detection

  • Market Data Access Sensor: Diagnostic sensor showing real-time market data availability
    • State: "Available", "Unavailable", or "Unknown"
    • Automatic detection of API market data permissions
    • Calculated prices from P/L data when real-time prices not available

Options Flow

  • New configuration option to enable/disable position sensors
  • Integration reloads automatically when option changes
  • Translations for all 11 supported languages

Full Changelog: https://github.com/steynovich/ha-saxo-portfolio/blob/main/CHANGELOG.md

v2.6.0-beta.1: Position Sensors

02 Feb 13:04
v2.6.0-beta.1
00c565b

Choose a tag to compare

Pre-release

Beta Release: Position Sensors Feature

This prerelease adds optional sensors for individual portfolio positions.

New Features

  • Position Sensors: One sensor per portfolio position with current price as state

    • Rich attributes: symbol, description, asset_type, amount, market_value, profit_loss, uic, currency
    • Entity ID pattern: sensor.saxo_{client_id}_position_{symbol}_{asset_type}
    • Dynamic creation when positions open, unavailable when closed
    • Long-term statistics support
  • Market Data Access Sensor: Diagnostic sensor showing real-time market data availability

    • State: "Available", "Unavailable", or "Unknown"
    • Calculated prices from P/L data when real-time prices unavailable
  • Options Flow: Enable/disable position sensors in integration options (default: disabled)

Installation (HACS)

  1. Go to HACS → Integrations → Saxo Portfolio
  2. Click three-dot menu → Redownload
  3. Check "Show beta versions"
  4. Select v2.6.0-beta.1

Testing Needed

  • Enable position sensors in options
  • Verify sensors created for each open position
  • Check state shows current price, attributes show details
  • Open new position → verify new sensor appears
  • Close position → verify sensor becomes unavailable

Closes #8

v2.5.1 - Token Refresh Resilience

10 Jan 13:29
v2.5.1
8117a1d

Choose a tag to compare

Added

  • Token Refresh Retry Logic: Improved resilience for OAuth token refresh

    • Automatic retry with exponential backoff for transient failures (5xx, network errors, timeouts)
    • Up to 3 retry attempts before failing
    • Permanent auth failures (401/403) fail immediately without retry
  • Enhanced Token Status Logging: New _log_refresh_token_status() method

    • Shows clear remaining time for both access and refresh tokens
    • Warns when refresh token is about to expire (< 5 minutes)
    • Uses timezone-aware datetime for accurate calculations
  • HTML Error Message Extraction: New _extract_error_from_html() method

    • Parses HTML error pages into readable messages
    • Cleaner error logs instead of raw HTML dumps

Fixed

  • Missing DIAGNOSTICS_REDACTED Constant: Added missing constant to const.py
  • Diagnostics Sensor Count: Updated from 6 to 16 sensors

Technical Details

  • New helper methods: _extract_error_from_html(), _log_refresh_token_status()
  • Token refresh distinguishes between permanent (401/403) and transient (5xx) failures
  • Uses MAX_RETRIES and RETRY_BACKOFF_FACTOR constants

Full Changelog: v2.5.0...v2.5.1

v2.5.0 - Graceful Degradation for Performance API

04 Jan 16:33
v2.5.0
cafe47e

Choose a tag to compare

What's Changed

Added

  • Graceful Degradation for Data Fetching: Performance API failures no longer block balance data
    • Balance sensors now work even when the performance API is slow or unresponsive
    • New _fetch_performance_data_safely() method with dedicated 30-second timeout
    • Performance data failures return cached/default values instead of failing the entire update
    • Cached performance values are kept indefinitely until API recovers

Changed

  • Separate Timeout Handling: Performance data now has its own timeout context
    • Balance fetch (required): Uses coordinator timeout, must succeed
    • Performance fetch (optional): Uses separate 30s timeout, fails gracefully
    • Added PERFORMANCE_FETCH_TIMEOUT = 30 constant

Why This Matters

  • Previously: If performance API took >30s, entire 60s coordinator timeout was exceeded, ALL sensors became unavailable
  • Now: Balance sensors (cash, total value, positions) work independently of performance API health
  • Users see their balance data immediately while performance data may show cached values during API issues

Technical Details

  • Refactored _fetch_portfolio_data() to implement two-phase data fetching
  • New method handles all performance/client detail fetching with graceful error handling
  • Cache is never expired - shows last known values until API recovers

Full Changelog: v2.4.1...v2.5.0