|
3 | 3 | 'use strict'
|
4 | 4 |
|
5 | 5 | // Import libraries
|
6 |
| -import { existsSync, readFileSync, writeFileSync } from 'fs' |
7 |
| -import { join } from 'path' |
8 |
| -import AlphaVantage from 'alphavantage' |
9 |
| -import jayson from 'jayson' |
10 |
| -import cors from 'cors' |
11 |
| -import bodyParser from 'body-parser' |
12 |
| -import connect from 'connect' |
13 | 6 | import StockInfo from 'stock-info'
|
14 | 7 |
|
15 |
| -// Load environment |
16 |
| -require('dotenv').config() |
17 |
| - |
18 |
| -// Constants |
19 |
| -const AUTOCACHE_PATH = join(__dirname, 'alphavantage.cache') |
20 |
| - |
21 |
| -// Initialize AlphaVantage API Client |
22 |
| -const alpha = AlphaVantage({ key: process.env.ALPHAVANTAGE }) |
23 |
| - |
24 |
| -// Cache to circumvent AlphaVantage API restrictions |
25 |
| -class AutoCache { |
26 |
| - static tryInit () { |
27 |
| - if ( |
28 |
| - Object.keys(AutoCache.cache).length === 0 && |
29 |
| - existsSync(AUTOCACHE_PATH) |
30 |
| - ) { |
31 |
| - AutoCache.cache = JSON.parse(readFileSync(AUTOCACHE_PATH)) |
32 |
| - } |
33 |
| - } |
34 |
| - |
35 |
| - static verifyArgs (args1, args2) { |
36 |
| - let valid = args1.length === args2.length |
37 |
| - if (!valid) console.log('[Cache] Parameter count mismatch') |
38 |
| - for (const i of args1.keys()) { |
39 |
| - if (!valid) break |
40 |
| - valid &= args1[i] === args2[i] |
41 |
| - } |
42 |
| - if (!valid) console.log('[Cache] Detected invalid args') |
43 |
| - return valid |
44 |
| - } |
45 |
| - |
46 |
| - static retrieve (key, args) { |
47 |
| - console.log(`[Cache::FetchDry] ${key}`) |
48 |
| - AutoCache.tryInit() |
49 |
| - return key in AutoCache.cache && |
50 |
| - AutoCache.verifyArgs(args, AutoCache.cache[key].args) |
51 |
| - ? AutoCache.cache[key].data |
52 |
| - : null |
53 |
| - } |
54 |
| - |
55 |
| - static store (key, args, data) { |
56 |
| - console.log(`[Cache::Store] ${key}`) |
57 |
| - AutoCache.cache[key] = { args, data } |
58 |
| - writeFileSync(AUTOCACHE_PATH, JSON.stringify(AutoCache.cache)) |
59 |
| - return data |
60 |
| - } |
61 |
| - |
62 |
| - static async _call (key, fn, ...args) { |
63 |
| - console.log(`[Cache::FetchWet] ${key}`) |
64 |
| - const rawData = await fn(...args) |
65 |
| - return alpha.util.polish(rawData) |
66 |
| - } |
67 |
| - |
68 |
| - static async call (key, fn, ...args) { |
69 |
| - const newKey = `${key}<${JSON.stringify(args)}>` |
70 |
| - return ( |
71 |
| - AutoCache.retrieve(newKey, args) || |
72 |
| - AutoCache.store(newKey, args, await AutoCache._call(newKey, fn, ...args)) |
73 |
| - ) |
74 |
| - } |
75 |
| -} |
76 |
| -AutoCache.cache = {} |
77 |
| - |
78 |
| -class DividendProjector { |
79 |
| - static getTotalDividendAmount (data, timeframeInMonths) { |
80 |
| - return data |
81 |
| - .slice(1, timeframeInMonths + 1) |
82 |
| - .reduce((acc, { dividend }) => acc + dividend, 0.0) |
83 |
| - } |
84 |
| - |
85 |
| - static getYearlyAverageDividendYield (data) { |
86 |
| - const validPeriods = data.slice(1).slice(0, 12 * 5) // five year averages |
87 |
| - const indexOfFirstPaidPeriod = validPeriods.findIndex( |
88 |
| - p => p.dividend > 0 |
89 |
| - ) |
90 |
| - const indexOfLastPaidPeriod = |
91 |
| - validPeriods.length - |
92 |
| - 1 - |
93 |
| - [...validPeriods].reverse().findIndex(p => p.dividend > 0) |
94 |
| - const validPeriodsAfterFirstPaid = validPeriods.slice( |
95 |
| - indexOfFirstPaidPeriod, |
96 |
| - indexOfLastPaidPeriod |
97 |
| - ) |
98 |
| - const periodRate = |
99 |
| - validPeriodsAfterFirstPaid[0].dividend / |
100 |
| - validPeriodsAfterFirstPaid[validPeriodsAfterFirstPaid.length - 1] |
101 |
| - .dividend |
102 |
| - const averageDividendGrowthPerYear = |
103 |
| - periodRate ** (1.0 / (validPeriodsAfterFirstPaid.length / 12.0)) |
104 |
| - return averageDividendGrowthPerYear |
105 |
| - } |
106 |
| -} |
107 |
| - |
108 |
| -async function getDividendStatistics (symbol) { |
109 |
| - const data = await getDividendPayments(symbol) |
110 |
| - const yearlyAverageDividendYield = DividendProjector.getYearlyAverageDividendYield( |
111 |
| - data.history |
112 |
| - ) |
113 |
| - const yearlyAverageDividendYieldPercentage = |
114 |
| - 100 * (yearlyAverageDividendYield - 1.0) |
115 |
| - const totalDividendAmount12m = DividendProjector.getTotalDividendAmount( |
116 |
| - data.history, |
117 |
| - 12 |
118 |
| - ) |
119 |
| - const totalDividendAmount24m = DividendProjector.getTotalDividendAmount( |
120 |
| - data.history, |
121 |
| - 24 |
122 |
| - ) |
123 |
| - return { |
124 |
| - symbol: data.symbol, |
125 |
| - updated: data.updated, |
126 |
| - zone: data.zone, |
127 |
| - yearlyAverageDividendYield, |
128 |
| - yearlyAverageDividendYieldPercentage, |
129 |
| - totalDividendAmount12m, |
130 |
| - totalDividendAmount24m |
131 |
| - } |
132 |
| -} |
133 |
| - |
134 |
| -async function getDividendPayments (symbol) { |
135 |
| - const res = await AutoCache.call( |
136 |
| - 'monthly_adjusted', |
137 |
| - alpha.data.monthly_adjusted, |
138 |
| - symbol |
139 |
| - ) |
140 |
| - return { |
141 |
| - ...res.meta, |
142 |
| - history: Object.entries(res.data).map(([time, { dividend }]) => ({ |
143 |
| - time, |
144 |
| - dividend: parseFloat(dividend) |
145 |
| - })) |
146 |
| - } |
147 |
| -} |
148 |
| - |
149 |
| -// Easy chaining of complex async operations |
150 |
| -class ComplexOperation { |
151 |
| - constructor (context) { |
152 |
| - this.chain = [] |
153 |
| - this.context = context |
154 |
| - } |
155 |
| - |
156 |
| - static init (context = {}) { |
157 |
| - return new ComplexOperation(context) |
158 |
| - } |
159 |
| - |
160 |
| - next ({ fn, errorCode }) { |
161 |
| - this.chain.push({ fn, errorCode }) |
162 |
| - return this |
163 |
| - } |
164 |
| - |
165 |
| - middleware (fn) { |
166 |
| - return this.next(fn()) |
167 |
| - } |
168 |
| - |
169 |
| - assert ({ fn, errorCode }) { |
170 |
| - const condFn = context => { |
171 |
| - return new Promise((resolve, reject) => { |
172 |
| - if (fn(context)) { |
173 |
| - resolve({}) |
174 |
| - } else { |
175 |
| - reject(new Error('Assertion failed.')) |
176 |
| - } |
177 |
| - }) |
178 |
| - } |
179 |
| - return this.next({ fn: condFn, errorCode }) |
180 |
| - } |
181 |
| - |
182 |
| - async evaluate ({ success, error }) { |
183 |
| - for (const { fn, errorCode } of this.chain) { |
184 |
| - try { |
185 |
| - this.context = { |
186 |
| - ...this.context, |
187 |
| - ...(await fn(this.context)) |
188 |
| - } |
189 |
| - } catch (e) { |
190 |
| - console.log('ERROR') |
191 |
| - console.log({ code: errorCode, data: e }) |
192 |
| - error({ |
193 |
| - code: errorCode, |
194 |
| - message: 'Error', |
195 |
| - data: e |
196 |
| - }) |
197 |
| - return |
198 |
| - } |
199 |
| - } |
200 |
| - console.log(this.context) |
201 |
| - success(this.context) |
202 |
| - } |
203 |
| -} |
204 |
| - |
205 | 8 | class StockInfoClient {
|
206 | 9 | static async getStockInformation (symbol) {
|
207 | 10 | return new Promise((resolve, reject) => {
|
208 |
| - AutoCache.call('stockinfo__getsinglestockinfo', StockInfo.getSingleStockInfo, symbol).then(resolve).catch(reject) |
| 11 | + StockInfo.getSingleStockInfo(symbol).then(resolve).catch(reject) |
209 | 12 | })
|
210 | 13 | }
|
211 | 14 | }
|
@@ -235,14 +38,3 @@ const server = jayson.Server({
|
235 | 38 | StockInfoClient.getStockInformation(args.symbol).then(res => cb(null, res)).catch(() => cb(error))
|
236 | 39 | }
|
237 | 40 | })
|
238 |
| - |
239 |
| -// Configure server |
240 |
| -const app = connect() |
241 |
| -app.use(cors({ methods: ['GET', 'POST'] })) |
242 |
| -app.use(bodyParser.json()) |
243 |
| -app.use(server.middleware()) |
244 |
| - |
245 |
| -// Listen |
246 |
| -app.listen(process.env.PORT, process.env.HOST, () => { |
247 |
| - console.log(`Listening on port ${process.env.PORT}.`) |
248 |
| -}) |
0 commit comments