-
Notifications
You must be signed in to change notification settings - Fork 1
/
code.gs
202 lines (174 loc) · 7.53 KB
/
code.gs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// SETTINGS //
var token = "YOUR_API_TOKEN"; // Your Indexa Capital API token
var accountNumber = "YOUR_ACCOUNT_NUMBER"; // Your Indexa Capital account number
var filename = "FILE NAME"; // Desired name for the Google Sheets file. Will be created if it doesn't exist.
var newestOnTop = false; // Set to true if you want the table sorted from newest to oldest
//////////////
const tableHeaders = [["Date", "Price", "Titles", "Cost Amount", "Amount"]];
function run() {
let file = getActiveSpreadsheet(filename);
let positions = getAccountPositions(token, accountNumber);
updateSheet(positions, file);
}
function getActiveSpreadsheet(filename) {
// Find a Google Sheets document with the specified title
let files = DriveApp.searchFiles('title = "' + filename + '" and mimeType = "' + MimeType.GOOGLE_SHEETS + '"');
// Open it if it exists, otherwise create it.
if (files.hasNext()) {
Logger.log("Existing file " + filename + " found");
return SpreadsheetApp.open(files.next());
} else {
Logger.log("File " + filename + " not found. Creating it.");
return SpreadsheetApp.create(filename);
}
}
function getAccountPositions(token, accountNumber) {
// Prepare API request
let host = "https://api.indexacapital.com";
let url = host + "/accounts/" + accountNumber + "/portfolio";
let options = {
'method': 'get',
'headers': {'X-AUTH-TOKEN': token, 'Accept' : '*/*'},
'muteHttpExceptions': true
};
// Send query
let response = UrlFetchApp.fetch(url, options);
// Parse response
let data = JSON.parse(response.getContentText());
// Return data from all positions
return data.instrument_accounts[0].positions;
}
function extractPositionValues(positionData) {
let position = {};
// Get the identifier name (ISIN, DGS or EPSV)
let identifierName = positionData.instrument.identifier_name;
position.instrumentIdentifierName = positionData.instrument.identifier_name;
// Get the correct identifier variable name for the instrument and any subfunds (in case of pension plans)
let identifierVariable = "";
let subfundIdentifierVariable = "";
if (identifierName == "ISIN") {
identifierVariable = "isin_code";
}
if (identifierName == "DGS") {
identifierVariable = "dgs_code";
subfundIdentifierVariable = "dgs_fund_code";
}
if (identifierName == "EPSV") {
identifierVariable = "epsv_plan_code";
subfundIdentifierVariable = "epsv_fund_code";
}
// Populate the value of the instrument identifier
let identifier = positionData.instrument[identifierVariable];
let subfundIdentifier = "";
if (subfundIdentifierVariable != "" && positionData.instrument[subfundIdentifierVariable] != "") {
subfundIdentifier = positionData.instrument[subfundIdentifierVariable];
position.instrumentIdentifier = identifier + " - " + subfundIdentifier;
} else {
position.instrumentIdentifier = identifier;
}
// Populate the other variables related to the instrument
position.instrumentName = positionData.instrument.name;
position.instrumentAssetClass = positionData.instrument.asset_class;
position.instrumentManagementCompanyDescription = positionData.instrument.management_company_description;
// Populate the variables related to the current position status
position.price = positionData.price;
position.titles = positionData.titles;
position.date = positionData.date;
position.amount = positionData.amount;
position.costAmount = positionData.cost_amount;
// Return the object containing all the variables
return position;
}
function sheetIsEmpty(sheet) {
// Check if selected sheet is empty
return sheet.getLastRow() == 0 && sheet.getLastColumn() == 0;
}
function addCurrentValues(sheet, position) {
// Add current position values to the table
let lastRow = sheet.getRange("A1:A").getValues().filter(String).length;
let newRange;
if (newestOnTop) {
Logger.log("Inserting new row at the top");
newRange = sheet.getRange("A2:E2");
if (!newRange.isBlank()) {
// If the table is not empty, we shift down the existing data to add the new entry on top.
newRange.insertCells(SpreadsheetApp.Dimension.ROWS);
}
} else {
Logger.log("Adding new row at the bottom");
let nextRow = lastRow + 1;
newRange = sheet.getRange("A" + nextRow + ":E" + nextRow);
}
newRange.setValues(
[
[position.date, position.price, position.titles, position.costAmount, position.amount]
]
);
}
function addInstrumentInfo(sheet, position) {
sheet.getRange('G1:H4').setValues(
[
['Name', position.instrumentName],
[position.instrumentIdentifierName, position.instrumentIdentifier],
["Asset class", position.instrumentAssetClass],
["Management Company", position.instrumentManagementCompanyDescription]
]
);
}
function formatSheet(sheet) {
sheet.setColumnWidths(1, 5, 110);
sheet.setColumnWidths(7, 1, 200);
sheet.setColumnWidths(8, 1, 300);
sheet.getRange('A1:E1').setFontWeight('bold');
sheet.getRange('G1:G4').setFontWeight('bold');
sheet.getRange('G1:H4').setBorder(true, true, true, true, false, true, null, SpreadsheetApp.BorderStyle.SOLID);
}
function updateSheet(positions, file) {
// Loop through each position
for (let i = 0; i < positions.length; i++) {
position = extractPositionValues(positions[i]);
// Check if there's already a sheet for the current instrument
sheet = file.getSheetByName(position.instrumentIdentifier);
if (!sheet) {
// Otherwise, create it.
Logger.log("Sheet " + position.instrumentIdentifier + " not found. Inserting it.");
file.insertSheet(position.instrumentIdentifier);
sheet = file.getSheetByName(position.instrumentIdentifier);
}
Logger.log("Selected sheet: " + sheet.getName());
if (sheetIsEmpty(sheet)) {
// If the sheet is empty add the table headers and the instrument information
Logger.log("Sheet is empty. Adding headers and instrument information.");
sheet.getRange('A1:E1').setValues(tableHeaders);
addInstrumentInfo(sheet, position);
// Add the first entry with the current position status
Logger.log("Adding current position status.");
addCurrentValues(sheet, position);
// Apply sheet formatting
formatSheet(sheet);
continue;
}
Logger.log("Sheet is not empty. Checking contents...");
if (JSON.stringify(sheet.getRange('A1:E1').getValues()) != JSON.stringify(tableHeaders)) {
// If the sheet is not empty and the headers don't match our format, we assume there's something else on this sheet and stop execution.
Logger.log("Headers don't look right, stopping script. Is this sheet being used for something else?");
Logger.log("Expected " + tableHeaders + ", found " + sheet.getRange('A1:E1').getValues());
continue;
}
Logger.log("Headers look correct. Checking if there's new data to add.");
// Headers look right, so we check the date column to see if today's data is already in place.
let sheetIsUpToDate = sheet.getRange("A1:A").createTextFinder(position.date).matchEntireCell(true).findNext();
if (sheetIsUpToDate) {
Logger.log("Sheet already contains latest data from " + position.date + ". Nothing to add.");
continue;
}
// Today's data is not in the table, so we add it.
Logger.log("Adding today's values.");
addCurrentValues(sheet, position);
// Finally, we update the instrument information, just in case it has changed.
Logger.log("Updating instrument information.");
addInstrumentInfo(sheet, position);
// Apply sheet formatting
formatSheet(sheet);
}
}