Releases: steynovich/ha-saxo-portfolio
v2.9.0-beta.2: Security, correctness, and dependency hygiene
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:00on 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_safelynow log at WARNING so real bugs surface through the graceful-degradation path.
Security
SaxoTokenExpirySensorno longer publishes the absoluteexpires_atISO timestamp to the HA state machine.expires_in_seconds,is_expired, andneeds_refreshremain.- Diagnostics
REDACT_KEYSexpanded 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_dataandmask_url_for_logging— the real consumers — are retained. - Dropped the
saxo-openapidependency entirely. It hadn't been imported anywhere; end users were installing a 2019-vintage unmaintained package for nothing.
Build / CI
requires-pythonis now>=3.14(matches theexcept A, B:tuple syntax the codebase already uses); CI matrix dropped 3.13.uv.lockis now tracked for reproducible contributor environments.- Honest dep floors:
homeassistant>=2025.1,aiohttp>=3.11, dev-deps bumped toward whatuv.lockresolves 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: Proactive Refresh Token Rotation
Added
- Proactive Refresh Token Rotation: Survives brief Saxo outages without forcing reauthentication (#11)
- New
_proactive_refresh_token()incoordinator.pyforces 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.5constant inconst.py
- New
- Extended Token Refresh Retries: Increased from 3 to 5 attempts in
application_credentials.pywith 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 preemptiveConfigEntryAuthFailedis 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
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.ClientErrorhandling in coordinator with actionable log messages
Fixed
- Auth error retry bug:
aiohttp.ClientResponseErrorfrom 400/401 responses was incorrectly caught by the genericaiohttp.ClientErrorretry handler; now re-raised immediately
Changed
- Replaced
async_timeoutwithasyncio.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
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
- Updated ruff target-version to
Changed
- Replaced
async_timeoutwithasyncio.timeout: Removed third-partyasync_timeoutdependencyasyncio.timeoutis available in stdlib since Python 3.11- Updated
coordinator.pyandapi/saxo_client.py
- Code consistency: Added
from __future__ import annotationstoconst.py - Formatting: Applied ruff formatting to
diagnostics.pyandmodels.py
Full Changelog: v2.7.1...v2.8.0-beta.1
v2.7.1 - Fix Home Assistant Validation Error
🐛 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
🎉 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_classfrom"total"to"measurement"for all performance sensors - Added
suggested_display_precision=2for consistent display formatting - Updated
SaxoPerformanceSensorBaseandSaxoAccumulatedProfitLossSensorclasses - 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
What's New
Position Sensors (Opt-in)
- Individual Position Sensors: One sensor per portfolio position from
/port/v1/netpositions/meendpoint- 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)
- Current price as sensor state with long-term statistics support (
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
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)
- Go to HACS → Integrations → Saxo Portfolio
- Click three-dot menu → Redownload
- Check "Show beta versions"
- 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
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_RETRIESandRETRY_BACKOFF_FACTORconstants
Full Changelog: v2.5.0...v2.5.1
v2.5.0 - Graceful Degradation for Performance API
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 = 30constant
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