Кабинет: структура портфеля и ближайшие выплаты за 90 дней (купоны, оферты, погашения, дивиденды)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
zuevav
2026-09-18 16:02:48 +03:00
parent c32df3752e
commit c32f399acc
5 changed files with 231 additions and 6 deletions
+62 -3
View File
@@ -425,6 +425,58 @@ type lkPosition struct {
Note string `json:"note"`
}
// lkUpcoming — ожидаемая выплата по бумаге портфеля.
type lkUpcoming struct {
Date string `json:"date"`
ISIN string `json:"isin"`
Security string `json:"security"`
Kind string `json:"kind"`
PerUnit float64 `json:"per_unit"`
Quantity float64 `json:"quantity"`
Total float64 `json:"total"`
Percent float64 `json:"percent"`
}
// lkSegment — доля вида активов в портфеле для полосы структуры.
type lkSegment struct {
Kind string
Value float64
Share float64
Color string
}
// portfolioStructure — структура портфеля по видам активов (бумаги по виду + деньги).
func portfolioStructure(p lkPortfolio) []lkSegment {
if p.Total <= 0 {
return nil
}
colors := map[string]string{"акция": "#e8a33d", "облигация": "#4f7cac", "фонд": "#5f9e7a", "пай": "#5f9e7a", "депозитарная расписка": "#b07aa1", "деньги": "#8e8879"}
byKind := map[string]float64{}
for _, pos := range p.Positions {
k := pos.Kind
if k == "" || k == "бумага" {
k = "прочее"
}
byKind[k] += pos.Value
}
if p.CashRUB > 0 {
byKind["деньги"] += p.CashRUB
}
var out []lkSegment
for k, v := range byKind {
if v <= 0 {
continue
}
c := colors[k]
if c == "" {
c = "#c9c3b4"
}
out = append(out, lkSegment{Kind: k, Value: v, Share: v / p.Total * 100, Color: c})
}
sort.Slice(out, func(i, j int) bool { return out[i].Value > out[j].Value })
return out
}
// lkOpRow — операция по брокерскому счёту (ответ /api/lk/ops).
type lkOpRow struct {
Date string `json:"date"`
@@ -706,9 +758,10 @@ func (s *Server) buildHandler() http.Handler {
q.Set("date", date)
}
var out struct {
Portfolio lkPortfolio `json:"portfolio"`
Contract string `json:"contract"`
FullName string `json:"full_name"`
Portfolio lkPortfolio `json:"portfolio"`
Contract string `json:"contract"`
FullName string `json:"full_name"`
Upcoming []lkUpcoming `json:"upcoming"`
}
path := "/api/lk/portfolio2"
if len(q) > 0 {
@@ -722,6 +775,12 @@ func (s *Server) buildHandler() http.Handler {
return
}
data["P"], data["Contract"], data["FullName"] = out.Portfolio, out.Contract, out.FullName
data["Structure"], data["Upcoming"] = portfolioStructure(out.Portfolio), out.Upcoming
var upTotal float64
for _, u := range out.Upcoming {
upTotal += u.Total
}
data["UpcomingTotal"] = upTotal
s.render(w, r, "portfolio", data)
})