codex/tbank-broker-portfolios-design #15
@ -14,6 +14,13 @@ export default registerAs('app', () => ({
|
||||
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: {
|
||||
marketDataTtl: parseInt(process.env.CACHE_MARKET_DATA_TTL || '900', 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),
|
||||
searchTtl: parseInt(process.env.CACHE_SEARCH_TTL || '3600', 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: {
|
||||
jwtSecret: process.env.JWT_SECRET || 'dev-jwt-secret-change-in-production',
|
||||
|
||||
39
apps/backend/src/modules/tbank/tbank.config.spec.ts
Normal file
39
apps/backend/src/modules/tbank/tbank.config.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
22
apps/backend/src/modules/tbank/tbank.config.ts
Normal file
22
apps/backend/src/modules/tbank/tbank.config.ts
Normal 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;
|
||||
90
apps/backend/src/modules/tbank/types/broker.types.ts
Normal file
90
apps/backend/src/modules/tbank/types/broker.types.ts
Normal 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;
|
||||
};
|
||||
148
apps/backend/src/modules/tbank/types/tbank-proto.types.ts
Normal file
148
apps/backend/src/modules/tbank/types/tbank-proto.types.ts
Normal 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;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user