Skip to content

Instantly share code, notes, and snippets.

@DaniilVysotskiy
Created February 21, 2020 09:25
Show Gist options
  • Save DaniilVysotskiy/147de2326f6bd8cfee230606714b5f23 to your computer and use it in GitHub Desktop.
Save DaniilVysotskiy/147de2326f6bd8cfee230606714b5f23 to your computer and use it in GitHub Desktop.
Search shares and bonds on Moex by tickers Google App Script for Google Spreadsheet
// Link to security's history example: https://iss.moex.com/iss/history/engines/stock/markets/bonds/securities/SU29012RMFS0?till=2020-02-14&from=2020-02-07&sort_column=TRADEDATE&sort_order=desc&limit=1
// Manual: http://iss.moex.com/iss/reference/63
// Moex market types:
// - bonds
// - index
// - shares
const moexMarketTypesMap = {
"облигации": "bonds",
"акции": "shares",
"фонды": "shares",
"индексы": "index",
"bonds": "bonds",
"etf": "bonds",
"shares": "shares",
"fonds": "shares",
"indexes": "index",
undefined: "shares",
};
// Get normalized ticker type according to moex tickers types
const getNormalizedTickerType = (type, map) => {
return map[type.toLowerCase()];
};
// Get link to moex item, using market and security (ticker)
const getMoexItemLink = (market, security, from, till) => {
return `https://iss.moex.com/iss/history/engines/stock/markets/${market}/securities/${security}.xml?till=${till}&from=${from}&sort_column=TRADEDATE&sort_order=desc&limit=1&numtrades=1`;
};
const collectMyTickers = (spreadsheet, range, sheetPortfolioName) => {
const portfolioSheet = spreadsheet.getSheetByName(sheetPortfolioName);
const tickersList = portfolioSheet.getRange(range);
return tickersList.getValues().reduce((result, ticker) => {
const item = { ticker: ticker[0], type: ticker[1] };
if (item.ticker && item.type) {
result.push(item);
}
return result;
}, []);
}
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.createMenu('Scripts!')
.addItem('Fetch Market Data', 'init')
.addToUi();
init();
}
function collectDataFromXML(url, itemType) {
const response = UrlFetchApp.fetch(url);
const xml = response.getContentText() || null;
if (xml === null) return null;
const document = XmlService.parse(xml);
const root = document.getRootElement();
const data = root.getChild("data");
const rows = data.getChild("rows");
const item = rows.getChild("row");
const itemData = [];
if (item) {
const ticker = item.getAttribute('SECID').getValue().trim();
const name = item.getAttribute('SHORTNAME').getValue().trim();
const close = item.getAttribute('CLOSE').getValue().trim().replace(".", ",");
const open = item.getAttribute('OPEN').getValue().trim().replace(".", ",");
const curPrice = close != "" && close != "0" && close != 0 ? close : open;
const marketPrice = item.getAttribute('MARKETPRICE2').getValue().trim().replace(".",",");
const numTrades = item.getAttribute('NUMTRADES').getValue().trim();
const low = item.getAttribute('LOW').getValue().trim().replace(".", ",");
const high = item.getAttribute('HIGH').getValue().trim().replace(".", ",");
const couponValue = itemType === 'bonds' ? item.getAttribute('COUPONVALUE').getValue().replace(".", ",") : null;
const couponPrecent = itemType === 'bonds' ? item.getAttribute('COUPONPERCENT').getValue().replace(".", ",") : null;
// const couponPeriod = itemType === 'bonds' ? item.getAttribute('COUPONPERIOD').getValue() : null;
const expirationDate = itemType === 'bonds' ? item.getAttribute('MATDATE').getValue() : null;
const faceValue = itemType === 'bonds' ? item.getAttribute('FACEVALUE').getValue().replace(".", ",") : null;
const NKD = itemType === 'bonds' ? item.getAttribute('ACCINT').getValue().replace(".", ",") : null;
const tradeDate = item.getAttribute('TRADEDATE').getValue().trim();
itemData.push(ticker);
itemData.push(name);
itemData.push(curPrice);
itemData.push(marketPrice);
itemData.push(open);
itemData.push(close);
itemData.push(low);
itemData.push(high);
itemData.push(numTrades);
itemData.push(couponValue);
itemData.push(couponPrecent);
// itemData.push(couponPeriod);
itemData.push(expirationDate);
itemData.push(faceValue);
itemData.push(NKD);
itemData.push(tradeDate);
}
return itemData;
}
const fetchMarketsData = (tickersList, googleSpreadSheet, sheetMarketDataName) => {
if (!tickersList.length) return;
const headerTitles = [
"Ticker",
"Name",
"Current Price",
"Market Price",
"Open",
"Close",
"Low",
"High",
"Number of Trades",
"Coupon Value",
"Coupon Precent",
"Expiration Date",
"Face Value",
"NKD",
"Trade Date"
];
const marketData = googleSpreadSheet.getSheetByName(sheetMarketDataName);
let dataRange = marketData.getRange("A1:R300");
const currentDate = new Date();
// Get current date in "YYYY-MM-DD" format
const currentDateFormatted = currentDate.toISOString().split('T')[0];
const weekAgoDate = new Date(currentDate.setDate(currentDate.getDate() - 7));
// Get date week ago in "YYYY-MM-DD" format
const weekAgoDateFormatted = weekAgoDate.toISOString().split('T')[0];
// Market data container
const allMarketData = [headerTitles];
tickersList.forEach((item, index) => {
item.type = getNormalizedTickerType(item.type, moexMarketTypesMap);
const url = getMoexItemLink(item.type, item.ticker, weekAgoDateFormatted, currentDateFormatted);
const data = collectDataFromXML(url, item.type);
if (data !== null) {
allMarketData.push(data);
}
});
dataRange.clear({ contentsOnly: true });
dataRange = marketData.getRange(1, 1, allMarketData.length, allMarketData[0].length);
dataRange.setValues(allMarketData);
Browser.msgBox(`Обновлено ${allMarketData.length} записей! Все изменения на листе ${sheetMarketDataName}.`);
}
function init() {
const sheet_id = "1sVSFSdjKxAe7t-DJDBLdpy_vxskaYW4G9qhlxHNxAVg"; // Spreadsheet id of portfolio
const googleSpreadSheet = SpreadsheetApp.openById(sheet_id);
const rangeMyTickers = "A2:B100"; // Column consisted of tickers and its types in portfolio
const myTickersList = collectMyTickers(googleSpreadSheet, rangeMyTickers, "Портфель");
fetchMarketsData(myTickersList, googleSpreadSheet, "MarketData");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment