package lkgateway // lkfx.go — курсы валют к рублю для оценки валютных выпусков. // // Номинал «замещающих» и еврооблигаций в экспорте QUIK задан в валюте (FaceUnit = USD, EUR, // CNY), а цена — в процентах от него. Чтобы показать стоимость в рублях, нужен курс. В базе // экспорта курсов нет (валютный рынок не выгружается), поэтому берём официальный курс из // учётной системы: OD_VALUES_RATES с MARKET = 0 — сначала DEPO, затем SPECTRE, и берём более // свежий. Дата курса показывается клиенту: курс может отставать, и выдавать оценку за // сегодняшнюю нельзя. import ( "context" "database/sql" "strings" "sync" "time" ) // LKFxRate — курс валюты к рублю и дата, на которую он установлен. type LKFxRate struct { Rate float64 At time.Time } var fxCache = struct { mu sync.Mutex at time.Time m map[string]LKFxRate }{} // lkFxRates — курсы валют (ключ — код валюты в верхнем регистре), кэш на час. func lkFxRates(ctx context.Context, s FancyMSSQLSettings) map[string]LKFxRate { fxCache.mu.Lock() if fxCache.m != nil && time.Since(fxCache.at) < time.Hour { m := fxCache.m fxCache.mu.Unlock() return m } fxCache.mu.Unlock() out := map[string]LKFxRate{} if strings.TrimSpace(s.Server) == "" { return out } for _, db := range []string{s.DepoDB(), s.SpectreDB()} { conn, err := fancyOpen(s, db) if err != nil { continue } cctx, cancel := context.WithTimeout(ctx, 15*time.Second) rows, err := conn.QueryContext(cctx, `SELECT UPPER(LTRIM(RTRIM(v.SYSNAME))), r.OFICDATE, CAST(r.RATE AS float) FROM OD_VALUES_RATES r JOIN OD_VALUES v ON v.ID = r.VALUE_ID WHERE v.V_TYPE = 1 AND r.MARKET = 0 AND r.RATE > 0 AND r.OFICDATE = (SELECT MAX(x.OFICDATE) FROM OD_VALUES_RATES x WHERE x.VALUE_ID = r.VALUE_ID AND x.MARKET = 0 AND x.RATE > 0)`) if err == nil { for rows.Next() { var code string var at sql.NullTime var rate float64 if rows.Scan(&code, &at, &rate) != nil || rate <= 0 { continue } cur := LKFxRate{Rate: rate} if at.Valid { cur.At = at.Time } // из двух баз берём курс посвежее if old, ok := out[code]; !ok || cur.At.After(old.At) { out[code] = cur } } rows.Close() } cancel() conn.Close() } fxCache.mu.Lock() fxCache.m, fxCache.at = out, time.Now() fxCache.mu.Unlock() return out } // lkFxFor — курс валюты к рублю: рубль — единица, неизвестная валюта — 0 (оценивать нельзя). func lkFxFor(rates map[string]LKFxRate, cur string) LKFxRate { c := strings.ToUpper(strings.TrimSpace(cur)) switch c { case "", "SUR", "RUB", "RUR": return LKFxRate{Rate: 1} } return rates[c] }