feat: configure tbank integration

This commit is contained in:
Sergey Krylov 2026-06-16 22:15:31 +03:00
parent 526e5dc5f3
commit 7a8bc7c90a
5 changed files with 310 additions and 0 deletions

View File

@ -14,6 +14,13 @@ export default registerAs('app', () => ({
10, 10,
), ),
}, },
tbank: {
token: process.env.T_BANK_TOKEN || '',
baseUrl: process.env.T_BANK_BASE_URL || 'invest-public-api.tbank.ru:443',
appName: process.env.T_BANK_APP_NAME || 'ksv741.moex-vibe',
rateLimitPerSecond: parseInt(process.env.T_BANK_RATE_LIMIT_PER_SECOND || '5', 10),
requestTimeoutMs: parseInt(process.env.T_BANK_REQUEST_TIMEOUT_MS || '10000', 10),
},
cache: { cache: {
marketDataTtl: parseInt(process.env.CACHE_MARKET_DATA_TTL || '900', 10), marketDataTtl: parseInt(process.env.CACHE_MARKET_DATA_TTL || '900', 10),
historyTtl: parseInt(process.env.CACHE_HISTORY_TTL || '3600', 10), historyTtl: parseInt(process.env.CACHE_HISTORY_TTL || '3600', 10),
@ -21,6 +28,10 @@ export default registerAs('app', () => ({
securityTtl: parseInt(process.env.CACHE_SECURITY_TTL || '86400', 10), securityTtl: parseInt(process.env.CACHE_SECURITY_TTL || '86400', 10),
searchTtl: parseInt(process.env.CACHE_SEARCH_TTL || '3600', 10), searchTtl: parseInt(process.env.CACHE_SEARCH_TTL || '3600', 10),
dividendsTtl: parseInt(process.env.CACHE_DIVIDENDS_TTL || '86400', 10), dividendsTtl: parseInt(process.env.CACHE_DIVIDENDS_TTL || '86400', 10),
tbankAccountsTtl: parseInt(process.env.CACHE_TBANK_ACCOUNTS_TTL || '3600', 10),
tbankPortfolioTtl: parseInt(process.env.CACHE_TBANK_PORTFOLIO_TTL || '60', 10),
tbankOperationsTtl: parseInt(process.env.CACHE_TBANK_OPERATIONS_TTL || '300', 10),
tbankInstrumentTtl: parseInt(process.env.CACHE_TBANK_INSTRUMENT_TTL || '86400', 10),
}, },
auth: { auth: {
jwtSecret: process.env.JWT_SECRET || 'dev-jwt-secret-change-in-production', jwtSecret: process.env.JWT_SECRET || 'dev-jwt-secret-change-in-production',

View File

@ -0,0 +1,39 @@
import configuration from '../../config/configuration';
describe('T-Bank configuration', () => {
const originalEnv = process.env;
beforeEach(() => {
process.env = { ...originalEnv };
});
afterAll(() => {
process.env = originalEnv;
});
it('uses conservative defaults for T-Bank integration', () => {
delete process.env.T_BANK_BASE_URL;
delete process.env.T_BANK_RATE_LIMIT_PER_SECOND;
delete process.env.CACHE_TBANK_PORTFOLIO_TTL;
const config = configuration();
expect(config.tbank.baseUrl).toBe('invest-public-api.tbank.ru:443');
expect(config.tbank.rateLimitPerSecond).toBe(5);
expect(config.cache.tbankPortfolioTtl).toBe(60);
});
it('reads T-Bank token and TTL overrides from environment', () => {
process.env.T_BANK_TOKEN = 'secret-token';
process.env.T_BANK_BASE_URL = 'sandbox-invest-public-api.tbank.ru:443';
process.env.T_BANK_RATE_LIMIT_PER_SECOND = '2';
process.env.CACHE_TBANK_ACCOUNTS_TTL = '120';
const config = configuration();
expect(config.tbank.token).toBe('secret-token');
expect(config.tbank.baseUrl).toBe('sandbox-invest-public-api.tbank.ru:443');
expect(config.tbank.rateLimitPerSecond).toBe(2);
expect(config.cache.tbankAccountsTtl).toBe(120);
});
});

View File

@ -0,0 +1,22 @@
export const TBANK_PROTO_PACKAGE = 'tinkoff.public.invest.api.contract.v1';
export const TBANK_PROTO_FILES = {
users: 'users.proto',
operations: 'operations.proto',
instruments: 'instruments.proto',
} as const;
export const TBANK_ACCOUNT_TYPES = {
brokerage: 'ACCOUNT_TYPE_TINKOFF',
iis: 'ACCOUNT_TYPE_TINKOFF_IIS',
} as const;
export const TBANK_OPEN_ACCOUNT_STATUS = 'ACCOUNT_STATUS_OPEN';
export const TBANK_CACHE_KEYS = {
accounts: 'tbank:accounts',
portfolio: 'tbank:portfolio',
positions: 'tbank:positions',
operations: 'tbank:operations',
instrument: 'tbank:instrument',
} as const;

View File

@ -0,0 +1,90 @@
export type BrokerMoney = {
currency: string;
units: string;
nano: number;
value: number;
};
export type BrokerAccount = {
id: string;
type: 'brokerage' | 'iis';
name: string;
status: string;
openedAt: string | null;
accessLevel: string | null;
};
export type BrokerPosition = {
figi: string | null;
instrumentUid: string | null;
positionUid: string | null;
ticker: string | null;
classCode: string | null;
instrumentType: string | null;
name: string | null;
quantity: number | null;
blockedLots: number | null;
currentPrice: BrokerMoney | null;
currentValue: BrokerMoney | null;
averagePositionPrice: BrokerMoney | null;
expectedYieldPercent: number | null;
dailyYield: BrokerMoney | null;
};
export type BrokerPortfolio = {
account: BrokerAccount;
totals: {
shares: BrokerMoney | null;
bonds: BrokerMoney | null;
etf: BrokerMoney | null;
currencies: BrokerMoney | null;
futures: BrokerMoney | null;
options: BrokerMoney | null;
structuredProducts: BrokerMoney | null;
dfa: BrokerMoney | null;
portfolio: BrokerMoney | null;
};
yields: {
expectedPercent: number | null;
daily: BrokerMoney | null;
dailyPercent: number | null;
};
cash: BrokerMoney[];
blockedCash: BrokerMoney[];
positions: BrokerPosition[];
asOf: string;
};
export type BrokerOperationCategory = 'trade' | 'income' | 'tax' | 'fee' | 'transfer' | 'other';
export type BrokerOperation = {
cursor: string | null;
accountId: string;
id: string | null;
parentOperationId: string | null;
date: string | null;
type: string;
category: BrokerOperationCategory;
description: string | null;
state: string | null;
instrumentUid: string | null;
figi: string | null;
ticker: string | null;
classCode: string | null;
instrumentType: string | null;
payment: BrokerMoney | null;
price: BrokerMoney | null;
commission: BrokerMoney | null;
yield: BrokerMoney | null;
accruedInt: BrokerMoney | null;
quantity: number | null;
quantityDone: number | null;
};
export type BrokerOperationsPage = {
accountId: string;
items: BrokerOperation[];
nextCursor: string | null;
hasNext: boolean;
asOf: string;
};

View File

@ -0,0 +1,148 @@
export type TBankTimestamp = {
seconds?: number | string;
nanos?: number;
};
export type TBankMoneyValue = {
currency?: string;
units?: number | string;
nano?: number;
};
export type TBankQuotation = {
units?: number | string;
nano?: number;
};
export type TBankAccount = {
id: string;
type: string;
name?: string;
status: string;
openedDate?: TBankTimestamp;
closedDate?: TBankTimestamp;
accessLevel?: string;
};
export type TBankAccountsResponse = {
accounts?: TBankAccount[];
};
export type TBankPortfolioPosition = {
figi?: string;
instrumentType?: string;
quantity?: TBankQuotation;
averagePositionPrice?: TBankMoneyValue;
expectedYield?: TBankQuotation;
currentNkd?: TBankMoneyValue;
currentPrice?: TBankMoneyValue;
averagePositionPriceFifo?: TBankMoneyValue;
blocked?: boolean;
blockedLots?: TBankQuotation;
positionUid?: string;
instrumentUid?: string;
expectedYieldFifo?: TBankQuotation;
dailyYield?: TBankMoneyValue;
ticker?: string;
classCode?: string;
};
export type TBankPortfolioResponse = {
accountId?: string;
totalAmountShares?: TBankMoneyValue;
totalAmountBonds?: TBankMoneyValue;
totalAmountEtf?: TBankMoneyValue;
totalAmountCurrencies?: TBankMoneyValue;
totalAmountFutures?: TBankMoneyValue;
expectedYield?: TBankQuotation;
positions?: TBankPortfolioPosition[];
totalAmountOptions?: TBankMoneyValue;
totalAmountSp?: TBankMoneyValue;
totalAmountPortfolio?: TBankMoneyValue;
dailyYield?: TBankMoneyValue;
dailyYieldRelative?: TBankQuotation;
totalAmountDfa?: TBankMoneyValue;
};
export type TBankPositionsSecurity = {
figi?: string;
blocked?: string | number;
balance?: string | number;
positionUid?: string;
instrumentUid?: string;
ticker?: string;
classCode?: string;
exchangeBlocked?: boolean;
instrumentType?: string;
};
export type TBankPositionsResponse = {
accountId?: string;
money?: TBankMoneyValue[];
blocked?: TBankMoneyValue[];
securities?: TBankPositionsSecurity[];
};
export type TBankOperationTrade = {
num?: string;
date?: TBankTimestamp;
quantity?: string | number;
price?: TBankMoneyValue;
yield?: TBankMoneyValue;
yieldRelative?: TBankQuotation;
};
export type TBankOperationItem = {
cursor?: string;
brokerAccountId?: string;
id?: string;
parentOperationId?: string;
name?: string;
date?: TBankTimestamp;
type?: string;
description?: string;
state?: string;
instrumentUid?: string;
figi?: string;
instrumentType?: string;
instrumentKind?: string;
positionUid?: string;
ticker?: string;
classCode?: string;
payment?: TBankMoneyValue;
price?: TBankMoneyValue;
commission?: TBankMoneyValue;
yield?: TBankMoneyValue;
yieldRelative?: TBankQuotation;
accruedInt?: TBankMoneyValue;
quantity?: string | number;
quantityRest?: string | number;
quantityDone?: string | number;
tradesInfo?: { trades?: TBankOperationTrade[] };
};
export type TBankOperationsByCursorResponse = {
hasNext?: boolean;
nextCursor?: string;
items?: TBankOperationItem[];
};
export type TBankInstrument = {
figi?: string;
ticker?: string;
classCode?: string;
isin?: string;
lot?: number;
currency?: string;
name?: string;
exchange?: string;
instrumentType?: string;
uid?: string;
positionUid?: string;
assetUid?: string;
instrumentKind?: string;
};
export type TBankInstrumentResponse = {
instrument?: TBankInstrument;
};