t-tech-investments 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/rules/git-flow.mdc +49 -5
- data/.cursor/rules/less-code.mdc +6 -0
- data/.cursor/rules/only-important-steps.mdc +5 -0
- data/.cursor/rules/sdk-architecture.mdc +31 -0
- data/.cursor/rules/sdk-code-standards.mdc +22 -0
- data/.cursor/rules/sdk-patterns.mdc +22 -0
- data/.cursor/rules/sdk-testing.mdc +19 -0
- data/CHANGELOG.md +14 -0
- data/README.md +320 -7
- data/Rakefile +4 -0
- data/contracts.lock +6 -0
- data/lib/t/tech/investments/client.rb +54 -0
- data/lib/t/tech/investments/coercers.rb +171 -0
- data/lib/t/tech/investments/configuration.rb +74 -0
- data/lib/t/tech/investments/errors.rb +27 -0
- data/lib/t/tech/investments/proto/common_pb.rb +42 -0
- data/lib/t/tech/investments/proto/google/api/field_behavior_pb.rb +19 -0
- data/lib/t/tech/investments/proto/instruments_pb.rb +157 -0
- data/lib/t/tech/investments/proto/instruments_services_pb.rb +115 -0
- data/lib/t/tech/investments/proto/marketdata_pb.rb +99 -0
- data/lib/t/tech/investments/proto/marketdata_services_pb.rb +68 -0
- data/lib/t/tech/investments/proto/operations_pb.rb +78 -0
- data/lib/t/tech/investments/proto/operations_services_pb.rb +67 -0
- data/lib/t/tech/investments/proto/orders_pb.rb +64 -0
- data/lib/t/tech/investments/proto/orders_services_pb.rb +69 -0
- data/lib/t/tech/investments/proto/sandbox_pb.rb +37 -0
- data/lib/t/tech/investments/proto/sandbox_services_pb.rb +75 -0
- data/lib/t/tech/investments/proto/signals_pb.rb +37 -0
- data/lib/t/tech/investments/proto/signals_services_pb.rb +36 -0
- data/lib/t/tech/investments/proto/stoporders_pb.rb +45 -0
- data/lib/t/tech/investments/proto/stoporders_services_pb.rb +38 -0
- data/lib/t/tech/investments/proto/users_pb.rb +47 -0
- data/lib/t/tech/investments/proto/users_services_pb.rb +51 -0
- data/lib/t/tech/investments/proto_loader.rb +31 -0
- data/lib/t/tech/investments/services/contracts/market_data_stream_contract.rb +51 -0
- data/lib/t/tech/investments/services/mappers/market_data_event_mapper.rb +68 -0
- data/lib/t/tech/investments/services/market_data_facade.rb +46 -0
- data/lib/t/tech/investments/services/registry.rb +80 -0
- data/lib/t/tech/investments/services/stores/local_subscription_store.rb +54 -0
- data/lib/t/tech/investments/services/stores/subscription.rb +82 -0
- data/lib/t/tech/investments/services/stream/market_data_kinds.rb +24 -0
- data/lib/t/tech/investments/services/stream/market_data_stream_session.rb +131 -0
- data/lib/t/tech/investments/services/stream/subscription_reconciler.rb +46 -0
- data/lib/t/tech/investments/services/unary_adapter.rb +64 -0
- data/lib/t/tech/investments/services.rb +12 -0
- data/lib/t/tech/investments/transport.rb +137 -0
- data/lib/t/tech/investments/version.rb +1 -1
- data/lib/t/tech/investments.rb +30 -1
- data/script/regenerate_proto +119 -0
- data/sig/t/tech/investments.rbs +154 -1
- metadata +75 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20123e15a4902951a5ee22661123c8a69ec7a55573203f505ec3f2ffd0016fc4
|
|
4
|
+
data.tar.gz: 24cd3848684453f36079b82c8398c9f69e8d29d422375e6396654b2b55eadd09
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c2ab86b84b0303698ccb523c981e1437da6d0efb7a111971f7682db44e8bb855c26b1582a977b047ff2c111683f718fcd08ddb88956df32745fa620de39191d
|
|
7
|
+
data.tar.gz: 63a2b49c538d2b9c95f204e517325630de7ac26f9e5397002a81cf8cc6f15a991f99904195241bbbc21f84faa8324a688a0bcf8c3a6664d9bb68dc42ece6143c
|
data/.cursor/rules/git-flow.mdc
CHANGED
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: >
|
|
3
|
+
Правила следования Git Flow для разработки торговой системы.
|
|
4
|
+
Определяет модель веток, правила коммитов и merge-стратегии.
|
|
5
|
+
|
|
6
|
+
globs:
|
|
7
|
+
- "**/*"
|
|
8
|
+
|
|
3
9
|
alwaysApply: true
|
|
4
10
|
---
|
|
11
|
+
В проекте используется Git Flow.
|
|
12
|
+
|
|
13
|
+
Основные ветки:
|
|
14
|
+
- main — стабильная ветка, всегда в рабочем состоянии.
|
|
15
|
+
- develop — основная ветка разработки.
|
|
16
|
+
|
|
17
|
+
Рабочие ветки:
|
|
18
|
+
- feature/* — разработка новых фич и модулей
|
|
19
|
+
- bugfix/* — исправления ошибок
|
|
20
|
+
- hotfix/* — срочные исправления для main
|
|
21
|
+
- release/* — подготовка релиза
|
|
22
|
+
|
|
23
|
+
Правила работы:
|
|
24
|
+
- Прямая разработка в main запрещена.
|
|
25
|
+
- Новые фичи всегда начинаются от develop.
|
|
26
|
+
- Feature-ветки мержатся обратно только в develop.
|
|
27
|
+
- Hotfix-ветки начинаются от main и мержатся в main и develop.
|
|
28
|
+
- Release-ветки используются для стабилизации перед релизом.
|
|
29
|
+
|
|
30
|
+
Коммиты:
|
|
31
|
+
- Коммиты атомарные и логически завершённые.
|
|
32
|
+
- Один коммит — одно логическое изменение.
|
|
33
|
+
- Сообщения коммитов описывают ЧТО и ЗАЧЕМ, а не КАК.
|
|
5
34
|
|
|
6
|
-
|
|
35
|
+
Рекомендованный формат commit message:
|
|
36
|
+
- feat: добавление новой функциональности
|
|
37
|
+
- fix: исправление ошибки
|
|
38
|
+
- refactor: рефакторинг без изменения поведения
|
|
39
|
+
- chore: инфраструктурные изменения
|
|
40
|
+
- test: добавление или изменение тестов
|
|
41
|
+
- docs: документация
|
|
7
42
|
|
|
8
|
-
|
|
43
|
+
Примеры:
|
|
44
|
+
- feat(strategy): add breakout strategy
|
|
45
|
+
- fix(risk): prevent position size overflow
|
|
46
|
+
- refactor(execution): simplify order state machine
|
|
9
47
|
|
|
10
|
-
|
|
48
|
+
Merge:
|
|
49
|
+
- Предпочтителен merge commit или squash (осознанно).
|
|
50
|
+
- Избегать rebase на общих ветках (develop, main).
|
|
51
|
+
- Конфликты решаются до merge, не после.
|
|
11
52
|
|
|
12
|
-
|
|
53
|
+
Cursor должен:
|
|
54
|
+
- учитывать текущую ветку при генерации изменений
|
|
55
|
+
- не предлагать изменения напрямую в main
|
|
56
|
+
- придерживаться Git Flow при создании новых задач и файлов
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Пожалуйста, решите эту задачу с минимально возможным количеством строк кода.
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
Пожалуйста, решите эту задачу с минимально возможным количеством строк кода.
|
|
6
|
+
Убедитесь, что результат функционально эквивалентен исходной цели и проходит существующие тесты или сценарии использовани
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Минимальная архитектура SDK: слои, зависимости, контракты
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Архитектура SDK (минимум правил)
|
|
7
|
+
|
|
8
|
+
## Слои
|
|
9
|
+
|
|
10
|
+
- `Client` (facade) → `Services` → `Transport` (gRPC)
|
|
11
|
+
- `Services` подслои: `contracts` (proto-обвязка), `mappers`, `stores`, `stream`
|
|
12
|
+
- Вспомогательные: `Coercers/Mappers`, `Errors`, `Proto` (сгенерёнка)
|
|
13
|
+
|
|
14
|
+
## Зависимости (жёсткие запреты)
|
|
15
|
+
|
|
16
|
+
- `Services` не вызывают gRPC stub напрямую — только через `Transport`.
|
|
17
|
+
- Публичный API не возвращает protobuf по умолчанию (только `raw:`/debug, если нужен).
|
|
18
|
+
- `Transport` без бизнес-логики; только: metadata, deadlines, retries-policy, error mapping.
|
|
19
|
+
- `Coercers/Mappers` — чистые и детерминированные (без IO/сети).
|
|
20
|
+
|
|
21
|
+
## Контракты (invest-contracts)
|
|
22
|
+
|
|
23
|
+
- Контракты **пинуем по tag/SHA** (не `master`), храним в `contracts.lock`.
|
|
24
|
+
- Сгенерированный Ruby protobuf/gRPC код **коммитим** в `lib/**/proto/**` (установка гема без `protoc`).
|
|
25
|
+
- Обновление контрактов обязано включать: pin → реген → тест “все RPC экспонированы”.
|
|
26
|
+
|
|
27
|
+
## T‑Invest инварианты (transport обязан)
|
|
28
|
+
|
|
29
|
+
- metadata каждого запроса: `Authorization: Bearer <token>`; `x-app-name` (если задан).
|
|
30
|
+
- endpoints по умолчанию (конфигурируемо): prod/sandbox.
|
|
31
|
+
- `x-tracking-id`: извлекать из unary headers и прокидывать в ошибки/логи; для stream — логировать tracking id из статусных сообщений подписки.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Минимальные стандарты кода/PR для SDK
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Код и процесс (минимум правил)
|
|
7
|
+
|
|
8
|
+
## Стиль/качество
|
|
9
|
+
|
|
10
|
+
- Ruby **>= 3.2**, стиль — через `rubocop` (double quotes и т.д.).
|
|
11
|
+
- `rubocop` и `rspec` обязаны проходить.
|
|
12
|
+
- Не логировать секреты (token/credentials).
|
|
13
|
+
- Protobuf наружу не экспонировать по умолчанию (только `raw:`/debug).
|
|
14
|
+
|
|
15
|
+
## Контракты
|
|
16
|
+
|
|
17
|
+
- Любое обновление `invest-contracts` включает: обновить pin tag/SHA → реген `lib/**/proto/**` → golden test “все RPC экспонированы”.
|
|
18
|
+
|
|
19
|
+
## Git/PR
|
|
20
|
+
|
|
21
|
+
- Следовать git-flow из `.cursor/rules/git-flow.mdc` (feature от `develop` → `develop`).
|
|
22
|
+
- PR: кратко “что/зачем” + тест-план + breaking/non-breaking для публичного Ruby API.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Минимальные паттерны: auto-expose RPC, coercion, streaming API
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Паттерны (минимум правил)
|
|
7
|
+
|
|
8
|
+
## 1) “Все методы реализованы”
|
|
9
|
+
|
|
10
|
+
- RPC **экспонируются автоматически** из descriptor’ов/Stub’ов (метапрограммирование).
|
|
11
|
+
- Нельзя вручную поддерживать списки методов или дублировать proto-структуру в коде.
|
|
12
|
+
|
|
13
|
+
## 2) Преобразования Ruby ↔ protobuf
|
|
14
|
+
|
|
15
|
+
- Вход в unary/stream управление: Ruby hash/kwargs → protobuf message через reflection (descriptor).
|
|
16
|
+
- Выход наружу: protobuf response → Ruby-friendly hash/DTO; protobuf наружу только `raw:`/debug.
|
|
17
|
+
|
|
18
|
+
## 3) Streaming (block/yield)
|
|
19
|
+
|
|
20
|
+
- Streaming API наружу: `client.market_data.stream { |s| s.subscribe_*; s.each_event { |event| ... } }`.
|
|
21
|
+
- Внутри: bidi stream + очередь исходящих команд; server ping игнорируется.
|
|
22
|
+
- Для MarketDataStream: reconciliation через `GetMySubscriptions` (рассинхрон → resubscribe; пусто → restart).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Тесты без сети: golden checks, coercion, streaming через fake transport
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Тестирование (строго без сети)
|
|
7
|
+
|
|
8
|
+
- Любые тесты **без сетевых вызовов** (ни prod, ни sandbox).
|
|
9
|
+
- Тестируем только: преобразования, поведение SDK, соответствие контрактам и документации.
|
|
10
|
+
|
|
11
|
+
## Обязательные проверки
|
|
12
|
+
|
|
13
|
+
- **Golden test “все RPC экспонированы”**: после регенерации proto тест падает, если появился новый RPC, но SDK его не выставил.
|
|
14
|
+
- **Coercers/Mappers**: reflection-based Hash/kwargs ↔ protobuf (enum/time/money/oneof).
|
|
15
|
+
- **Transport**: `GRPC::BadStatus` → `Errors::*`, извлечение `x-tracking-id` (unary headers), redaction секретов.
|
|
16
|
+
- **Streaming**: тестировать через fake transport + controlled `Enumerator`:
|
|
17
|
+
- ping игнорируется
|
|
18
|
+
- статусы подписки (включая `TOO_MANY_REQUESTS`) мапятся в события/ошибки
|
|
19
|
+
- reconciliation через `GetMySubscriptions` (рассинхрон → resubscribe; пусто → restart)
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-02-06
|
|
4
|
+
|
|
5
|
+
- Client: services exposed via Registry (method_missing), no hardcoded list; facades (e.g. market_data) discovered dynamically
|
|
6
|
+
- ProtoLoader: load proto files dynamically from directory; common_pb first, then rest + services
|
|
7
|
+
- Market data stream: session stop/close, reconciliation (GetMySubscriptions → restart + resubscribe on drift), max_restarts limit and StreamRestartLimitError
|
|
8
|
+
- Market data: contract/mapper/store/reconciler split; subscription kinds single source of truth; candle interval validation
|
|
9
|
+
- Transport: allow custom CA certs for gRPC
|
|
10
|
+
- Dev: SimpleCov coverage (COVERAGE=1), codecov badge in README
|
|
11
|
+
- Docs: README stream stopping, reconciliation example, installation and usage
|
|
12
|
+
|
|
13
|
+
## [0.1.1] - 2026-02-05
|
|
14
|
+
|
|
15
|
+
- Docs: update README with correct links and env options
|
|
16
|
+
|
|
3
17
|
## [0.1.0] - 2026-02-05
|
|
4
18
|
|
|
5
19
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
# T::Tech::Investments
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/t-tech-investments)
|
|
4
|
+
[](https://www.ruby-lang.org/)
|
|
5
|
+
[](https://github.com/naveroot/t-tech-investments/actions/workflows/main.yml)
|
|
6
|
+
[](https://codecov.io/gh/naveroot/t-tech-investments)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Ruby wrapper for the gRPC API of [T-Bank Invest](https://developer.tbank.ru/invest/intro/intro/). Contracts (proto files) are sourced from [investAPI](https://github.com/RussianInvestments/investAPI) and pinned by SHA in `contracts.lock`; generated protobuf/gRPC code is committed to `lib/**/proto/**`. The gem provides a convenient Ruby client for accounts, instruments, orders, and market data streams. Available `Client` methods depend on the loaded contract; after regenerating proto, the set of methods can change.
|
|
9
|
+
|
|
10
|
+
This gem integrates T-Bank investment services into Ruby apps: typed calls instead of manual gRPC handling, consistent API style, and adherence to official contracts.
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Minimal requirements (Prod / Sandbox)](#minimal-requirements-prod--sandbox)
|
|
16
|
+
- [Usage](#usage)
|
|
17
|
+
- [Development](#development)
|
|
18
|
+
- [Contributing](#contributing)
|
|
19
|
+
- [License](#license)
|
|
20
|
+
- [Code of Conduct](#code-of-conduct)
|
|
21
|
+
|
|
22
|
+
## Key features
|
|
23
|
+
|
|
24
|
+
- Typed unary RPC wrappers for all services
|
|
25
|
+
- MarketData bidirectional stream with Ruby-friendly events
|
|
26
|
+
- Hash/kwargs ↔ protobuf coercers (reflection-based)
|
|
27
|
+
- Config snapshot per client (safe for background jobs)
|
|
28
|
+
- Error mapping with tracking id extraction
|
|
6
29
|
|
|
7
30
|
## Installation
|
|
8
31
|
|
|
9
|
-
Install the gem and add to the application's Gemfile by executing:
|
|
32
|
+
Install the gem and add it to the application's Gemfile by executing:
|
|
10
33
|
|
|
11
34
|
```bash
|
|
12
35
|
bundle add t-tech-investments
|
|
@@ -18,19 +41,309 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
18
41
|
gem install t-tech-investments
|
|
19
42
|
```
|
|
20
43
|
|
|
44
|
+
## Minimal requirements (Prod / Sandbox)
|
|
45
|
+
|
|
46
|
+
Minimum required to start using T-Invest API:
|
|
47
|
+
|
|
48
|
+
- **Ruby**: >= 3.2
|
|
49
|
+
- **Access token**: obtain a token in T-Bank Dev Portal (“Getting started”) — [docs](https://developer.tbank.ru/invest/intro/intro/)
|
|
50
|
+
- **Environment**: use a **prod** or **sandbox** token
|
|
51
|
+
- **gRPC endpoint** (from docs):
|
|
52
|
+
- **Prod**: `invest-public-api.tbank.ru:443`
|
|
53
|
+
- **Sandbox**: `sandbox-invest-public-api.tbank.ru:443`
|
|
54
|
+
|
|
55
|
+
Recommended environment-variable configuration (names follow this gem's convention):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Prod
|
|
59
|
+
export TINVEST_TOKEN="..."
|
|
60
|
+
export TINVEST_ENDPOINT="invest-public-api.tbank.ru:443"
|
|
61
|
+
|
|
62
|
+
# or Sandbox
|
|
63
|
+
export TINVEST_TOKEN="..."
|
|
64
|
+
export TINVEST_ENDPOINT="sandbox-invest-public-api.tbank.ru:443"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Optional (ENV-first configuration):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Application identifier (x-app-name)
|
|
71
|
+
export TINVEST_APP_NAME="my-app"
|
|
72
|
+
|
|
73
|
+
# Default timeout (seconds)
|
|
74
|
+
export TINVEST_TIMEOUT="10"
|
|
75
|
+
|
|
76
|
+
# Path to CA certs PEM (custom environments)
|
|
77
|
+
export TINVEST_SSL_CA_CERTS="/path/to/ca.pem"
|
|
78
|
+
```
|
|
79
|
+
|
|
21
80
|
## Usage
|
|
22
81
|
|
|
23
|
-
|
|
82
|
+
The gem provides a global default configuration plus per-client overrides.
|
|
83
|
+
|
|
84
|
+
### Configuration
|
|
85
|
+
|
|
86
|
+
#### ENV-first (quick start)
|
|
87
|
+
|
|
88
|
+
**Given** you have a token and selected environment (prod/sandbox)
|
|
89
|
+
**When** you set `TINVEST_TOKEN` and `TINVEST_ENDPOINT`
|
|
90
|
+
**Then** you can create a client without additional setup
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
require "t/tech/investments"
|
|
94
|
+
|
|
95
|
+
client = T::Tech::Investments.client
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If you installed the gem via `gem install` and see `uninitialized constant T`, make sure your script requires the gem:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
require "t/tech/investments"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Explicit configuration (Rails / monolith)
|
|
105
|
+
|
|
106
|
+
**Given** you want centralized configuration
|
|
107
|
+
**When** you call `T::Tech::Investments.configure`
|
|
108
|
+
**Then** new clients use these values by default
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
T::Tech::Investments.configure do |c|
|
|
112
|
+
c.token = ENV.fetch("TINVEST_TOKEN")
|
|
113
|
+
c.endpoint = ENV.fetch("TINVEST_ENDPOINT", "invest-public-api.tbank.ru:443") # or sandbox
|
|
114
|
+
c.timeout = 10
|
|
115
|
+
|
|
116
|
+
# optional
|
|
117
|
+
c.app_name = "my-app"
|
|
118
|
+
c.logger = Logger.new($stdout)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
client = T::Tech::Investments.client
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Override for a single client
|
|
125
|
+
|
|
126
|
+
**Given** you need different settings for one call/process
|
|
127
|
+
**When** you pass parameters to `client(...)`
|
|
128
|
+
**Then** they apply only to that instance
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
client = T::Tech::Investments.client(
|
|
132
|
+
endpoint: "sandbox-invest-public-api.tbank.ru:443",
|
|
133
|
+
timeout: 2
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Configuration best practices
|
|
138
|
+
|
|
139
|
+
- **Validate token**: if missing/empty, raise `ConfigurationError` (or similar) with a clear message.
|
|
140
|
+
- **Do not log secrets**: token must not appear in logs or exceptions.
|
|
141
|
+
- **Snapshot config per client**: a created client should not pick up global config changes later (important for jobs/threads).
|
|
142
|
+
- **Prod vs Sandbox**: distinguish environments only by the endpoint from the docs:
|
|
143
|
+
- **Prod**: `invest-public-api.tbank.ru:443`
|
|
144
|
+
- **Sandbox**: `sandbox-invest-public-api.tbank.ru:443`
|
|
145
|
+
|
|
146
|
+
### Unary requests (data fetch)
|
|
147
|
+
|
|
148
|
+
Unary requests in T-Invest API are standard gRPC calls: “one request → one response”.
|
|
149
|
+
|
|
150
|
+
#### Authorization and headers
|
|
151
|
+
|
|
152
|
+
Per T-Invest API docs, the token is passed in **request metadata** as a header:
|
|
153
|
+
|
|
154
|
+
- **`authorization: Bearer <token>`**
|
|
155
|
+
|
|
156
|
+
Additionally (recommended for SDK) you can pass:
|
|
157
|
+
|
|
158
|
+
- **`x-app-name`** — app/SDK identifier for analytics (as recommended in the docs)
|
|
159
|
+
|
|
160
|
+
Source: [gRPC — Authorization / AppName / trackingId](https://developer.tbank.ru/invest/intro/developer/protocols/grpc/).
|
|
161
|
+
|
|
162
|
+
#### Example (common pattern)
|
|
163
|
+
|
|
164
|
+
**Given** `TINVEST_TOKEN` and `TINVEST_ENDPOINT` are configured
|
|
165
|
+
**When** you call a unary service method
|
|
166
|
+
**Then** you get a Hash (default) or raw protobuf with `raw: true`
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
client = T::Tech::Investments.client
|
|
170
|
+
|
|
171
|
+
# Accounts list (Hash with :accounts etc.)
|
|
172
|
+
accounts = client.users.get_accounts
|
|
173
|
+
# With params and options
|
|
174
|
+
accounts = client.users.get_accounts(status: :open)
|
|
175
|
+
|
|
176
|
+
# Candles for an instrument (from/to — Time or Hash)
|
|
177
|
+
candles = client.market_data.get_candles(
|
|
178
|
+
figi: "BBG004730N88",
|
|
179
|
+
from: Time.now - 3600,
|
|
180
|
+
to: Time.now,
|
|
181
|
+
interval: :candle_interval_1_min
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Raw protobuf (debugging)
|
|
185
|
+
raw_response = client.users.get_accounts(raw: true)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Timeouts (deadline)
|
|
189
|
+
|
|
190
|
+
For gRPC it is important to set a deadline/timeout to avoid hanging requests. Calls use `timeout` from configuration (or an override per client).
|
|
191
|
+
|
|
192
|
+
#### Tracking id (x-tracking-id)
|
|
193
|
+
|
|
194
|
+
Docs note that **`x-tracking-id` is included in unary responses**. This UUID should be provided to support when troubleshooting.
|
|
195
|
+
|
|
196
|
+
Recommended practice for the gem:
|
|
197
|
+
|
|
198
|
+
- log/propagate `x-tracking-id` on errors;
|
|
199
|
+
- optionally expose it via a response wrapper or instrumentation hooks.
|
|
200
|
+
|
|
201
|
+
### Streaming (MarketData)
|
|
202
|
+
|
|
203
|
+
Bidirectional stream: in the block you send subscriptions, then `each_event` yields events. Events are Hashes with `:type` and `:data`; **ping is ignored** by the SDK.
|
|
204
|
+
|
|
205
|
+
#### Correct usage
|
|
206
|
+
|
|
207
|
+
- Treat the stream as **long-lived** and set a higher `timeout` (or override per call).
|
|
208
|
+
- Enqueue subscriptions **before** calling `each_event`.
|
|
209
|
+
- Keep `each_event` running; break the block when you want to stop the stream.
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
client.market_data.stream(timeout: 300) do |s|
|
|
213
|
+
s.subscribe_candles(
|
|
214
|
+
subscription_action: :SUBSCRIPTION_ACTION_SUBSCRIBE,
|
|
215
|
+
instruments: [
|
|
216
|
+
{
|
|
217
|
+
figi: "BBG004730N88",
|
|
218
|
+
interval: :SUBSCRIPTION_INTERVAL_ONE_MINUTE
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
waiting_close: true,
|
|
222
|
+
candle_source_type: :CANDLE_SOURCE_EXCHANGE
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
s.each_event do |event|
|
|
226
|
+
p event
|
|
227
|
+
# break if you want to stop the stream
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Troubleshooting
|
|
233
|
+
|
|
234
|
+
If you see `SUBSCRIPTION_STATUS_SUBSCRIPTION_NOT_FOUND` or the server responds with an unsubscribe action, explicitly pass full enum values:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
client.market_data.stream(timeout: 300) do |s|
|
|
238
|
+
s.subscribe_candles(
|
|
239
|
+
subscription_action: :SUBSCRIPTION_ACTION_SUBSCRIBE,
|
|
240
|
+
instruments: [
|
|
241
|
+
{
|
|
242
|
+
figi: "BBG004730N88",
|
|
243
|
+
interval: :SUBSCRIPTION_INTERVAL_ONE_MINUTE
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
waiting_close: true,
|
|
247
|
+
candle_source_type: :CANDLE_SOURCE_EXCHANGE
|
|
248
|
+
)
|
|
249
|
+
s.each_event { |event| p event }
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Also ensure your **token and endpoint match the same environment** (prod vs sandbox).
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
client = T::Tech::Investments.client
|
|
257
|
+
|
|
258
|
+
client.market_data.stream do |s|
|
|
259
|
+
# 1) Subscriptions (subscription_action: :subscribe or :unsubscribe)
|
|
260
|
+
s.subscribe_candles(
|
|
261
|
+
subscription_action: :subscribe,
|
|
262
|
+
instruments: [{ figi: "BBG004730N88", interval: :subscription_interval_one_minute }],
|
|
263
|
+
waiting_close: true
|
|
264
|
+
)
|
|
265
|
+
s.subscribe_order_book(
|
|
266
|
+
subscription_action: :subscribe,
|
|
267
|
+
instruments: [{ figi: "BBG004730N88", depth: 10 }]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# 2) Read events (event[:type], event[:data])
|
|
271
|
+
s.each_event do |event|
|
|
272
|
+
case event[:type]
|
|
273
|
+
when :candle
|
|
274
|
+
puts "candle: #{event[:data].inspect}"
|
|
275
|
+
when :orderbook
|
|
276
|
+
puts "orderbook: #{event[:data].inspect}"
|
|
277
|
+
when :subscribe_candles_response, :subscribe_order_book_response
|
|
278
|
+
# subscription responses (tracking_id and statuses in event[:data])
|
|
279
|
+
warn "subscription: #{event[:data].inspect}"
|
|
280
|
+
when :trading_status, :last_price, :trade
|
|
281
|
+
# other types by contract
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Reconciliation: if subscriptions drift, call `s.get_my_subscriptions` and resubscribe or restart the stream as needed.
|
|
288
|
+
|
|
289
|
+
#### Stopping a stream (graceful)
|
|
290
|
+
|
|
291
|
+
If you need to stop a long-running stream from another thread or from a signal handler, call `stop`/`close` on the session to terminate the request stream cleanly:
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
session = nil
|
|
295
|
+
stream_thread = Thread.new do
|
|
296
|
+
client.market_data.stream(timeout: 300) do |s|
|
|
297
|
+
session = s
|
|
298
|
+
s.subscribe_candles(
|
|
299
|
+
subscription_action: :subscribe,
|
|
300
|
+
instruments: [{ figi: "BBG004730N88", interval: :subscription_interval_one_minute }]
|
|
301
|
+
)
|
|
302
|
+
s.each_event { |event| p event }
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# later...
|
|
307
|
+
session&.stop
|
|
308
|
+
stream_thread.join
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### Reconciliation example (server vs local subscriptions)
|
|
312
|
+
|
|
313
|
+
To detect drift, request server-side subscriptions and let the session reconcile:
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
client.market_data.stream(timeout: 300) do |s|
|
|
317
|
+
s.subscribe_candles(
|
|
318
|
+
subscription_action: :subscribe,
|
|
319
|
+
instruments: [{ figi: "BBG004730N88", interval: :subscription_interval_one_minute }]
|
|
320
|
+
)
|
|
321
|
+
s.get_my_subscriptions
|
|
322
|
+
|
|
323
|
+
s.each_event do |event|
|
|
324
|
+
case event[:type]
|
|
325
|
+
when :subscribe_candles_response
|
|
326
|
+
# local subscription responses
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Notes:
|
|
333
|
+
|
|
334
|
+
- Stream authorization uses metadata `authorization: Bearer <token>` (same as unary). See [gRPC — Authorization / AppName / trackingId](https://developer.tbank.ru/invest/intro/developer/protocols/grpc/).
|
|
335
|
+
- A single stream can send multiple subscription requests (candles, order book, trades, etc.) — each is enqueued separately.
|
|
336
|
+
- See [Market data service docs](https://developer.tbank.ru/invest/services/quotes/head-marketdata/).
|
|
24
337
|
|
|
25
338
|
## Development
|
|
26
339
|
|
|
27
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
340
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. To generate a coverage report (development only), run `COVERAGE=1 bundle exec rspec`; the report is written to `coverage/`. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
28
341
|
|
|
29
342
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
30
343
|
|
|
31
344
|
## Contributing
|
|
32
345
|
|
|
33
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
346
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/naveroot/t-tech-investments. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/naveroot/t-tech-investments/blob/main/CODE_OF_CONDUCT.md).
|
|
34
347
|
|
|
35
348
|
## License
|
|
36
349
|
|
|
@@ -38,4 +351,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
38
351
|
|
|
39
352
|
## Code of Conduct
|
|
40
353
|
|
|
41
|
-
Everyone interacting in the T::Tech::Investments project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
354
|
+
Everyone interacting in the T::Tech::Investments project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/naveroot/t-tech-investments/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
data/contracts.lock
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repository": "https://github.com/RussianInvestments/investAPI.git",
|
|
3
|
+
"ref": "3eaf23a25f598fe483c913184acdbd9132bc68d2",
|
|
4
|
+
"contracts_path": "src/docs/contracts",
|
|
5
|
+
"description": "invest-contracts pin (Release 1.43). Update ref then run script/regenerate_proto and golden test."
|
|
6
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module T
|
|
4
|
+
module Tech
|
|
5
|
+
module Investments
|
|
6
|
+
# Client facade: config snapshot, transport, and service adapters (users, instruments, etc.).
|
|
7
|
+
# Services expose unary RPCs via Transport + Coercers; RPCs are defined from proto descriptors.
|
|
8
|
+
class Client
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
11
|
+
def initialize(config_snapshot)
|
|
12
|
+
@config = config_snapshot.validate!
|
|
13
|
+
@transport = nil
|
|
14
|
+
@services = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def transport
|
|
18
|
+
@transport ||= Transport.new(config)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
|
22
|
+
if Services::Registry.public_service_names.include?(method_name) &&
|
|
23
|
+
args.empty? && kwargs.empty? && block.nil?
|
|
24
|
+
return service(method_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
31
|
+
Services::Registry.public_service_names.include?(method_name) || super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def service(name)
|
|
37
|
+
if (facade_class = Services::Registry.facade_class(name))
|
|
38
|
+
@facades ||= {}
|
|
39
|
+
return @facades[name] ||= begin
|
|
40
|
+
ProtoLoader.load!
|
|
41
|
+
unary = Services::UnaryAdapter.new(Services::Registry.service_class(name), transport)
|
|
42
|
+
facade_class.new(unary, transport)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@services[name] ||= begin
|
|
47
|
+
ProtoLoader.load!
|
|
48
|
+
Services::UnaryAdapter.new(Services::Registry.service_class(name), transport)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|