
Baru-baru ini, artikel-artikel mulai bermunculan di pusat yang dimulai dengan cerita-cerita tentang waktu senggang tentang isolasi diri dan, sebagai akibatnya, troli-troli dari sebuah roti telah muncul. Mungkin administrasi harus mempertimbangkan untuk menambah hub baru - Self - isolation ..
Jadi saya mendapat waktu luang, yang saya curahkan untuk menganalisis transaksi saya di Tinkoff Investments. Ada 2 jenis orang: beberapa membangun array multidimensi dengan indah di kepala mereka, melalui mereka untuk siklus di Notebook IPython, sementara yang lain suka "merasakan" angka-angka, meletakkannya di rak-rak di Excel. Saya menghubungkan diri saya dengan kategori kedua, jadi saya dengan hati-hati memasukkan semua penawaran saya ke Google Sheets.
Di bawah potongan, saya akan memberi tahu Anda bagaimana saya mengotomatisasi rutin saya menggunakan Google Apps Script dan Tinkoff Investments API.
Sebelum kita sampai pada intinya, daftar istilah yang saya gunakan dalam artikel:
- TI - Investasi Tinkoff
- Instrumen adalah keamanan apa pun, seperti saham, obligasi, atau 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 . , )
Semua kode dan cara singkat diunggah ke github
Bagi pembaca yang ingin memulai investasi, tetapi tidak tahu harus mulai dari mana - saya dapat menyarankan kursus gratis dari Tinkoff Magazine https://journal.tinkoff.ru/pro/invest/ - pendek, informatif, dan mudah dipahami.
Dan ketika Anda membuka akun pialang di TI, menggunakan tautan saya, Anda akan menerima nilai saham hingga 20.000 rubel sebagai hadiah.
Terima kasih atas perhatian Anda.