Кабинет: верная оценка валютных выпусков и читаемые числа
- prices.go: у выпуска берётся валюта номинала (FaceUnit); «замещающие» и еврооблигации котируются в процентах от валютного номинала, поэтому тело переводится в рубли по курсу, НКД экспорт отдаёт уже в рублях. Без курса оценка не выдумывается - lkfx.go: официальные курсы валют из учётной системы (OD_VALUES_RATES, MARKET=0; DEPO, затем SPECTRE — берём свежее), кэш на час; дата курса показывается под таблицей - БР-719 онлайн: было 17 млн ₽ (валютные считались рублёвыми), стало 522 млн ₽ — сходится с архивом брокерского учёта (НОВАТЭК 001Р-02: 162,65 млн против 162,32 млн на дату среза) - вёрстка: отступ между числовыми колонками (числа больше не слипаются), больше воздуха между показателями в шапке, кнопка «Новое поручение» компактнее, цены от 10 — два знака - структура портфеля: плитки с долей и суммой вместо строки в одну линию; блок денег показывается, только когда валют несколько Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -151,6 +152,7 @@ func lkOnlinePortfolio(ctx context.Context, rc *RuntimeConfig, contract string)
|
||||
}
|
||||
}
|
||||
prices := lkPrices(ctx, rc, isins)
|
||||
fxUsed := map[string]SecurityPrice{}
|
||||
|
||||
for _, r := range pos {
|
||||
s := secByCode[r.code]
|
||||
@@ -161,10 +163,19 @@ func lkOnlinePortfolio(ctx context.Context, rc *RuntimeConfig, contract string)
|
||||
if px, ok := prices[s.isin]; ok && px.Price > 0 {
|
||||
lp.Price, lp.InPct, lp.DayPct = px.Price, px.InPct, px.ChangePc
|
||||
lp.Value = px.Value(r.qty)
|
||||
out.Priced++
|
||||
if r.avg > 0 {
|
||||
if lp.Value > 0 {
|
||||
out.Priced++
|
||||
}
|
||||
switch {
|
||||
case lp.Value <= 0 && px.FaceCur != "":
|
||||
lp.Note = "номинал в " + px.FaceCur + ": нет курса для оценки"
|
||||
case px.FX > 0 && px.FX != 1:
|
||||
lp.Note = "номинал в " + px.FaceCur
|
||||
fxUsed[px.FaceCur] = px // курс покажем одной строкой под таблицей
|
||||
}
|
||||
if r.avg > 0 && lp.Value > 0 {
|
||||
// средняя цена в тех же единицах, что и котировка: для облигаций — процент номинала
|
||||
cost := SecurityPrice{Price: r.avg, Face: px.Face, InPct: px.InPct}
|
||||
cost := SecurityPrice{Price: r.avg, Face: px.Face, InPct: px.InPct, FaceCur: px.FaceCur, FX: px.FX}
|
||||
lp.Cost = cost.Value(r.qty)
|
||||
lp.PnL = lp.Value - lp.Cost
|
||||
if lp.Cost != 0 {
|
||||
@@ -203,10 +214,25 @@ func lkOnlinePortfolio(ctx context.Context, rc *RuntimeConfig, contract string)
|
||||
if !asOf.IsZero() {
|
||||
out.AsOf = asOf.Format("02.01.2006")
|
||||
}
|
||||
if len(fxUsed) > 0 {
|
||||
var parts []string
|
||||
for cur, px := range fxUsed {
|
||||
p := cur + " " + fmtRate(px.FX) + " ₽"
|
||||
if !px.FXAt.IsZero() {
|
||||
p += " от " + px.FXAt.Format("02.01.2006")
|
||||
}
|
||||
parts = append(parts, p)
|
||||
}
|
||||
sort.Strings(parts)
|
||||
out.Note = "выпуски с валютным номиналом пересчитаны по курсу " + strings.Join(parts, ", ")
|
||||
}
|
||||
lkPortfolioTotals(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// fmtRate — курс валюты с двумя знаками.
|
||||
func fmtRate(v float64) string { return strconv.FormatFloat(v, 'f', 2, 64) }
|
||||
|
||||
// lkPortfolioTotals — итоги и доли.
|
||||
func lkPortfolioTotals(p *LKPortfolio) {
|
||||
p.Securities, p.Cost, p.PnL = 0, 0, 0
|
||||
|
||||
Reference in New Issue
Block a user