mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-04 02:18:36 +00:00
docs(stock): document dividend event buttons
This commit is contained in:
@@ -47,20 +47,35 @@ Stock dividends are manual portfolio adjustments:
|
||||
|
||||
Ratios use `owned:new` exactly as written in the issuer notice. Equivalent
|
||||
unreduced ratios are accepted and the entered ratio is preserved in the reply.
|
||||
The bot validates syntax, tickers, and arithmetic safety, but does not look up
|
||||
notices or prevent duplicate calls. The caller is responsible for verifying the
|
||||
notice and avoiding accidental repeated adjustments.
|
||||
The bot validates syntax, tickers, and arithmetic safety. `/stock_portfolio`
|
||||
also checks SSI iBoard for cash and explicit share-dividend events published
|
||||
since each holding's last successful check. The portfolio is always sent first;
|
||||
each event then appears in its own message with an `Apply dividend` button. If
|
||||
no relevant event exists, no additional message is sent.
|
||||
|
||||
Suggestions expire after 24 hours and are bound to the Telegram user who
|
||||
requested the portfolio, the originating chat, and the event message. Another
|
||||
group member cannot apply them. Acceptance calculates from the user's current
|
||||
holding at click time, records the provider event atomically with the portfolio
|
||||
change, and prevents the same SSI event from being applied twice. SSI iBoard is
|
||||
an undocumented, best-effort source; failures do not prevent the portfolio from
|
||||
being shown. The bot does not persist dated lots, so suggestions are not legal
|
||||
record-date entitlement calculations. Users should verify the issuer notice.
|
||||
|
||||
The manual commands remain available, but they do not carry an SSI event ID.
|
||||
Applying an event manually and then accepting its button can therefore record
|
||||
the same dividend twice; use one method for a given event.
|
||||
|
||||
### Stock and coin P&L accounting
|
||||
|
||||
Stock and coin portfolios embed each open position under `assets.<symbol>`.
|
||||
Both store `quantity` and total remaining `base`; stock positions additionally
|
||||
store `dividendCheckedAt`. Stock cash is stored directly as `vnd`; coin cash
|
||||
remains `usd`. Buys add their actual spend. Partial sells remove basis using the
|
||||
weighted-average method and report realized P&L; full sells remove the position
|
||||
and its basis. Stock share dividends add shares without adding cost, which
|
||||
lowers the derived average price, while cash dividends do not change position
|
||||
basis.
|
||||
store `dividendCheckedAt` and an `openedAt` lifecycle marker. Stock cash is
|
||||
stored directly as `vnd`; coin cash remains `usd`. Buys add their actual spend.
|
||||
Partial sells remove basis using the weighted-average method and report realized
|
||||
P&L; full sells remove the position and its basis. Stock share dividends add
|
||||
shares without adding cost, which lowers the derived average price, while cash
|
||||
dividends do not change position basis.
|
||||
|
||||
`/stock_portfolio` and `/coin_portfolio` show aligned monospace tables with
|
||||
average entry price and unrealized P&L for each priced position. `Account P&L` remains the broader
|
||||
@@ -68,11 +83,20 @@ account value minus all top-ups, so it also reflects realized proceeds,
|
||||
dividend cash, and idle cash. If any current quote is unavailable, totals are
|
||||
marked partial and numeric Account P&L is withheld.
|
||||
|
||||
For stock positions, `dividendCheckedAt` is a future dividend-event cursor. It
|
||||
is initialized when a position is first bought, preserved across later buys and
|
||||
sells, and advanced when a stock dividend is recorded. A full exit removes the
|
||||
For stock positions, `dividendCheckedAt` is the dividend-event discovery cursor.
|
||||
It is initialized when a position is first bought, preserved across later buys
|
||||
and sells, and advanced after a successful event check or when a manual stock
|
||||
dividend is recorded. Failed checks do not advance it. A full exit removes the
|
||||
cursor; reopening the position starts it again. Coin positions do not store a
|
||||
dividend cursor.
|
||||
dividend cursor. Applied SSI event identities are retained in the stock
|
||||
portfolio as `appliedDividendEvents.<hashed_provider_event_id> =
|
||||
<applied_at_unix_milliseconds>` for idempotency and audit history. SSI queries
|
||||
overlap the previous Asia/Saigon calendar day to avoid missing provider rows
|
||||
whose publication time has only day precision; pending and applied provider IDs
|
||||
suppress duplicate suggestions. The stock-only `assets.<ticker>.openedAt`
|
||||
marker identifies the current position lifecycle and invalidates suggestion
|
||||
buttons after a full sale and later repurchase. Existing positions adopt this
|
||||
behavior without a startup migration.
|
||||
|
||||
## Layout
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
type: technical-journal
|
||||
topic: stock-dividend-event-buttons
|
||||
conducted_at: 2026-07-21T18:15:00+07:00
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Stock Dividend Event Buttons Journal
|
||||
|
||||
## Context
|
||||
|
||||
`/stock_portfolio` needed to surface recent dividend events for held tickers
|
||||
without obscuring the portfolio or silently changing financial state. SSI
|
||||
iBoard was selected as a best-effort discovery source, while the existing
|
||||
manual dividend commands remain available.
|
||||
|
||||
## What Happened
|
||||
|
||||
- The portfolio response is sent first. Each discovered cash or explicit share
|
||||
dividend follows in its own message with an `Apply dividend` button; a ticker
|
||||
with no relevant event produces no extra message.
|
||||
- Added a replaceable SSI provider with pagination, local event classification,
|
||||
stable event IDs, deduplication, and exact ratio handling.
|
||||
- Added callback registration to the shared module framework and persisted
|
||||
short-lived pending actions separately from portfolios.
|
||||
- Successful checks advance `dividendCheckedAt`; failed checks leave the cursor
|
||||
unchanged and never prevent the portfolio from being displayed.
|
||||
- Added `openedAt` to distinguish the holding lifecycle and retained applied SSI
|
||||
event identities for auditability and replay protection.
|
||||
|
||||
SSI discovery accepts only day-granularity date windows. Queries therefore use
|
||||
a cursor overlap at the lower boundary, then locally filter and deduplicate the
|
||||
returned events. This avoids missing events around a date boundary while stable
|
||||
SSI event IDs prevent overlap from producing repeated actionable suggestions.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- Callback data carries an opaque random token, not trusted dividend values.
|
||||
Pending actions expire after 24 hours and are bound to the requesting
|
||||
Telegram user, originating chat, and event message.
|
||||
- A callback reloads the current portfolio and calculates from the holding at
|
||||
click time. Another group member, an expired action, or a holding sold and
|
||||
reopened after suggestion creation cannot apply it.
|
||||
- Applied SSI event IDs are recorded atomically with the portfolio mutation, so
|
||||
duplicate clicks and retries cannot apply the same provider event twice.
|
||||
- Event fetches occur outside the per-user mutation lock. Cursor persistence
|
||||
reloads and merges under that lock, preserving concurrent buys, sells, and
|
||||
manual dividends without holding a lock across network calls.
|
||||
- Notification or provider failures do not advance the affected cursor. A later
|
||||
portfolio request can safely retry rather than lose an event.
|
||||
|
||||
## Verification
|
||||
|
||||
- Focused stock and module tests passed.
|
||||
- Full suite passed: `go test -count=1 ./...`.
|
||||
- Stock race tests and 20-iteration concurrency stress tests passed.
|
||||
- `go vet ./...` and `go build ./...` passed.
|
||||
- `golangci-lint run` completed with zero issues.
|
||||
- Independent testing, debugging, and code review found no blockers.
|
||||
|
||||
## Risks and Limitations
|
||||
|
||||
- SSI iBoard is undocumented and has no published stability or availability
|
||||
guarantee; the provider can require replacement if its contract changes.
|
||||
- The portfolio has no dated transaction lots, so current quantity is not proof
|
||||
of record-date entitlement. Users must verify the issuer notice.
|
||||
- Manual commands do not record an SSI event ID. Applying manually and later
|
||||
accepting the related button can count the same dividend twice.
|
||||
|
||||
## Next
|
||||
|
||||
Observe SSI response stability and callback behavior in real use. Commit these
|
||||
changes only if the user requests it.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
type: project-status-report
|
||||
topic: stock-dividend-event-buttons
|
||||
reported_at: 2026-07-21T18:15:00+07:00
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Stock Dividend Event Buttons Completion
|
||||
|
||||
## Summary
|
||||
|
||||
`/stock_portfolio` now checks SSI dividend events for held tickers after sending
|
||||
the portfolio. Relevant events receive owner-bound, expiring Apply buttons;
|
||||
successful checks with no events remain silent.
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] Replaceable SSI provider with pagination, local classification, exact
|
||||
share ratios, event-ID deduplication, and bounded date overlap.
|
||||
- [x] Per-ticker cursor checks that preserve concurrent portfolio writes.
|
||||
- [x] Opaque 24-hour pending actions bound to caller, chat, and message.
|
||||
- [x] Current-holding cash/share application with replay-safe event ledger.
|
||||
- [x] Position lifecycle binding that rejects buttons after full sale/reopen.
|
||||
- [x] Shared callback registration, visibility validation, and authorization.
|
||||
- [x] README and regression tests updated.
|
||||
|
||||
## Verification
|
||||
|
||||
| Gate | Result |
|
||||
|---|---|
|
||||
| Focused stock/modules tests | Passed |
|
||||
| Full `go test -count=1 ./...` | Passed |
|
||||
| Stock race and concurrency stress tests | Passed |
|
||||
| `go vet ./...` | Passed |
|
||||
| `golangci-lint run` | Passed, 0 issues |
|
||||
| `go build ./...` | Passed |
|
||||
| Independent final review | Passed, no blockers |
|
||||
|
||||
## Plan Sync
|
||||
|
||||
No active phase plan maps to this incremental request. Existing stock-dividend
|
||||
and cost-basis plans were already completed before this work and remain
|
||||
unchanged. Session task tracking is fully complete.
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- SSI iBoard is undocumented and best-effort.
|
||||
- SSI publication timestamps are day-granularity; a bounded overlap prevents
|
||||
same-day loss, while pending actions and applied IDs suppress duplicates.
|
||||
- The portfolio has no dated-lot ledger, so acceptance uses holdings at click
|
||||
time rather than legal record-date entitlement.
|
||||
- Manual dividend commands cannot attach SSI event IDs; users must not apply
|
||||
the same event both manually and through its button.
|
||||
|
||||
## Next Step
|
||||
|
||||
Commit only after explicit user approval.
|
||||
Reference in New Issue
Block a user