
рд╣рд╛рд▓ рд╣реА рдореЗрдВ, рд╣рдм рдкрд░ рд▓реЗрдЦ рджрд┐рдЦрд╛рдИ рджреЗрдиреЗ рд▓рдЧреЗ рд╣реИрдВ рдЬреЛ рдЖрддреНрдо-рдЕрд▓рдЧрд╛рд╡ рдкрд░ рдЦрд╛рд▓реА рд╕рдордп рдХреА рдХрд╣рд╛рдирд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рд╣реЛрддреЗ рд╣реИрдВ рдФрд░, рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рдПрдХ рдкрд╛рд╡ рд░реЛрдЯреА рд╕реЗ рдЯреНрд░реЙрд▓реАрдмрд╕ рджрд┐рдЦрд╛рдИ рджреЗрддреЗ рд╣реИрдВред рд╢рд╛рдпрдж рдкреНрд░рд╢рд╛рд╕рди рдХреЛ рдПрдХ рдирдпрд╛ рд╣рдм рдЬреЛрдбрд╝рдиреЗ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП - рд╕реНрд╡ - рдЕрд▓рдЧрд╛рд╡ ред
рдЗрд╕рд▓рд┐рдП рдореБрдЭреЗ рдЦрд╛рд▓реА рд╕рдордп рдорд┐рд▓рд╛, рдЬрд┐рд╕реЗ рдореИрдВрдиреЗ рдЯрд┐рдВрдХреЙрдл рдЗрдиреНрд╡реЗрд╕реНрдЯрдореЗрдВрдЯреНрд╕ рдореЗрдВ рдЕрдкрдиреЗ рд▓реЗрдирджреЗрди рдХрд╛ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдкрд┐рдд рдХрд┐рдпрд╛ред 2 рдкреНрд░рдХрд╛рд░ рдХреЗ рд▓реЛрдЧ рд╣реИрдВ: рдХреБрдЫ рдЕрдкрдиреЗ рд╕рд┐рд░ рдореЗрдВ рдЦреВрдмрд╕реВрд░рддреА рд╕реЗ рдмрд╣реБрдЖрдпрд╛рдореА рд╕рд░рдгрд┐рдпреЛрдВ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░рддреЗ рд╣реИрдВ, рдЙрди рдкрд░ рдЖрдИрдкреАрдереЙрди рдиреЛрдЯрдмреБрдХ рдореЗрдВ рдлреЙрд░-рд▓реВрдк рдореЗрдВ рдЬрд╛рддреЗ рд╣реИрдВ, рдЬрдмрдХрд┐ рдЕрдиреНрдп рд▓реЛрдЧ рд╕рдВрдЦреНрдпрд╛рдУрдВ рдХреЛ "рдорд╣рд╕реВрд╕" рдХрд░рдирд╛ рдкрд╕рдВрдж рдХрд░рддреЗ рд╣реИрдВ, рдЙрдиреНрд╣реЗрдВ рдПрдХреНрд╕реЗрд▓ рдореЗрдВ рдЕрд▓рдорд╛рд░рд┐рдпреЛрдВ рдкрд░ рд░рдЦрдирд╛ред рдореИрдВ рдЕрдкрдиреЗ рдЖрдк рдХреЛ рджреВрд╕рд░реА рд╢реНрд░реЗрдгреА рдореЗрдВ рд░рдЦрддрд╛ рд╣реВрдВ, рдЗрд╕рд▓рд┐рдП рдореИрдВрдиреЗ рд╕рд╛рд╡рдзрд╛рдиреАрдкреВрд░реНрд╡рдХ Google рд╢реАрдЯ рдореЗрдВ рдЕрдкрдиреЗ рд╕рднреА рд╕реМрджреЗ рджрд░реНрдЬ рдХрд┐рдПред
рдХрдЯ рдХреЗ рддрд╣рдд, рдореИрдВ рдЖрдкрдХреЛ рдмрддрд╛рддрд╛ рд╣реВрдВ рдХрд┐ рдореИрдВрдиреЗ Google Apps рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдФрд░ рдЯрд┐рдВрдХрдлреЙрдл рдЗрдВрд╡реЗрд╕реНрдЯрдореЗрдВрдЯ рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдиреА рджрд┐рдирдЪрд░реНрдпрд╛ рдХреЛ рдХреИрд╕реЗ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдХрд┐рдпрд╛ред
рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░ рдкрд╣реБрдБрдЪреЗрдВ, рд╢рдмреНрджреЛрдВ рдХреА рдПрдХ рдЫреЛрдЯреА рд╢рдмреНрджрд╛рд╡рд▓реА рдЬреЛ рдореИрдВ рд▓реЗрдЦ рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдБ:
- рдЯреАрдЖрдИ - рдЯрд┐рдВрдХреЙрдл рдирд┐рд╡реЗрд╢
- рдПрдХ рд╕рд╛рдзрди рдХрд┐рд╕реА рднреА рд╕реБрд░рдХреНрд╖рд╛, рдЬреИрд╕реЗ рдХрд┐ рд╕реНрдЯреЙрдХ, рдмреЙрдиреНрдб рдпрд╛ рдИрдЯреАрдПрдл рд╣реИред
- 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 . , )
рд╕рднреА рдХреЛрдб рдФрд░ рдПрдХ рдЫреЛрдЯрд╛ рдХреИрд╕реЗ-рдХреЛ рдЬреАрдереВрдм рдкрд░ рдЕрдкрд▓реЛрдб рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ
рдЙрди рдкрд╛рдардХреЛрдВ рдХреЗ рд▓рд┐рдП рдЬреЛ рдирд┐рд╡реЗрд╢ рдХреА рд░рд╛рд╣ рдкрд░ рдЪрд▓рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдпрд╣ рдирд╣реАрдВ рдЬрд╛рдирддреЗ рдХрд┐ рдХрд╣рд╛рдВ рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВ - рдореИрдВ рдЯрд┐рдВрдХреЙрдл рдкрддреНрд░рд┐рдХрд╛ https://journal.tinkoff.ru/pro/invest/ рд╕реЗ рдореБрдлреНрдд рдХреЛрд░реНрд╕ рдХреА рд╕рд▓рд╛рд╣ рджреЗ рд╕рдХрддрд╛ рд╣реВрдВ - рдпрд╣ рд╕рдВрдХреНрд╖рд┐рдкреНрдд, рдЬрд╛рдирдХрд╛рд░реАрдкреВрд░реНрдг рдФрд░ рд╕рдордЭрдиреЗ рдореЗрдВ рдЖрд╕рд╛рди рд╣реИред
рдФрд░ рдЬрдм рдЖрдк TI рдореЗрдВ рдмреНрд░реЛрдХрд░реЗрдЬ рдЦрд╛рддрд╛ рдЦреЛрд▓рддреЗ рд╣реИрдВ, рддреЛ рдореЗрд░реЗ рд▓рд┐рдВрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЖрдкрдХреЛ рдЙрдкрд╣рд╛рд░ рдХреЗ рд░реВрдк рдореЗрдВ 20,000 рд░реВрдмрд▓ рддрдХ рдХрд╛ рд╣рд┐рд╕реНрд╕рд╛ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдЧрд╛ред
рдзреНрдпрд╛рди рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред