feat: Add YTD profit/loss and net transfers sensors; correct YTD window - #12
Merged
Merged
Conversation
Adds design for two currency-denominated YTD sensors (profit/loss, net deposits/withdrawals) and repointing the existing YTD percentage sensor. Live API probing found StandardPeriod=Year is a trailing 12-month window, not year-to-date, so the shipped ytd_investment_performance sensor reports rolling-1-year performance (17.83% vs true YTD 9.32%). Month and Quarter are trailing too; correcting those is deferred. Net API cost is zero: the trailing-Year call has no reader once the YTD percentage is repointed, so the Jan-1 anchored call takes its slot.
Six TDD tasks: remove dead v4 helpers, anchor the batch to 1 January, parse the two new metrics, wire the coordinator, add the sensors with translations, document the repoint.
get_performance_v4, _ytd, _month and _quarter were referenced only by their own tests; get_performance_v4_batch is the sole production caller of the v4 endpoint.
StandardPeriod=Year is a trailing 12-month window, not year-to-date. Replace it with an explicit FromDate/ToDate range (the API rejects FromDate alone) and trim Month/Quarter to KeyFigures, which is all anything reads. Still four requests per refresh.
get_performance_v4_batch now requires ytd_from/ytd_to keyword arguments (previous commit); update its only caller in the coordinator so mypy stays clean on every commit. Computes the window from the current date: 1 January of the current year through today.
Adds ytd_profit_loss (current calendar-year YearlyProfitLoss bucket) and ytd_cash_transfer (last CashTransfer value, cumulative within the window). Both default to None rather than 0.0 so missing data cannot render as a plausible-looking zero on a currency sensor.
Coordinator owns the clock and passes the window to the client. Adds get_ytd_profit_loss/get_ytd_cash_transfer getters returning float | None, and stops logging monetary amounts at DEBUG.
Two currency-denominated year-to-date sensors reading the Jan-1 anchored window. Both go unavailable rather than reporting 0.0 when data is missing.
Three "nine sensors" mentions undercounted by two after the YTD profit/loss and YTD net transfers sensors were added, bringing the supported sensor total to eleven (3 balance + 8 performance/transfer).
…E caveat Pre-merge review fixes: - SaxoYTDCashTransferSensor now exposes a last_reset property pinned to 1 January local midnight (via dt_util.now()/start_of_local_day), recomputed on every access. Without it, the annual reset of the Jan-1 anchored source window was being recorded as a permanent negative delta into the entity's long-term-statistics sum every 1 January. - _get_period_dates now uses dt_util.now() instead of the naive, process-local datetime.now(), matching the rest of the codebase and the CHANGELOG's claim that the from/thru attributes are correct. - README's Month/Quarter Investment Performance entries no longer assert a to-date window; both now note the trailing ~28/~90 day windows already documented as a known issue in CHANGELOG.md.
Ruff >=0.16 formats Python code blocks inside Markdown files. The design docs quote deliberately partial snippets — class methods shown without their class, fragments of larger literals — and formatting rewrites them into code that no longer matches the source they document. CI installs ruff unpinned, so this surfaced as a format-check failure without any source change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two currency-denominated year-to-date sensors, and corrects the existing YTD percentage sensor, which was reporting a trailing 12-month figure.
While investigating which YTD datapoints Saxo exposes, probing two live accounts turned up a defect:
StandardPeriod=Yearis a trailing 12-month window, not year-to-date.So
ytd_investment_performancehas been reporting rolling-1-year performance:Changes
Added
sensor.saxo_{clientid}_ytd_profit_loss— YTD profit/loss in account currencysensor.saxo_{clientid}_ytd_cash_transfer— YTD net deposits and withdrawalsFixed
ytd_investment_performancenow uses an explicitFromDate/ToDatewindow anchored to 1 January. The API rejectsFromDatewithoutToDate, so both are sent._get_period_datesused naivedatetime.now()instead ofdt_util.now()Changed
Yearrequest is dropped; the January-anchored request takes its slot, so the refresh stays at four API callsMonth/Quarterrequests trimmed to theKeyFiguresfield groupget_performance_v4,get_performance_v4_ytd,get_performance_v4_month,get_performance_v4_quarter(referenced only by their own tests)Breaking change
The YTD percentage sensor's value changes on upgrade (17.83% → 9.32% on a test account). Its
entity_idis unchanged, so dashboards and automations keep working, but long-term statistics recorded before the upgrade are trailing-12-month figures — historical graphs will show a discontinuity at the upgrade point. Repointing in place was chosen deliberately over renaming, to avoid breaking existing dashboards.Design notes
None, not0.0, when data is missing. The two new sensors go unavailable rather than reporting zero — on a currency sensor,0.0reads as "you earned nothing this year" rather than "no data". This deliberately diverges from the surrounding metrics'0.0defaults.last_resetonytd_cash_transfer. It inheritsstate_class="total"+device_class=MONETARY, but unlike its all-time sibling this metric zeroes every 1 January. Withoutlast_resetthe recorder would log that drop as a large negative delta into the entity's cumulative statistics, permanently. Implemented as a property so it re-anchors at the year boundary without a restart.Parsing rules (verified against live responses)
Balance.YearlyProfitLossreturns per-calendar-year buckets, values per-year and not cumulative — the current-year bucket is selected by matching the year, not by assuming a single elementBalance.CashTransferis cumulative within the requested window and starts at zero, so the last numeric value is the net transferredCross-window consistency checks came back exact (
1.000000) on both accounts for the current-year bucket and for YTD net transfers computed two independent ways.Known issue (pre-existing, not addressed here)
Month and Quarter sensors are trailing windows too (rolling ~28 and ~90 days), so they are not month-to-date and quarter-to-date and their
from/thruattributes are inaccurate. Documented under Known Issues in the CHANGELOG and caveated in the README; correcting them needs two more API calls and was deferred.Testing
ruff checkclean,ruff format --checkclean,mypy --strictclean on 11 source filesNot yet verified against the Saxo web platform — the design rests on ratio evidence, since the probe deliberately never printed balances. Worth confirming the three YTD values after deploy.