Кабинет: оценка портфеля по ценам QUIK и карточка бумаги из справочника депозитария
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+97
-5
@@ -53,6 +53,40 @@ func New(cfg Config) (*Server, error) {
|
||||
}
|
||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||
},
|
||||
"fmtmoney": func(f float64) string { // 1 234 567,89 — с разделителем разрядов
|
||||
neg := f < 0
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
whole := int64(f)
|
||||
cents := int64((f-float64(whole))*100 + 0.5)
|
||||
if cents >= 100 {
|
||||
whole, cents = whole+1, 0
|
||||
}
|
||||
d := strconv.FormatInt(whole, 10)
|
||||
var b strings.Builder
|
||||
for i, c := range d {
|
||||
if i > 0 && (len(d)-i)%3 == 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteRune(c)
|
||||
}
|
||||
out := b.String()
|
||||
if cents > 0 {
|
||||
out += "," + fmt.Sprintf("%02d", cents)
|
||||
}
|
||||
if neg {
|
||||
out = "−" + out
|
||||
}
|
||||
return out
|
||||
},
|
||||
"fmtpct": func(f float64) string {
|
||||
s := strconv.FormatFloat(f, 'f', 2, 64)
|
||||
if f > 0 {
|
||||
s = "+" + s
|
||||
}
|
||||
return strings.ReplaceAll(s, ".", ",") + " %"
|
||||
},
|
||||
"shortid": func(s string) string {
|
||||
if len(s) > 8 {
|
||||
return s[:8] + "…"
|
||||
@@ -126,6 +160,9 @@ type position struct {
|
||||
Available float64 `json:"available"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Transferable bool `json:"transferable"`
|
||||
Price float64 `json:"price"`
|
||||
Value float64 `json:"value"`
|
||||
ChangePct float64 `json:"change_pct"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
@@ -175,11 +212,42 @@ func claimsView(rows []claimRow) (active, done int) {
|
||||
|
||||
// portfolioResp — ответ /api/lk/portfolio.
|
||||
type portfolioResp struct {
|
||||
Contract string `json:"contract"`
|
||||
FullName string `json:"full_name"`
|
||||
Positions []position `json:"positions"`
|
||||
Source string `json:"source"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
Contract string `json:"contract"`
|
||||
FullName string `json:"full_name"`
|
||||
Positions []position `json:"positions"`
|
||||
Source string `json:"source"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
Priced int `json:"priced"`
|
||||
}
|
||||
|
||||
// securityCard — карточка бумаги из ядра.
|
||||
type securityCard struct {
|
||||
ISIN string `json:"isin"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Issuer string `json:"issuer"`
|
||||
RegNumber string `json:"reg_number"`
|
||||
CFI string `json:"cfi"`
|
||||
Face float64 `json:"face"`
|
||||
Maturity string `json:"maturity"`
|
||||
Accepted bool `json:"accepted"`
|
||||
QualOnly bool `json:"qual_only"`
|
||||
QualNote string `json:"qual_note"`
|
||||
Price float64 `json:"price"`
|
||||
PriceIn string `json:"price_in"`
|
||||
Board string `json:"board"`
|
||||
ChangePct float64 `json:"change_pct"`
|
||||
Position *position `json:"position"`
|
||||
Payments []struct {
|
||||
Date string `json:"date"`
|
||||
Kind string `json:"kind"`
|
||||
Amount float64 `json:"amount"`
|
||||
Percent float64 `json:"percent"`
|
||||
FixDate string `json:"fix_date"`
|
||||
Paid bool `json:"paid"`
|
||||
} `json:"payments"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// portfolio — реестр клиента из ядра.
|
||||
@@ -324,6 +392,7 @@ func (s *Server) Handler() http.Handler {
|
||||
"Nav": "b", "Contract": pf.Contract, "FullName": pf.FullName,
|
||||
"Positions": pos, "Claims": claims, "Flash": r.URL.Query().Get("m"),
|
||||
"Total": total, "Free": free, "Blocked": blocked, "Active": active,
|
||||
"Value": pf.TotalValue, "Priced": pf.Priced,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -349,9 +418,32 @@ func (s *Server) Handler() http.Handler {
|
||||
"Positions": pf.Positions, "Flash": r.URL.Query().Get("m"),
|
||||
"Source": pf.Source, "ReadOnly": pf.ReadOnly, "UpdatedAt": updated,
|
||||
"Total": total, "Free": free, "Blocked": blocked,
|
||||
"Value": pf.TotalValue, "Priced": pf.Priced,
|
||||
})
|
||||
})
|
||||
|
||||
// Карточка бумаги
|
||||
mux.HandleFunc("/security", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
isin := strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("isin")))
|
||||
if isin == "" {
|
||||
http.Redirect(w, r, "/portfolio", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var out struct {
|
||||
Security securityCard `json:"security"`
|
||||
}
|
||||
if err := bj.call(r.Context(), http.MethodGet, "/api/lk/security?isin="+url.QueryEscape(isin), sess, nil, &out); err != nil {
|
||||
s.render(w, r, "security", map[string]any{"Nav": "p", "Error": err.Error(), "ISIN": isin})
|
||||
return
|
||||
}
|
||||
s.render(w, r, "security", map[string]any{"Nav": "p", "S": out.Security, "ISIN": isin})
|
||||
})
|
||||
|
||||
// Профиль: договор, счета депо, доступ
|
||||
mux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
|
||||
Reference in New Issue
Block a user