All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- HA Quality Scale Platinum Compliance: Full compliance with all 52 Home Assistant Quality Scale rules across Bronze, Silver, Gold, and Platinum tiers
- Strict Typing (
strict-typing): Enabledmypy --strictwith zero errors across all source files; addedpy.typedPEP 561 marker - HA Shared Websession (
inject-websession):SaxoApiClientnow uses HA'sasync_get_clientsession()instead of managing its ownaiohttp.ClientSession; auth headers sent per-request - Runtime Data (
runtime-data): Migrated fromhass.data[DOMAIN]to typedentry.runtime_data = SaxoRuntimeData(coordinator)pattern - Entity Translations (
has-entity-name,entity-translations): All entities use_attr_has_entity_name = Truewith_attr_translation_key; fullstrings.jsonand 11 translation files - Icon Translations (
icon-translations): Newicons.jsonmapping all entity icons; removed hardcoded_attr_iconfrom Python code - Test-Before-Configure (
test-before-configure,unique-config-entry): Config flow validates API credentials and setsunique_idfromClientKeybefore creating entry - Entity Disabled by Default (
entity-disabled-by-default):SaxoClientIDSensor,SaxoAccountIDSensor,SaxoNameSensordisabled by default - Action Exceptions (
action-exceptions): Service handler and refresh button wrap errors inHomeAssistantErrorwith translation keys - Exception Translations (
exception-translations): Addedexceptionssection tostrings.jsonfor translated error messages - Parallel Updates (
parallel-updates): AddedPARALLEL_UPDATES = 0tosensor.pyandbutton.py - Comprehensive Test Suite: 634 tests after the post-beta.1 cleanup (was 696 before dead-code removal); still 99% coverage
- Exclusive market-close boundary (
coordinator.py): Market hours check now usesmarket_open <= current_time < market_closeso 17:00:00 on the dot reads as "After Hours" instead of "Open" - DST-safe token-age math (
coordinator.py): Proactive refresh math now runs in UTC (dt_util.utcnow()+ UTC-awaredatetime.fromtimestamp(..., tz=dt_util.UTC)); previously naive local datetimes could cancel incorrectly across DST transitions - Visible perf-fetch failures (
coordinator.py): Non-timeout exceptions inside_fetch_performance_data_safelynow log at WARNING instead of DEBUG so real bugs surface; graceful-degradation behaviour unchanged
- Diagnostics redaction (
diagnostics.py):REDACT_KEYSexpanded to coverexpires_at,expires_at_timestamp,expires_at_iso,current_time_iso,token_issued_at, andtoken_typeso downloaded diagnostics no longer expose exact token-rotation timing - Token-expiry sensor attributes (
sensor.py):SaxoTokenExpirySensor.extra_state_attributesno longer publishes the absoluteexpires_atISO timestamp to the HA state machine;expires_in_seconds,is_expired, andneeds_refreshremain
- API Client Architecture: Removed
sessionproperty (auto-creating sessions),close(),__aenter__/__aexit__fromSaxoApiClient— HA owns the HTTP session lifecycle - Coordinator Simplified: Removed
_close_old_client()and session close logic; token rotation is now a lightweight client wrapper swap - Config Flow Return Types: Changed
FlowResulttoConfigFlowResultfor mypy compatibility - Documentation: Updated README with Platinum badge, uninstallation instructions, use cases, automation examples, known limitations, and data update strategy
- CLAUDE.md: Condensed to ~42 lines of non-obvious architecture (runtime_data, market-hours cadence, sticky availability, rate limits); removed tree/command duplication and stale claims
DATA_COORDINATORandDATA_UNSUBconstants (replaced byentry.runtime_data)API_TIMEOUT_CONNECTandAPI_TIMEOUT_READimports fromsaxo_client.py(HA session manages timeouts)- Unused
models.pydataclasses/helpers:PortfolioData,AccountData,CoordinatorData, the duplicatePositionData,from_api_responses,validate_iso_currency_code,sanitize_financial_value,calculate_portfolio_totals— no production callers (the coordinator rolls its own data shapes). ~300 prod LoC + ~830 test LoC removed;mask_sensitive_dataandmask_url_for_logging(the actual consumers) remain saxo-openapidependency: Dropped frommanifest.jsonandpyproject.toml. The package was never imported; end users were installing a 2019-vintage unmaintained package for nothing
requires-python→>=3.14: Matches theexcept A, B:tuple syntax the codebase relies on (a 3.14-only feature);ruff'starget-versionandmypy'spython_versionwere already3.14, so the old>=3.13floor would have crashed at install- CI matrix (
tests.yml): Dropped Python 3.13 from the matrix (the package no longer installs on 3.13) uv.locknow tracked: Reproducible dev environments for contributors; end users still install via HACS and don't see this file- Honest dep floors:
homeassistant>=2024.1→>=2025.1;aiohttp>=3.8→>=3.11; dev floors for pytest, ruff, mypy, pre-commit, pytest-asyncio, pytest-homeassistant-custom-component all brought closer to what actually ships inuv.lock
- 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
_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
- 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
- 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 - New
TOKEN_REFRESH_TIMEOUTconstant inconst.py
- Auth error retry bug:
aiohttp.ClientResponseErrorfrom 400/401 responses was incorrectly caught by the genericaiohttp.ClientErrorretry handler; now re-raised immediately
- 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
2.7.1 - 2026-02-10
- Home Assistant Validation Error: Removed
device_class='monetary'fromSaxoAccumulatedProfitLossSensor- Home Assistant doesn't allow
device_class='monetary'withstate_class='measurement' - Profit/loss can be negative, requiring
state_class='measurement'for long-term statistics - Sensor still displays currency unit and includes currency in attributes
- Resolves warning: "Entity is using state class 'measurement' which is impossible considering device class ('monetary')"
- Home Assistant doesn't allow
2.7.0 - 2026-02-10
- Long-Term Statistics Support: Performance sensors now support Home Assistant's long-term statistics system (#10)
- Changed
state_classfrom"total"to"measurement"for all performance sensors - Enables historical tracking beyond the default 10-day recorder retention period
- Supports trend visualization in History/Energy panels and statistical analysis
- Compatible with negative performance values (losses)
- Affects all 5 performance sensors:
SaxoInvestmentPerformanceSensor(all-time)SaxoYTDInvestmentPerformanceSensorSaxoMonthInvestmentPerformanceSensorSaxoQuarterInvestmentPerformanceSensorSaxoAccumulatedProfitLossSensor
- Changed
- Performance Sensor Configuration: All performance sensors now use
state_class="measurement"withsuggested_display_precision=2 - SaxoPerformanceSensorBase: Added default
state_classand display precision configuration - SaxoAccumulatedProfitLossSensor: Changed from
state_class="total"tostate_class="measurement"
- Updated
SaxoPerformanceSensorBase.__init__()to setstate_class="measurement"andsuggested_display_precision=2 - Updated
SaxoAccumulatedProfitLossSensor.__init__()to usestate_class="measurement" - Home Assistant
state_class="measurement"allows both positive and negative values, suitable for investment performance tracking - No breaking changes - existing functionality preserved, statistics tracking now enabled
2.6.0 - 2026-02-03
-
Position Sensors: Optional sensors for individual portfolio positions (#8)
- One sensor per 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)
- One sensor per position from
-
Position Data Infrastructure:
- New
PositionDatadataclass with slug generation for URL-safe entity IDs - New
PositionsCachefor efficient position data caching - New
get_net_positions()API method with graceful error handling - Position listener for dynamic sensor creation on coordinator updates
- New
-
Market Data Access Detection:
- New
SaxoMarketDataAccessSensordiagnostic sensor (only created when position sensors enabled)- Display name: "Real-Time Market Data Access"
- State: "Available", "Unavailable", or "Unknown"
- Attributes:
has_real_time_prices,note(guidance when unavailable) - Entity ID:
sensor.saxo_{client_id}_market_data_access
- Automatic detection of API market data permissions
- Warning logged once per session if market data access unavailable
- Calculated prices from P/L data when real-time prices not available
- Real-time market data may require a separate Saxo market data subscription
- New
-
Options Flow: New configuration option to enable/disable position sensors
- Integration reloads automatically when option changes
- Translations for all 11 supported languages
- New constants:
API_NET_POSITIONS_ENDPOINT,CONF_ENABLE_POSITION_SENSORS,DEFAULT_ENABLE_POSITION_SENSORS - New coordinator methods:
_fetch_positions_data_safely(),get_positions(),get_position(),get_position_ids(),has_market_data_access() - New coordinator flag:
_has_market_data_accessfor tracking API market data access status - New sensor classes:
SaxoPositionSensor,SaxoMarketDataAccessSensorextendingSaxoSensorBase/SaxoDiagnosticSensorBase - New helper:
_setup_position_listener()for dynamic entity creation - Comprehensive test coverage: unit, integration, and contract tests
-
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)
- Logs token status before and after refresh attempts
- Uses timezone-aware datetime (dt_util) for accurate calculations
-
HTML Error Message Extraction: New
_extract_error_from_html()method- Parses HTML error pages into readable messages
- Extracts title or h1 content from Saxo error pages
- Cleaner error logs instead of raw HTML dumps
-
Missing DIAGNOSTICS_REDACTED Constant: Added missing constant to const.py
- Was causing ImportError when logging URLs with query parameters
-
Diagnostics Sensor Count: Updated from 6 to 16 sensors
- Added all diagnostic sensor types to the list
- New helper methods in coordinator.py:
_extract_error_from_html(),_log_refresh_token_status() - Token refresh now distinguishes between permanent (401/403) and transient (5xx) failures
- Uses
MAX_RETRIESandRETRY_BACKOFF_FACTORconstants from const.py
- 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
- 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 inconst.py
- Refactored
_fetch_portfolio_data()to implement two-phase data fetching:- Phase 1: Fetch balance data (required) - if this fails, update fails
- Phase 2: Fetch performance data (optional) - wrapped in try/except with 30s timeout
- New method
_fetch_performance_data_safely()handles all performance and client detail fetching
- 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
- Critical: Fixed integration setup timeout causing "Setup cancelled" errors
- Staggered update offset was being applied during initial setup, adding 0-30 seconds of delay
- Combined with slow/unresponsive Saxo performance API, this exceeded Home Assistant's 60-second setup timeout
- Fix: Skip the staggered offset during initial setup (when
_last_successful_updateis None) - Offset now only applies on subsequent scheduled updates where it prevents rate limiting
- Symptom: Integration showed "Error" and failed to setup after reauthentication or restart
- Timeline in logs:
- 16.5s staggered offset delay applied during setup
- Balance/client details fetch (~0.6s) - successful
- Performance API timeout (~30s+) - slow/unresponsive
- Total time >60s → Home Assistant cancelled setup
- Solution: Check
_last_successful_update is not Nonebefore applying staggered offset- During initial setup:
_last_successful_updateis None → skip offset → data fetch starts immediately - On subsequent updates:
_last_successful_updateis set → apply offset to prevent rate limiting
- During initial setup:
- Modified condition in
coordinator.py:628fromif self._initial_update_offset > 0to includeand self._last_successful_update is not None - The staggered offset design prevents multiple accounts from hitting the API simultaneously
- During initial setup, accounts are set up one at a time anyway, so the offset provided no benefit
- This fix eliminates the unnecessary delay during setup while preserving rate limiting protection for regular updates
-
Manual Refresh Button: Added a refresh button entity to each device
- Entity ID:
button.saxo_{client_id}_refresh - Appears on device page under configuration entities
- Press to trigger immediate data refresh from Saxo Bank API
- Can be added to dashboards or used in automations
- Translations available in all 11 supported languages
- Entity ID:
-
Refresh Data Service: Added
saxo_portfolio.refresh_dataservice- Call from Developer Tools → Services or automations
- Refreshes all registered Saxo Portfolio accounts
- Bypasses normal update schedule for immediate data fetch
- New
button.pyplatform withSaxoRefreshButtonentity - Service registered once per domain in
__init__.py - Service automatically removed when last integration entry is unloaded
- Button uses
ButtonDeviceClass.UPDATEandEntityCategory.CONFIG
- Reconfigure Dialog: Fixed empty popup when clicking Reconfigure button
- Simplified reconfigure flow to use single step with proper HA method
_get_reconfigure_entry() - Changed step ID from
reconfigure_confirmtoreconfigureto match HA conventions - Dialog now properly displays title and description
- Simplified reconfigure flow to use single step with proper HA method
- Multi-language Support: Added translations for 11 languages
- English (en)
- German (de)
- French (fr)
- Spanish (es)
- Dutch (nl)
- Italian (it)
- Portuguese (pt)
- Danish (da)
- Swedish (sv)
- Norwegian Bokmål (nb)
- Finnish (fi)
- Reconfigure Dialog Empty: Fixed empty popup when clicking Reconfigure button
- Added missing
translations/en.jsonfile required by Home Assistant for runtime string loading - Reconfigure dialog now displays title and description correctly
- Added missing
- Manual Reauthentication Button: Users can now proactively reauthenticate via the "Reconfigure" menu option
- Go to Settings → Devices & Services → Saxo Portfolio → three-dot menu → Reconfigure
- Useful for refreshing tokens before they expire
- Resolves authentication issues without removing the integration
- All settings, entity history, and automations are preserved
- Added
async_step_reconfigure()andasync_step_reconfigure_confirm()in config_flow.py:268-302 - Added
reconfigure_confirmstep strings in strings.json
- Multi-Account Identification: Config entry titles now include the Client ID for clear account identification
- Titles automatically update from "Saxo Portfolio" to "Saxo Portfolio (CLIENT_ID)" after first successful data fetch
- Makes it easy to identify which account each integration represents in Settings → Devices & Services
- Improved Reauthentication Dialog: Updated reauth UI to display the account identifier
- Dialog title now shows "Reauthenticate Saxo Portfolio (CLIENT_ID)"
- Description clearly states which account needs reauthentication
- Essential for users with multiple Saxo accounts configured
- Added
_update_config_entry_title_if_needed()method in coordinator.py:1359-1387 - Method updates config entry title only when:
- Client ID is successfully fetched (not "unknown")
- Title is still generic ("Saxo Portfolio") or doesn't contain Client ID
- Updated strings.json reauth_confirm step to use
{title}placeholder
- Critical: Fixed reauthentication button not appearing when tokens expire
- Added explicit
ConfigEntryAuthFailedexception handler in coordinator.py:1058-1064 - Previously,
ConfigEntryAuthFailedwas caught by genericexcept Exceptionhandler and converted toUpdateFailed UpdateFailedonly makes sensors unavailable without triggering reauth flow- Now
ConfigEntryAuthFailedproperly propagates to Home Assistant core to trigger reauth UI
- Added explicit
- Added dedicated exception handler for
ConfigEntryAuthFailedbefore genericExceptionhandler - Handler re-raises the exception to allow Home Assistant to display reauthentication prompt
- Added informative log message when authentication failure triggers reauth flow
- Root Cause: Exception handling order in
_fetch_portfolio_data()method- Token refresh failures raise
ConfigEntryAuthFailedat line 635 - Generic
except Exceptionhandler at line 1066 caught it and converted toUpdateFailed - This prevented Home Assistant from detecting auth failure and showing reauth button
- Token refresh failures raise
- Solution: Added specific handler at line 1058 that re-raises
ConfigEntryAuthFailedunchanged - Flow: Token expires →
_check_and_refresh_token()raisesConfigEntryAuthFailed→ new handler re-raises → Home Assistant shows reauth button
- Before: Tokens expire → sensors unavailable → no UI prompt → users confused
- After: Tokens expire → sensors unavailable → reauthentication button appears in UI → users can easily reauth
- After upgrading to 2.3.5, wait for tokens to expire (or force expiry)
- Check Settings → Devices & Services → Saxo Portfolio
- You should now see the reauthentication prompt/button appear
- Click "Configure" or "Reauthenticate" to restore access
- Critical: Fixed missing reauthentication UI in Home Assistant
- Added
reauth_confirmstep to strings.json for proper UI display - Added
async_step_reauth_confirm()to show confirmation dialog before OAuth flow - Users now see a clear "Reauthenticate Saxo Portfolio" dialog when tokens expire
- Dialog explains that settings and history will be preserved during reauth
- Added
- Enhanced
async_step_reauth()to show confirmation form first (config_flow.py:247-248) - Added
async_step_reauth_confirm()method to handle user confirmation (config_flow.py:250-264) - Updated strings.json with reauth_confirm step configuration (lines 22-25)
- Before: ConfigEntryAuthFailed raised but no UI appeared → users confused
- After: Clear dialog appears with "Submit" button → starts OAuth flow → seamless reauth
- Dialog message: "Your Saxo Bank authentication has expired. Please sign in again to continue using the integration. All your settings, entity history, and automations will be preserved."
- Root Cause: Missing reauth_confirm step in UI configuration prevented Home Assistant from displaying the reauthentication prompt
- Solution: Added proper reauth confirmation flow following Home Assistant OAuth2 best practices
- Flow: ConfigEntryAuthFailed → async_step_reauth → async_step_reauth_confirm (show form) → user clicks Submit → async_step_pick_implementation → OAuth flow
- Wait for tokens to expire OR restart HA after this upgrade
- Look in Settings → Devices & Services
- You should now see one of:
- Yellow banner: "Authentication required for Saxo Portfolio"
- Integration card with warning badge
- Notification icon (🔔) with auth failure message
- Click "Configure" or "Reauthenticate"
- You'll see the new confirmation dialog
- Click "Submit" to start OAuth flow
- Critical: Fixed refresh token expiration by implementing proactive refresh token rotation
- Integration now checks refresh token expiry INDEPENDENTLY of access token expiry
- Proactively refreshes access token when refresh token has less than 5 minutes remaining
- Ensures we get a new refresh token before the old one expires (if Saxo supports refresh token rotation)
- Prevents "Refresh token has expired" errors when HA is running continuously
- Solves the core issue: access token expires in 20min, refresh token expires in 5min → now we refresh at 5min mark
- Added
REFRESH_TOKEN_BUFFERconstant (5 minutes) in const.py:150-152 - Enhanced
_check_and_refresh_token()to check refresh token expiry first (coordinator.py:278-371) - Implemented two-step token validation:
- STEP 1: Check if refresh token will expire soon (proactive) - Lines 299-352
- STEP 2: Check if access token needs refresh (normal) - Lines 354-371
- Added detailed logging for refresh token expiry monitoring
- Root Cause: Old logic only checked refresh token when access token needed refresh, missing cases where refresh token expires before access token
- Example Scenario:
- Access token expires in 20 minutes
- Refresh token expires in 5 minutes
- Old behavior: Wait 20 minutes → refresh token already expired ❌
- New behavior: Check at 5 minutes → proactive refresh → get new refresh token ✅
- Refresh Token Rotation: If Saxo Bank provides a new refresh_token in the token refresh response, we now ensure we refresh often enough to keep getting new ones
- Compatibility: Works alongside v2.3.2's accurate timestamp tracking
- This does NOT eliminate the need for reauthentication - When HA is shut down longer than refresh token lifetime, manual reauth is still required (by design)
- This DOES prevent refresh token expiry during normal HA operation by refreshing proactively
- Requires Saxo support: If Saxo Bank does NOT provide new refresh tokens during token refresh, manual reauth will eventually be needed
- Critical: Fixed refresh token expiry calculation after Home Assistant shutdown
- Integration now stores
token_issued_attimestamp during initial OAuth and token refresh - Refresh token expiry is now calculated using actual issuance time instead of inferred time
- Prevents incorrect expiry calculations after the first token refresh
- Fixes issue where HA shutdown for hours could cause premature or missed refresh token expiry detection
- Includes backward compatibility fallback for existing installations without stored timestamp
- Integration now stores
- Enhanced token refresh logic to store accurate token issuance timestamp (coordinator.py:498-510)
- Updated refresh token expiry validation to use stored timestamp (coordinator.py:303-316)
- Added
token_issued_attimestamp storage during initial OAuth flow (config_flow.py:114-119)
- Root Cause: After token refresh, the integration calculated
token_issued_at = expires_at - expires_in, but this used the NEW access token expiry with the OLD refresh token lifetime, causing incorrect calculations - Solution: Store actual
token_issued_attimestamp whenever tokens are obtained or refreshed - Impact: Prevents unnecessary reauthentication requests and ensures refresh tokens work correctly after long HA downtime
- Compatibility: Existing installations automatically benefit after next token refresh
- Improved User Messaging: Updated error messages to guide users to the reauthentication button
- Changed "Delete and re-add the integration" message to "click the Reauthenticate button"
- Reduced log level from ERROR to INFO for user guidance message
- Makes it clearer that users don't need to delete the integration when tokens expire
- Improves user experience by directing to the correct GUI-based reauth flow
- Updated expired token error message to properly guide users to the reauthentication button in Settings > Devices & Services
- Prevents user confusion about needing to delete and re-add the integration
- GUI-Based Reauthentication: Seamless reauthentication flow when OAuth tokens expire
- Users can now reauthenticate directly from the Home Assistant UI
- Home Assistant automatically displays a "Reauthenticate" button when tokens expire or become invalid
- No need to delete and re-add the integration when tokens expire
- All configuration settings (timezone, entity customizations, etc.) are preserved
- All entity history and statistics are maintained
- Only OAuth tokens are updated during reauthentication
- Enhanced
async_step_reauth()in config_flow.py to properly initiate OAuth flow for reauthentication - Updated
async_oauth_create_entry()in config_flow.py to detect reauth flows and update existing config entry - Added
_reauth_entrytracking to flow handler to preserve config entry during reauth - Added user-facing success message for completed reauthentication
- Reauth flow triggered automatically when coordinator raises
ConfigEntryAuthFailed - Config entry is updated in place rather than creating a new entry
- Integration reloads after successful reauth to apply new tokens
- Preserves all existing data including timezone configuration and redirect_uri
- Follows Home Assistant OAuth2 reauth best practices
- Significantly improved user experience: No more deleting and re-adding the integration
- No data loss: All historical data and statistics are preserved
- Automatic detection: System detects expired tokens and prompts for reauth
- One-click solution: Simple button click starts the reauth process
- Critical: Fixed integration not detecting expired refresh tokens
- Saxo refresh tokens have a limited lifetime (typically 1 hour)
- Integration now checks if refresh token is expired before attempting refresh
- Triggers automatic reauth flow when refresh token has expired
- Prevents 401 errors from trying to use expired refresh tokens
- Provides clear error messages explaining the issue
- Enhanced
_check_and_refresh_token()to validate refresh token expiry (coordinator.py:299-329)- Calculates refresh token expiration time based on
refresh_token_expires_in - Logs refresh token expiration information for debugging
- Raises ConfigEntryAuthFailed with helpful message when refresh token expired
- Home Assistant will automatically prompt for reauth when this occurs
- Calculates refresh token expiration time based on
- Refresh token expiry calculation:
token_issued_at + refresh_token_expires_in - Prevents wasted API calls with expired credentials
- Clearer user experience with automatic reauth prompts
- INFO-level logging shows refresh token expiration time
- Enhanced Diagnostics: Added comprehensive logging for OAuth token refresh debugging
- INFO-level logging now shows client_id and redirect_uri being used for token refresh
- Logs source of redirect_uri (OAuth implementation vs config entry vs none)
- Added helpful error messages for 401 errors with troubleshooting steps
- Logs masked client_id (first 8 characters) to help identify configuration issues
- Provides clear guidance on potential causes: redirect_uri mismatch, invalid credentials, reconfiguration needed
- Enhanced
_refresh_oauth_token()method with comprehensive diagnostic logging - All key OAuth refresh parameters now logged at INFO level (no debug logging needed)
- 401 errors now include specific troubleshooting suggestions
- Helps diagnose redirect_uri mismatches and credential issues
- Critical: Fixed OAuth token refresh 401 Unauthorized errors (partial fix)
- Token refresh was using hardcoded redirect_uri instead of the actual redirect_uri from initial authorization
- OAuth 2.0 requires redirect_uri in refresh requests to match the one used during initial authorization
- Now properly retrieves redirect_uri from OAuth implementation object (coordinator.py:355-407)
- Falls back to stored redirect_uri in config entry if implementation is unavailable
- Integration will now successfully refresh tokens and maintain authentication
- Enhanced config flow to properly store redirect_uri during initial setup (config_flow.py:128-157)
- Retrieves redirect_uri from OAuth implementation instead of using hardcoded value
- Includes proper error handling and fallback mechanisms
- Improves reliability of token refresh operations
- Modified
_refresh_oauth_token()in coordinator.py to useimplementation.redirect_uri - Enhanced logging to show which redirect_uri is being used for token refresh
- Only uses hardcoded fallback as last resort with warning message
- Ensures OAuth 2.0 compliance for token refresh operations
- Long-term Statistics Support: All balance sensors now support Home Assistant long-term statistics
- Changed from
state_class = "measurement"(v2.2.14) tostate_class = "total"(compatible with monetary device class) - Enables historical tracking for Cash Balance, Total Value, Non-Margin Positions, and Cash Transfer Balance sensors
- Provides extended history beyond standard 10-day retention
- Enables statistics cards with min, max, mean values and trend analysis
- All balance sensors now have same long-term statistics support as Accumulated Profit/Loss sensor
- Changed from
- Fixed Home Assistant validation errors from v2.2.14
- Changed
state_classfrom "measurement" to "total" for balance sensors (sensor.py:180) - Home Assistant's
monetarydevice class only supportsstate_class = "total", not "measurement" - Fixes warnings: "Entity is using state class 'measurement' which is impossible considering device class ('monetary')"
- Changed
- Modified
SaxoBalanceSensorBase.__init__()to setself._attr_state_class = "total" - All balance sensors inheriting from this base class now support long-term statistics
- Compatible with Home Assistant's monetary device class requirements
- No breaking changes - existing functionality remains unchanged
This release was yanked due to Home Assistant validation errors. See v2.2.15 for the fix.
- Attempted to add long-term statistics support to balance sensors
- Added
state_class = "measurement"toSaxoBalanceSensorBase - This configuration is incompatible with
device_class = "monetary"in Home Assistant
- Added
- Fixed "Unclosed client session" error during OAuth token refresh
- Old API client session wasn't being closed before creating new one
- Added proper cleanup: close old client before setting to None
- Prevents resource leaks and error messages in logs
- Client closure happens in background to avoid blocking
- Fixed repeated market hours debug logging
- Multiple sensors were calling
_is_market_hours()simultaneously during state updates - Added 1-second cache to
_is_market_hours()to avoid redundant calculations - Reduces log noise from 3+ identical messages to 1 per second
- Improves performance by caching timezone calculations
- Multiple sensors were calling
- Critical: Fixed integration reloading every time OAuth token is refreshed
- Token refresh triggered config entry update listener causing full integration reload
- Now coordinator handles token updates internally without triggering reload
- Prevents unnecessary integration restarts every 20 minutes during token refresh
- Only reloads when actual configuration changes (not token updates)
- Critical: Fixed performance cache never updating when client details are successfully fetched
- Incorrect indentation in coordinator.py:826-840 caused cache update to only occur when client_details was None
- This defeated the entire caching mechanism and caused unnecessary API calls
- Performance cache now properly updates every 2 hours as designed
- Fixed duplicate condition check in SaxoLastUpdateSensor.native_value
- Removed redundant hasattr() check that was executed twice
- Simplified logic for better readability
- Fixed naive datetime usage in sensor availability check
- Now uses dt_util.as_utc() instead of manual pytz.UTC.localize()
- More consistent with Home Assistant datetime handling standards
- Improved error handling in coordinator client details fetch
- Now logs exception type and message for better debugging
- Previously used generic
except Exceptionwithout logging details
- Removed unused
_fetch_performance_data()method from coordinator- Dead code cleanup - method was defined but never called
- Reduces code complexity and maintenance burden
- Updated test_sticky_availability.py to use UTC-aware datetimes (dt_util.utcnow())
- All datetime comparisons now use timezone-aware timestamps
- Fixed test expectations to match actual sticky availability behavior
- All 8 sticky availability tests now pass
-
Comprehensive Rate Limiting Prevention: Eliminated 429 rate limiting errors
- Batched v4 API calls: Reduced from 7 calls to 4 calls per performance update (43% reduction)
- New
get_performance_v4_batch()method fetches AllTime/YTD/Month/Quarter with built-in 0.5s delays - Added inter-call delays between balance and client details calls
- Staggered multi-account updates with random 0-30s offset to prevent simultaneous API requests
- Impact: 14 calls in <2s → 8 calls over 4+ seconds (with 2 accounts)
-
Integration Reload Loop Fix: Fixed integration repeatedly loading/unloading on startup
- Added
_setup_completeflag to properly track initial setup completion - Reload check now only triggers after platform setup finishes, not during initial refresh
- Prevents unnecessary reload when client name is fetched during normal startup
- Preserves reload functionality for genuinely skipped sensors (unknown client data)
- Added
-
AttributeError Fix: Fixed crash during coordinator initialization
- Removed premature access to
self.databefore parent class initialization - Simplified
_last_known_client_nameinitialization to always start as "unknown"
- Removed premature access to
-
Performance Cache Interval: Increased from 1 hour to 2 hours
- Reduces performance update frequency by 50%
- Balance data still updates every 5-30 minutes based on market hours
- Performance data changes slowly, 2-hour cache is acceptable
-
API Client (
api/saxo_client.py):- Added
get_performance_v4_batch()method for batched performance data fetching - Existing individual v4 methods maintained for backwards compatibility
- Added
-
Coordinator (
coordinator.py):- Added
randomimport for stagger offset generation - Added
_setup_completeflag andmark_setup_complete()method - Added
_initial_update_offsetproperty with 0-30s random value - Refactored performance data fetch to use batched method
- Added strategic delays between API calls to prevent burst traffic
- Enhanced reload logic with setup completion tracking
- Added
-
Init (
__init__.py):- Call
coordinator.mark_setup_complete()after platform setup - Enhanced error logging with full traceback for debugging
- Call
-
Constants (
const.py):PERFORMANCE_UPDATE_INTERVAL: Changed from 1 hour to 2 hours
- Before: 14 API calls in <2 seconds (2 accounts × 7 calls each)
- After: 8 API calls spread over 4+ seconds, staggered between accounts
- Rate Limit Risk: Eliminated (well under 120/min threshold)
- All changes are backwards compatible
- No breaking changes to sensor entities or data structure
- Integration now handles multiple accounts gracefully without rate limiting
- Startup is clean with no reload loops or crashes
- Reload Loop Prevention (Correct Fix): Finally fixed the reload loop with proper timing logic
- Added
_setup_completeflag to track when platform setup finishes - Reload check now uses
self._setup_completeinstead ofself.last_update_success_time - Only triggers reload AFTER initial setup completes, not DURING it
- Added
The timing was misunderstood:
async_setup_entrycallscoordinator.async_refresh()(first update)- During this refresh,
_async_update_data()runs the reload check - At this point:
last_update_success_timegets set by coordinator base class - Sensors haven't been initialized yet (
_sensors_initialized == False) - Beta.3's condition
last_update_success_time is not Nonewas True! - Reload triggered immediately during initial setup
- New
_setup_completeflag starts as False - Reload requires:
_setup_complete == True(instead of checking last_update_success_time) mark_setup_complete()called AFTER platform setup finishes- Reload logic only activates after initial setup completes
- Coordinator (
coordinator.py):- Line 71: Added
_setup_completeflag (starts False) - Line 1038: Changed condition from
last_update_success_time is not Nonetoself._setup_complete - Line 1233-1241: Added
mark_setup_complete()method
- Line 71: Added
- Init (
__init__.py:49):- Call
coordinator.mark_setup_complete()after platform setup
- Call
- This is the CORRECT fix based on proper understanding of async_refresh timing
- All rate limiting improvements from beta.1 remain unchanged
- This is a beta prerelease for final verification before stable release
- Reload Loop Prevention (Improved): Completely fixed integration reload loop on startup
- Added 4th condition to reload check:
self.last_update_success_time is not None - Prevents reload during initial setup when client name is legitimately fetched for first time
- Only triggers reload when client name changes from unknown→known AFTER initial setup
- This is the scenario where sensors were skipped and need to be created later
- Added 4th condition to reload check:
Beta.2's fix was incomplete:
- Initial setup: coordinator created,
self.datais None - First refresh: fetches client_name = "20482598"
- Beta.2 still triggered reload because this looked like unknown→known transition
- Reload executed even though sensors were about to be created normally
- Created load/unload cycle
Reload now requires ALL 4 conditions:
- ✅ Previous client name was "unknown"
- ✅ Current client name is valid (not "unknown")
- ✅ Sensors not initialized (
_sensors_initialized == False) - ✅ NOT the first update (
last_update_success_time is not None)
Condition #4 ensures reload only happens AFTER initial setup completes, when sensors were genuinely skipped due to unknown client name.
- Coordinator (
coordinator.py:1023-1028):- Added
self.last_update_success_time is not Noneto reload condition - Prevents reload during normal initial setup flow
- Preserves reload functionality for genuinely skipped sensor scenarios
- Added
- This completely fixes the reload loop from beta.1 and beta.2
- All rate limiting improvements from beta.1 remain unchanged
- This is a beta prerelease for final testing before stable release
- Reload Loop Prevention: Fixed integration repeatedly loading and unloading on startup
- Initialize
_last_known_client_namefrom cached coordinator data if available - Prevents unnecessary config entry reload when client name hasn't actually changed
- Coordinator now properly detects when client name genuinely changes vs already-known values
- Integration loads once and stays loaded (no more load/unload cycles)
- Initialize
- Coordinator (
coordinator.py:67-69):_last_known_client_namenow initialized fromself.dataif available- Only triggers reload when client name genuinely changes from unknown to known
- Fixes issue introduced in v2.2.3's conditional sensor creation feature
- This fixes the reload loop observed in beta.1
- All rate limiting improvements from beta.1 remain unchanged
- This is a beta prerelease for testing the reload fix
-
Batched v4 API Calls: Consolidated 4 separate performance v4 API calls into single batched method
- Reduces from 7 API calls per performance update to 4 calls (43% reduction)
- With 2 accounts: 14 calls → 8 calls per performance update cycle
- New
get_performance_v4_batch()method fetches AllTime/YTD/Month/Quarter in sequence with delays - Prevents 429 rate limiting errors (120 requests/minute limit)
-
Inter-Call Delays: Added 0.5s delays between sequential API calls
- Spreads API calls over time instead of instant burst
- Delay between balance and client details calls
- Delay before batched performance calls
- Internal delays between v4 batch calls (AllTime → YTD → Month → Quarter)
- 4 calls now spread over ~2 seconds instead of <1 second
-
Staggered Multi-Account Updates: Added random 0-30s offset per config entry on startup
- Prevents multiple accounts from hitting performance updates simultaneously
- Each account coordinator has unique
_initial_update_offset - Offset applied once on first update, then cleared
- Reduces peak load when multiple accounts configured
-
Performance Cache Interval: Increased from 1 hour to 2 hours
- Reduces performance update frequency by 50%
- Balance data still updates every 5-30 minutes (unchanged)
- Performance data changes slowly, 2-hour cache is acceptable
- Further reduces rate limiting risk
-
API Client (
api/saxo_client.py):- Added
get_performance_v4_batch()method with built-in rate limiting - Existing individual v4 methods maintained for backwards compatibility
- Added
-
Coordinator (
coordinator.py):- Added
randomimport for stagger offset generation - Added
_initial_update_offsetproperty with 0-30s random value - Refactored performance data fetch to use batched method
- Added strategic delays between API calls to prevent burst traffic
- Added
-
Constants (
const.py):PERFORMANCE_UPDATE_INTERVAL: Changed from 1 hour to 2 hours
- Before: 14 API calls in <2 seconds (2 accounts × 7 calls each)
- After: 8 API calls spread over 4+ seconds, staggered between accounts
- Rate Limit Risk: Eliminated (well under 120/min threshold)
- All 4 rate limiting improvements implemented together for maximum effectiveness
- Batched calls maintain all existing functionality and data
- This is a beta prerelease for testing rate limiting fixes
- Coordinator Timeout: Increased from 30s to 60s to accommodate sequential API calls
- Testing beta.1 showed API calls succeeding but hitting 30s timeout
- Balance fetch (0.11s), client details, and performance data all succeed
- 60s provides enough time for all sequential API calls to complete
- Maintains single-layer timeout structure (no nested contexts)
- const.py:
COORDINATOR_UPDATE_TIMEOUTincreased from 30s to 60s - No other logic changes from beta.1
- Builds on beta.1's nested timeout fix
- API calls were working correctly in beta.1, just needed more time
- This is a beta prerelease for testing the adjusted timeout
- Nested Timeout Problem: Fixed integration startup failures caused by triple-nested timeout contexts
- Removed nested
API_TIMEOUT_BALANCE,API_TIMEOUT_PERFORMANCE,API_TIMEOUT_CLIENT_INFOconstants - Restored
COORDINATOR_UPDATE_TIMEOUTfrom 90s back to 30s (v2.2.2 value) - Removed 5 nested
async_timeout.timeout()calls in coordinator - Integration now starts successfully without repeated timeout warnings
- Removed nested
- v2.2.5 introduced triple-nested timeout contexts that raced with each other
- Nested timeouts (coordinator → balance/performance → API client) interrupted API client retry logic
- Caused retry counter to reset repeatedly, showing "Request timeout (attempt 1/3)" indefinitely
- Integration would wait 132+ seconds and fail to start, even when Saxo API was fully operational
- Reverted to v2.2.2's proven timeout structure
- Single coordinator timeout layer (30s) lets API client handle its own timeouts and retries
- Eliminates timeout race conditions
- Restores working behavior from v2.2.2
- const.py: Removed progressive timeout constants, restored simple 30s coordinator timeout
- coordinator.py: Removed nested timeout logic from 5 locations:
- Balance data fetch
- Client details fetch
- Performance v3 fetch
- Performance v4 fetch
- Individual performance period fetches
- All code quality checks passing: Ruff (linting), Python syntax
- No functionality changes beyond timeout handling
- Backwards compatible with v2.2.2 timeout behavior
- This is a beta prerelease for testing the timeout fix
- Versions v2.2.5, v2.2.6, v2.2.7 all had this nested timeout issue
- v2.2.2 and earlier worked correctly
- Please test and report results before stable v2.2.8 release
Note: This refactoring release was reverted due to startup timeout issues discovered during testing.
Note: This version reverted the v2.3.0-beta.1 refactoring but still contained the nested timeout issue from v2.2.5.
- Enhanced Rate Limiting Messages: Improved rate limiting error reporting for better user experience
- Changed first rate limit occurrence from WARNING to DEBUG level to reduce startup noise
- Added context-aware messages explaining rate limiting is normal during startup and high API usage
- Enhanced error messages to distinguish between expected rate limiting and potential issues
- Added startup phase tracking to provide better context for initial integration setup
- Startup Experience: Reduced confusing rate limit warnings during integration startup
- Rate limiting during startup is normal behavior and no longer generates warning messages
- Subsequent rate limit hits still generate warnings to indicate potential issues
- Added helpful context about when rate limiting is expected vs concerning
- Startup phase detection in coordinator for first 3 successful updates
- Context-aware rate limiting messages in API client with better user guidance
- Enhanced logging to help users understand normal vs problematic rate limiting scenarios
- Enhanced Timeout Handling: Significantly improved timeout management and error reporting
- Increased coordinator timeout from 30s to 90s to accommodate multiple API calls
- Added progressive timeouts: Balance (45s), Performance (60s), Client Info (30s)
- Enhanced timeout error messages with actual timing information and user guidance
- Implemented smart timeout warning system (first warning, then debug level for 5 minutes)
- Added comprehensive timing logs for debugging API performance issues
- Network Resilience: Better handling of network connectivity issues and high API load scenarios
- Timeout errors now provide actionable guidance instead of generic error messages
- Improved error recovery with detailed context about network conditions
- Reduced timeout error noise while maintaining visibility into genuine issues
- Progressive timeout implementation for different API endpoint types
- Enhanced logging with request timing information for troubleshooting
- Improved error message context with actual vs expected timing
- Better separation of concerns between critical and optional data fetching
- Logging Optimization: Reduced log noise by changing token refresh warnings to debug level
- Changed "Token expires very soon, immediate refresh needed" from WARNING to DEBUG level
- Token refresh operations are normal behavior and don't require user attention
- Cleaner Home Assistant logs with less verbose OAuth token management messaging
- Conditional Sensor Creation: Enhanced integration robustness with unknown client name protection
- Sensors are only created when valid client data is successfully retrieved from the Saxo API
- Prevents orphaned devices and entities when API authentication initially fails
- Automatic config entry reload when client data becomes available
- Clear warning messages with user guidance when sensor setup is skipped
- Enhanced Error Handling: Better handling of scenarios where client information is unavailable
- Integration gracefully handles temporary API unavailability during initial setup
- Automatic recovery when client data becomes available without user intervention
- Prevents unnecessary entity registry entries for incomplete integrations
- Testing Coverage: Added comprehensive tests for conditional sensor creation behavior
- Tests validate both successful creation and proper skipping scenarios
- Enhanced test coverage for config entry reload functionality
- Added client name validation in
async_setup_entry()before sensor creation - Implemented automatic config entry reload detection and scheduling
- Enhanced coordinator tracking for sensor initialization status
- Improved logging with actionable user guidance for troubleshooting
- Sensor Availability Stability: Improved sensor availability logic to prevent brief unavailability during updates
- Enhanced sticky availability system to maintain sensor availability during normal coordinator updates
- Sensors now only become unavailable after sustained failures (15+ minutes or 3x update interval)
- Fixed timezone-aware datetime comparison issues in availability calculations
- Improved edge case handling for initial startup and missing update timestamps
- Sensors remain available during OAuth token refresh operations
- Better handling of coordinators without last_successful_update_time attribute
- Refactored availability logic in SaxoSensorBase for better maintainability
- Added proper timezone conversion for datetime comparisons (UTC-aware)
- Improved graceful degradation when update timestamps are unavailable
- Enhanced code readability with clearer logic flow and documentation
2.2.1 - 2025-09-18
- HTTP Session Management: Enhanced session cleanup to prevent "Unclosed client session" errors
- Improved error handling in API client close methods with comprehensive logging
- Enhanced session recreation detection and logging in API client
- Added robust error handling to coordinator shutdown methods
- Better exception logging with stack traces for debugging session issues
- Ensures proper cleanup during integration unload and token refresh operations
- Added detailed logging for session creation, closure, and error scenarios
- Enhanced async_shutdown method with proper exception handling
- Improved _close_old_client method with comprehensive error recovery
- Better debugging capabilities for HTTP session lifecycle management
2.2.0 - 2025-09-18
- Python Version Requirement: Updated minimum Python version requirement to 3.13+
- Removed support for Python 3.11 and 3.12
- Updated all project configuration files to require Python 3.13+
- GitHub Actions workflows now test only Python 3.13
- Enhanced compatibility with latest Python features and improvements
- Python 3.13 Support: Full compatibility with Python 3.13
- Updated pyproject.toml with Python 3.13 classifiers and requirements
- Updated ruff configuration to target Python 3.13
- Updated MyPy configuration for Python 3.13 type checking
- Enhanced development toolchain for modern Python features
- Streamlined Python version support for better maintainability
- Updated CI/CD pipeline to use Python 3.13 exclusively
- Enhanced type checking and linting with Python 3.13 target
- Improved code quality tools configuration for latest Python version
- Documentation Updates: Updated project documentation with correct dates and current state
- Refreshed CLAUDE.md development guidelines with current implementation status
- Updated changelog with proper release version and date formatting
- Enhanced technical documentation for better developer onboarding
- Improved documentation accuracy and consistency across project files
- Enhanced development environment setup instructions
- Updated project status and implementation details
2.1.8 - 2025-09-18
- Unclosed Client Session During Token Refresh: Enhanced fix for memory leak from unclosed aiohttp sessions
- Improved api_client property with safer old client closure handling
- Added dedicated
_close_old_client()method with proper error handling and logging - Enhanced token comparison logic to detect when client needs recreation
- Uses async_create_task to properly close old client sessions during token refresh
- Eliminates "Unclosed client session" errors during OAuth token refresh cycles
- Prevents memory leaks and improves resource management with comprehensive error handling
2.1.7 - 2025-01-18
- Options Flow Deprecation Warning: Fixed deprecated config_entry assignment for Home Assistant 2025.12 compatibility
- Replaced manual config_entry assignment with dynamic property retrieval
- Updated SaxoOptionsFlowHandler to use modern Home Assistant pattern
- Resolves deprecation warning: "sets option flow config_entry explicitly"
- Ensures compatibility with future Home Assistant versions
2.1.6 - 2025-09-18
- Performance Sensor Inception Date Issue: Resolved misleading inception date display
- Removed hardcoded "2020-01-01" fallback that was showing incorrect inception dates for all performance sensors
- Removed
inception_dayattribute entirely as the required API FieldGroups are not available in the timeseries endpoint - Validated against Saxo API specifications -
InceptionDayfield is only available in/hist/v4/performance/summaryendpoint - AllTime performance sensors now show generic "inception" indicator instead of specific dates
- Prevents display of misleading dates while maintaining sensor functionality
- Major Sensor Architecture Optimization: Implemented shared base class hierarchy
- Reduced code duplication by 31% (from 1,472+ lines to 1,017 lines)
- Created
SaxoSensorBasefor common functionality across all sensors - Created
SaxoBalanceSensorBasefor monetary balance sensors with currency handling - Created
SaxoDiagnosticSensorBasefor diagnostic sensors with proper categorization - Enhanced
SaxoPerformanceSensorBaseto inherit fromSaxoSensorBase - Maintained all existing functionality while improving maintainability
- Enhanced Sensor Availability During Updates: Fixed sensors briefly showing as unavailable during coordinator updates
- Implemented "sticky availability" logic that keeps sensors available during normal update cycles
- Sensors only become unavailable after sustained failures (15+ minutes or 3 failed update cycles)
- Prevents UI flashing and maintains stable sensor states during API fetches
- Graceful handling of startup scenarios and genuine failures
- Enhanced availability logic in all sensor base classes
- Device Info Cleanup: Removed firmware version from device information display
- Explicitly set
sw_version=NoneinDeviceInfoto prevent automatic version display - Cleaner device presentation in Home Assistant UI
- Explicitly set
- Balance sensors reduced from ~100 lines each to ~10 lines each
- Diagnostic sensors reduced from ~40-60 lines each to ~15-25 lines each
- Comprehensive test suite updated to validate new architecture
- Enhanced availability logic with adaptive thresholds based on update intervals
- Improved code maintainability and extensibility for future sensor additions
2.1.5 - 2025-09-17
- Performance Sensor Last Updated Attribute: Fixed performance sensors last_updated attribute showing as unknown or missing
- Performance sensors now use performance cache timestamp (
_performance_last_updated) when available - Added fallback to general coordinator timestamp when performance cache is not yet populated
- Ensures performance sensors always display accurate last updated information
- Performance sensors now use performance cache timestamp (
- Performance Sensor Availability: Fixed performance sensors showing as unavailable despite API data being correctly fetched
- Added proper
availableproperty toSaxoPerformanceSensorBaseclass - Performance sensors now check if they can retrieve performance values from coordinator
- Affects Investment Performance, YTD, Month, and Quarter performance sensors
- Added proper
- Enhanced availability logic for performance sensors to validate data accessibility
- Improved error handling when checking performance value availability
- Performance Sensor Last Updated: Fixed "last_updated" attribute showing as "unknown" for performance sensors
- Converted datetime objects to ISO format strings for proper display in Home Assistant
- Performance sensors now properly show when performance data was last fetched
- Display Name Sensor Icon: Fixed missing icon for Display Name diagnostic sensor
- Changed from invalid
mdi:account-card-detailsto validmdi:account-boxicon - Added proper icon property method to ensure display in Home Assistant interface
- Changed from invalid
- OAuth Token Refresh: Added fallback redirect_uri for token refresh operations
- Prevents token refresh failures when redirect_uri is missing from config entry
- Uses standard Home Assistant OAuth redirect URL as fallback
- Enhanced error handling for OAuth token refresh with proper fallback mechanisms
- Added debug logging for sensor initialization and icon property calls
- Improved timestamp formatting for performance sensor attributes
2.2.1 - 2025-09-17
- Sensor Attribute Improvements: Cleaned up cross-reference attributes and enhanced performance sensor metadata
- Removed "total_value" attribute from Cash Balance sensor to eliminate circular references
- Removed "cash_balance" attribute from Total Value sensor to eliminate circular references
- Added "time_period" attribute to performance sensors showing StandardPeriod values ("AllTime" or "Year")
- Fixed "last_updated" attribute in Accumulated Profit/Loss sensor to use performance API timestamp instead of balance API
- Performance sensors now include proper time period identification matching API parameters
- Enhanced SaxoPerformanceSensorBase with time_period support and abstract _get_time_period() method
- Improved attribute consistency across all sensor types with appropriate data source timestamps
- Fixed data source mapping for last_updated attributes ensuring accuracy and consistency
2.2.0 - 2025-09-17
- YTD Investment Performance Sensor: New Year-to-Date portfolio return percentage sensor (
sensor.saxo_{clientid}_ytd_investment_performance) - Smart Performance Caching: Intelligent hourly caching system for performance data to optimize API usage
- Balance data: Real-time updates (5-30 minutes based on market hours)
- Performance data: Cached updates (1 hour) reducing API calls by ~90%
- Account/Client ID Diagnostic Sensors: New diagnostic entities for troubleshooting and multi-account identification
- Client ID sensor (
sensor.saxo_{clientid}_client_id) showing the Saxo Client ID used for entity naming - Account ID sensor (
sensor.saxo_{clientid}_account_id) displaying Account ID from balance data
- Client ID sensor (
- API Optimization: Performance endpoints now called hourly instead of every 5-30 minutes
- Code Quality: Refactored performance sensors with shared base class (SaxoPerformanceSensorBase) to reduce code duplication
- Entity Count: Expanded from 10 to 13 total entities (7 portfolio + 6 diagnostic sensors)
- Diagnostic Suite: Complete integration monitoring with account identification, health status, and configuration visibility
- v4 API Integration: Added YTD performance support using "Year" StandardPeriod parameter
- Added
get_performance_v4_ytd()method to API client for year-to-date performance data - Implemented performance data caching with
_should_update_performance_data()validation - Enhanced coordinator with smart update logic balancing real-time and cached data
- Added
get_account_id()method to coordinator for Account ID access - Enhanced balance data structure to include Account ID from API response
- Added
PERFORMANCE_UPDATE_INTERVALconstant (1 hour) for cache management - Reduced code duplication by ~24 lines through base class implementation
- Added two new diagnostic sensor classes (SaxoClientIDSensor, SaxoAccountIDSensor)
- Updated README.md with new YTD sensor, performance caching, and diagnostic sensors
- Enhanced CLAUDE.md with implementation details and updated file references for 13 entities
- Added configuration table showing different update intervals for balance vs performance data
- Updated entity count documentation from 11 to 13 total entities across all files
2.1.1 - 2025-09-17
- Options Flow Deprecation: Removed explicit
self.config_entry = config_entryassignment in options flow handler to fix Home Assistant 2025.12 deprecation warning
2.1.0 - 2025-09-17
- Diagnostics Support: Comprehensive diagnostic information for debugging and support
- Timezone configuration and market hours settings
- Detailed token expiry information with human-readable timestamps
- Coordinator status and update intervals
- Market hours detection status
- Dynamic version reading from manifest.json
- Diagnostic Sensors: Four dedicated diagnostic entities for real-time monitoring
- Token Expiry sensor with countdown and status indicators
- Market Status sensor showing current market state
- Last Update sensor with timestamp tracking
- Timezone sensor displaying configuration details
- Token Diagnostics: Comprehensive expiry tracking with status indicators (OK/WARNING/CRITICAL/EXPIRED)
- Market Configuration: Clear visibility into configured timezone and update intervals
- Security: All sensitive data automatically redacted in diagnostics
- Token Refresh: Added redirect_uri to config entry to fix OAuth token refresh failures
- State Class: Changed accumulated profit/loss sensor state_class from 'measurement' to 'total' for proper Home Assistant compatibility
- Configurable Market Timezone: Select from 9 major market timezones for intelligent scheduling
- "Any" Mode: Option to disable market hours detection with fixed 15-minute update intervals
- Options Flow: Change timezone configuration after initial setup through integration options
- Global Market Support: NYSE, LSE, Euronext, XETRA, TSE, HKEX, SGX, ASX markets
- Timezone Selection Step: New configuration step during initial setup to select market timezone
- Intelligent Scheduling: Dynamic update intervals based on selected market hours
- Backward Compatibility: Default timezone set to America/New_York for existing installations
- Market Hours Detection: Automatic DST handling for all supported timezones
- Update Intervals: 5 min (market hours), 30 min (after hours), 15 min ("any" mode)
- Added comprehensive timezone constants and market hours configuration
- Updated coordinator to use configurable timezone instead of hardcoded ET
- Enhanced config flow with timezone selection and options flow handler
- Added UI strings for timezone configuration in strings.json
2.0.3 - 2025-09-12
- Investment Performance Sensor: New sensor tracking overall portfolio return percentage from
/hist/v4/performance/timeseriesendpoint - Cash Transfer Balance Sensor: New sensor showing latest cash transfer value from performance timeseries data
- Enhanced API Coverage: Added v4 performance endpoint support alongside existing v3 endpoint
- Comprehensive Portfolio Monitoring: Now provides 6 sensors covering all key portfolio metrics
- Performance Analytics: ReturnFraction data converted to percentage for easy interpretation
- Historical Data Integration: Cash transfer tracking from timeseries performance data
- Added
get_performance_v4()method to API client for v4 performance endpoint - Enhanced coordinator with investment performance and cash transfer data fetching
- Improved sensor naming consistency with client_id integration
- All code validated with ruff formatting and quality checks
- Total sensors increased from 4 to 6:
- Cash Balance (existing)
- Total Value (existing)
- Non-Margin Positions Value (existing)
- Accumulated Profit/Loss (existing)
- Investment Performance (new)
- Cash Transfer Balance (new)
2.0.0 - 2025-09-11
- Automatic Entity Naming: Entity prefixes now automatically use Saxo Client ID (e.g.,
saxo_123456_cash_balance) - Removed Options Flow: No longer configurable entity prefixes - all entities use
saxo_{clientid}format - Removed Cash Deposit Sensor: Streamlined to 4 core sensors for better focus on available data
- Client ID Integration: Automatically fetches Client ID from
/port/v1/clients/meendpoint - Performance Analytics: Added all-time profit/loss tracking via
/hist/v3/perf/endpoint - Non-Margin Positions: Added sensor for non-margin trading positions value
- Accumulated Profit/Loss: Historical performance tracking with BalancePerformance data
- Simplified Setup: No configuration needed - entities automatically named using your Saxo Client ID
- Better API Coverage: Now uses 3 Saxo endpoints for comprehensive portfolio data
- Streamlined Sensors: Focused on 4 core sensors that provide meaningful data
- Unique Entity IDs: Each Saxo client gets unique entity names preventing conflicts
- Removed problematic accounts endpoint dependency
- Cleaner API client with focused endpoint usage
- Simplified configuration flow without user input requirements
- Enhanced error handling for client details retrieval
- Custom entity prefix configuration (now automatic)
- Cash deposit sensor (was returning 0.0 without proper data source)
- Options flow for prefix changes
- Manual entity naming system
1.0.1 - 2025-09-10
- Platinum Quality Compliance: Achieved Home Assistant Quality Scale Platinum status
- Code Documentation: Added comprehensive inline comments for complex logic and architectural decisions
- Type Coverage: Enhanced type annotations across all modules (76% function coverage)
- Performance Optimization: Refined concurrent API request handling and data processing efficiency
- Enhanced code clarity with detailed algorithmic explanations
- Improved error handling documentation and fallback strategies
- Optimized data handling patterns for reduced CPU and memory usage
- Strengthened asynchronous architecture with better task management
1.0.0 - 2025-09-10
- Initial release of Saxo Portfolio Home Assistant integration
- OAuth 2.0 authentication with automatic token refresh
- Portfolio monitoring with multiple sensor types:
- Total portfolio value
- Cash balance
- Unrealized P&L
- Position count
- P&L percentage
- Account-specific balance tracking
- Individual position monitoring
- Smart update scheduling based on market hours (5 min market hours, 30 min after hours)
- Rate limiting with server-side and client-side protection
- Environment separation (simulation vs production)
- Comprehensive error handling and recovery
- Application Credentials: Proper Home Assistant credential management (replaced hardcoded test values)
- OAuth Security: Cryptographically secure state parameters using
secrets.token_urlsafe(32) - SSL/TLS: Explicit SSL certificate verification with secure TCP connector
- Data Protection: Comprehensive sensitive data masking in logs
- Masked tokens, API keys, and authentication headers
- Safe URL logging with parameter redaction
- Error message sanitization to prevent information leakage
- CSRF Protection: Secure OAuth state parameter validation
- Encrypted Storage: OAuth tokens encrypted in Home Assistant config entries
- GitHub Actions Workflows:
- HACS Action validation for repository standards
- Hassfest validation for Home Assistant integration compliance
- Automated testing with Python 3.11/3.12
- Code quality checks with Ruff linting and formatting
- Type checking with MyPy
- Testing Framework:
- Structure validation tests
- Contract tests for API compliance
- Integration tests for end-to-end functionality
- Automated code quality and security checks
- Code Quality:
- 372+ linting issues resolved
- Modern Python type annotations
- Comprehensive error handling
- Security-focused logging patterns
- Complete repository structure for HACS publication
- All required files: README.md, LICENSE, CHANGELOG.md, hacs.json
- Proper manifest.json configuration
- GitHub Actions for continuous validation
- Security documentation (SECURITY.md)
- Dynamic update intervals based on market hours detection
- Exponential backoff retry logic with intelligent error categorization
- Market hours detection with Eastern Time timezone handling
- Comprehensive API client with rate limiting and timeout management
- Modular data models for type safety and consistency
- Proper Home Assistant integration patterns