
في الآونة الأخيرة ، بدأت المقالات تظهر في المحور والتي تبدأ بقصص حول وقت الفراغ في العزلة الذاتية ، ونتيجة لذلك ، ظهرت حافلات ترولي باص من الرغيف. ربما يجب على الإدارة أن تنظر في إضافة مركز جديد - العزلة الذاتية ..
لذلك حصلت على وقت فراغ خصصته لتحليل معاملاتي في Tinkoff Investments. هناك نوعان من الأشخاص: يقوم البعض ببناء مصفوفات متعددة الأبعاد بشكل جميل في رؤوسهم ، ويتفوق عليهم في حلقة في دفتر ملاحظات IPython ، بينما يحب البعض الآخر "الشعور" بالأرقام ، ووضعها على الرفوف في Excel. أنسب نفسي إلى الفئة الثانية ، لذا فقد أدخلت جميع صفقاتي بعناية في جداول بيانات Google.
ضمن هذا الخفض ، سأخبرك بكيفية أتمتة روتين عملي باستخدام Google Apps Script و Tinkoff Investments API.
قبل أن نصل إلى هذه النقطة ، مسرد صغير للمصطلحات التي أستخدمها في المقالة:
- TI - Tinkoff Investment
- الأداة هي أي ضمان ، مثل الأسهم أو السندات أو 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 . , )
يتم تحميل جميع التعليمات البرمجية وكيفية استخدام github
بالنسبة للقراء الذين يرغبون في الشروع في طريق الاستثمار ، لكنهم لا يعرفون من أين يبدأون - يمكنني تقديم المشورة لدورة مجانية من مجلة Tinkoff https://journal.tinkoff.ru/pro/invest/ - فهي قصيرة وغنية بالمعلومات وسهلة الفهم.
وعندما تفتح حساب سمسرة في TI ، باستخدام الرابط الخاص بي ، ستحصل على حصة تصل إلى 20000 روبل كهدية.
شكرآ لك على أهتمامك.