
In letzter Zeit erscheinen auf dem Hub Artikel, die mit Geschichten über Freizeit zur Selbstisolation beginnen, und infolgedessen sind Obusse von einem Laib erschienen. Vielleicht sollte die Verwaltung in Betracht ziehen, einen neuen Hub hinzuzufügen - Selbstisolation .
So bekam ich Freizeit, die ich der Analyse meiner Transaktionen bei Tinkoff Investments widmete. Es gibt zwei Arten von Personen: Einige erstellen mehrdimensionale Arrays wunderschön in ihren Köpfen und gehen sie im IPython-Notizbuch zyklisch durch, während andere die Zahlen gerne "fühlen" und sie in Excel in die Regale stellen. Ich ordne mich der zweiten Kategorie zu und habe daher alle meine Angebote sorgfältig in Google Sheets eingegeben.
Im Folgenden werde ich Ihnen erläutern, wie ich meine Routine mithilfe von Google Apps Script und Tinkoff Investments API automatisiert habe.
Bevor wir zum Punkt kommen, ein kleines Glossar der Begriffe, die ich im Artikel verwende:
- TI - Tinkoff Investition
- Ein Instrument ist ein Wertpapier wie eine Aktie, eine Anleihe oder ein ETF.
- Ticker — ID . , , .
- Figi — Financial Instrument Global Identifier ( ). API- figi.
- — .
. - , — . : , . /, .
- , . . OvkHabr. , API, .
Google Apps Script
, , Google Sheets, Tools -> Script editor
, JavaScript.

OpenAPI
OpenAPI, swagger-ui
http- , . .
http .
?
- , figi
- ,
- ,
, :
class TinkoffClientclass TinkoffClient {
constructor(token) {
this.token = token
this.baseUrl = 'https://api-invest.tinkoff.ru/openapi/'
}
_makeApiCall(methodUrl) {
const url = this.baseUrl + methodUrl
Logger.log(`[API Call] ${url}`)
const params = {'escaping': false, 'headers': {'accept': 'application/json', "Authorization": `Bearer ${this.token}`}}
const response = UrlFetchApp.fetch(url, params)
if (response.getResponseCode() == 200)
return JSON.parse(response.getContentText())
}
getInstrumentByTicker(ticker) {
const url = `market/search/by-ticker?ticker=${ticker}`
const data = this._makeApiCall(url)
return data.payload.instruments[0]
}
getOrderbookByFigi(figi) {
const url = `market/orderbook?depth=1&figi=${figi}`
const data = this._makeApiCall(url)
return data.payload
}
getOperations(from, to, figi) {
const url = `operations?from=${from}&to=${to}&figi=${figi}`
const data = this._makeApiCall(url)
return data.payload.operations
}
}
const tinkoffClient = new TinkoffClient(OPENAPI_TOKEN)
- , . , yandex YNDX
.
Custom Functions
Google Sheets , AVERAGE
, SUM
, VLOOKUP
. , , . , , — .gs . , , , . , , , , .
getPriceByTicker, . (=getPriceByTicker("YNDX")
).
figi , , :
function _getFigiByTicker(ticker) {
const {figi} = tinkoffClient.getInstrumentByTicker(ticker)
return figi
}
function getPriceByTicker(ticker) {
const figi = _getFigiByTicker(ticker)
const {lastPrice} = tinkoffClient.getOrderbookByFigi(figi)
return lastPrice
}

! , , .
, , . , GAS :
- ,
function onEdit(e) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sheet.getRange('Z1').setValue(Math.random())
}
- , ,
getPriceByTicker
=getPriceByTicker("YNDX", Z1)
Cache Service
, 2 API-. , , figi . , Apps Script Cache Service. key-value , :
const CACHE = CacheService.getScriptCache()
function _getFigiByTicker(ticker) {
const cached = CACHE.get(ticker)
if (cached != null)
return cached
const {figi} = tinkoffClient.getInstrumentByTicker(ticker)
CACHE.put(ticker, figi)
return figi
}
. getTrades
, API, .
, , . , ISO 8601, API.
Figi _getFigiByTicker
, .
function getTrades(ticker, from, to) {
const figi = _getFigiByTicker(ticker)
if (!from) {
from = TRADING_START_AT.toISOString()
}
if (!to) {
const now = new Date()
to = new Date(now + MILLIS_PER_DAY)
to = to.toISOString()
}
const operations = tinkoffClient.getOperations(from, to, figi)
...
}
, Operation
:
Operation"operation": {
"id": "string",
"status": "Done",
"trades": [{
"tradeId": "string",
"date": "2019-08-19T18:38:33.131642+03:00",
"price": 0,
"quantity": 0
}],
"commission": {
"currency": "RUB",
"value": 0
},
"currency": "RUB",
"payment": 0,
"price": 0,
"quantity": 0,
"figi": "string",
"instrumentType": "Stock",
"isMarginCall": true,
"date": "2019-08-19T18:38:33.131642+03:00",
"operationType": "Buy"
}
- . , : 100 YNDX
2500₽, 40 2500₽, 60 2499₽. , OvkHabr, — trades
.
, , , .
,
function _calculateTrades(trades) {
let totalSum = 0
let totalQuantity = 0
for (let j in trades) {
const {quantity, price} = trades[j]
totalQuantity += quantity
totalSum += quantity * price
}
const weigthedPrice = totalSum / totalQuantity
return [totalQuantity, totalSum, weigthedPrice]
}
, custom , . , .
, . , , . , " " ( ) . , ( )
const values = [
["ID", "Date", "Operation", "Ticker", "Quantity", "Price", "Currency", "SUM", "Commission"],
]
for (let i=operations.length-1; i>=0; i--) {
const {operationType, status, trades, id, date, currency, commission} = operations[i]
if (operationType == "BrokerCommission" || status == "Decline")
continue
let [totalQuantity, totalSum, weigthedPrice] = _calculateTrades(trades)
if (operationType == "Buy") {
totalQuantity = -totalQuantity
totalSum = -totalSum
}
values.push([
id, isoToDate(date), operationType, ticker, totalQuantity, weigthedPrice, currency, totalSum, commission.value
])
}
values. :
getTradesfunction isoToDate(dateStr){
const str = dateStr.replace(/-/,'/').replace(/-/,'/').replace(/T/,' ').replace(/\+/,' \+').replace(/Z/,' +00')
return new Date(str)
}
function _calculateTrades(trades) {
let totalSum = 0
let totalQuantity = 0
for (let j in trades) {
const {quantity, price} = trades[j]
totalQuantity += quantity
totalSum += quantity * price
}
const weigthedPrice = totalSum / totalQuantity
return [totalQuantity, totalSum, weigthedPrice]
}
function getTrades(ticker, from, to) {
const figi = _getFigiByTicker(ticker)
if (!from) {
from = TRADING_START_AT.toISOString()
}
if (!to) {
const now = new Date()
to = new Date(now + MILLIS_PER_DAY)
to = to.toISOString()
}
const operations = tinkoffClient.getOperations(from, to, figi)
const values = [
["ID", "Date", "Operation", "Ticker", "Quantity", "Price", "Currency", "SUM", "Commission"],
]
for (let i=operations.length-1; i>=0; i--) {
const {operationType, status, trades, id, date, currency, commission} = operations[i]
if (operationType == "BrokerCommission" || status == "Decline")
continue
let [totalQuantity, totalSum, weigthedPrice] = _calculateTrades(trades)
if (operationType == "Buy") {
totalQuantity = -totalQuantity
totalSum = -totalSum
}
values.push([
id, isoToDate(date), operationType, ticker, totalQuantity, weigthedPrice, currency, totalSum, commission.value
])
}
return values
}
:

API , , Google Apps Script, Google Sheets . , )
Der gesamte Code und eine kurze Anleitung werden auf github hochgeladen
Für diejenigen Leser, die sich auf den Weg des Investierens machen möchten, aber nicht wissen, wo sie anfangen sollen - ich kann einen kostenlosen Kurs vom Tinkoff Magazine https://journal.tinkoff.ru/pro/invest/ empfehlen - es ist kurz, informativ und leicht zu verstehen.
Und wenn Sie ein Maklerkonto in TI eröffnen, erhalten Sie über meinen Link einen Anteil im Wert von bis zu 20.000 Rubel als Geschenk.
Vielen Dank für Ihre Aufmerksamkeit.