This repository has been archived by the owner on Jan 14, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathalpha.js
347 lines (292 loc) · 10.9 KB
/
alpha.js
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// alpha is a very basic mean-reversion trading algorithm. it will determine
// valid trading ranges, and execute buy/sell orders when the stock is within
// those ranges. This bot has not been backtested or tested much at all, and
// is probably not a suitable investment vehicle for you. It is meant as a
// demonstration and learning tool for users who wish to create their own
// trading algorithms.
// im importing some local api functions here, but feel free to use your own
// or use these with the knowledge that they are not stable and are subject to
// change.
import { getPrices, getQuote } from '../../src/api/iex.js'
import {
getPosition,
getOrders,
submitClose,
submitOrder,
} from '../../src/api/alpaca.js'
// instantiate logger to keep track of trading activity
import { Log } from '../../src/log/log.js'
// i create a new log file every time the bot is started to keep track of each
// run
let d = new Date()
d =
d.toLocaleDateString().replace(/\//g, '') +
d.getHours() +
d.getMinutes() +
d.getSeconds()
const filepath = `../../data/log/log_alpha-${d}.js`
const log = new Log(
filepath,
// overwrite, overwrite instead of append. here all files should be new
// by naming convention
true,
)
// the main function that gets exported by bots.js
export async function alpha(ws, options) {
// perform checks to ensure bot will work
if (!options.symbol) {
ws.printLines(
'{#fa1-fg}alpha{/} bot needs a symbol, provide one with $symbol',
)
return
}
// log new function start
log.log({ type: 'start', timestamp: new Date() })
// set up the print object
const botOptions = {
bot: 'alpha ' + options.symbol,
symbol: options.symbol,
side: '',
pl: 0,
plpc: 0,
qty: 0,
msg: '',
}
// This bot is intended to be started and stopped from an iexcli repl, and does
// not do any checks to ensure the market is open and trading.
ws.printLines('{#afa-fg}alpha{/} bot, go')
// This bot tries to trade only one lot of shares at a time
let position
async function meanReversion() {
// First check whether or not bot is invested in the stock. If it is
// invested it will try to exit the entire position once it has gained or
// lost 1%.
ws.printLines(`{#afa-fg}alpha{/} bot, checking ${options.symbol} position`)
// first check if a position in the instrument exists
//
// n.b. most errors will NOT be propagated upwards and should be handled by
// the bot itself
try {
position = await getPosition(options)
} catch (e) {
position = null
ws.printLines(`{#afa-fg}alpha{/} bot, no ${options.symbol} position`)
}
if (position) {
let plAllowance = 0.01 // = 1% profit/loss margin on the currently held position
// if the bot is up or down more than a percent in profit, close the position
if (Math.abs(position.unrealized_intraday_plpc) > plAllowance) {
ws.printLines(
`{#afa-fg}alpha{/} bot, pl% ${position.unrealized_intraday_plpc}. closing position`,
)
try {
await submitClose(ws, options, position.symbol)
// this bot being a demo shows a lot of different printing and
// logging styles. use whichever is most convenient or make a
// function that does all three
// log first, since its probably the most important
log.log({ type: 'position', position, timestamp: new Date() })
// add a message to the repl output
ws.printLines(
`{#afa-fg}alpha{/} bot, closed ${options.symbol} position`,
)
// amend bot message
botOptions.msg = `{#ec0-fg}closed position{/} with ${+position.unrealized_intraday_pl} p/l = ${
+position.unrealized_intraday_plpc * 100
}`
botOptions.side = 'n/a'
botOptions.pl = 0
botOptions.plpc = 0
botOptions.qty = 0
options.print(botOptions)
// clear position this bot relies on the local position var to make
// subsequent decisions
position = null
return
} catch (e) {
ws.printLines(
`{#afa-fg}alpha{/} bot, {red-fg}ERR{/} could not close ${options.symbol} position`,
)
return
}
}
if (position === null) return
// else bot is invested but pl% is below threshold. bot will continue
// checking the market while threshold is unmet
// log
log.log({ type: 'position', position, timestamp: new Date() })
// print bot position info and return
botOptions.side = position.side
botOptions.pl = +position.unrealized_intraday_pl
botOptions.plpc = +position.unrealized_intraday_plpc * 100
botOptions.qty = +position.qty
options.print(botOptions)
return
}
// TODO, get all orders instead of default open
// if there is no position, check open orders in case there are unfilled
// orders for the stock. depending on the types of orders your bot is
// submitting, you should check to make sure that subsequent orders do not
// conflict with open orders, as you may not be able to enter/exit
// positions you otherwise would be able to. This bot is not set up to
// listen to the order stream, though most trading bots will want to do
// this to ensure order fullfillment and accuracy.
ws.printLines(`{#afa-fg}alpha{/} bot, checking ${options.symbol} orders`)
// check open orders for bot's targeted instrument
try {
const orders = await getOrders({ q: {} })
if (
orders.length &&
!!orders.find((o) => o.symbol == botOptions.symbol.toUpperCase())
) {
ws.printLines(
`{#afa-fg}alpha{/} bot, open ${options.symbol} orders, waiting till they are cleared...`,
)
return
}
} catch (e) {
ws.printLines(
`{#afa-fg}alpha{/} bot, {red-fg}ERR{/} could not get ${options.symbol} orders
${e}`,
)
return
}
// otherwise if the bot is not invested and has no open orders, it will
// query market data and look for opportunities to invest when the stock
// price has deviated more than 1% from the daily mean
ws.printLines(
`{#afa-fg}alpha{/} bot, getting ${options.time} price/vol data for ${options.symbol}`,
)
// get stocks daily bars
let data
try {
data = await getPrices(options)
} catch (e) {
ws.printLines(e)
return
}
// accumulate stock data
let { prices, avg, tot, hi, lo, vol } = data.reduce(
(a, v) => {
// strip nulls
if (!v.close) return a
a.prices.push(v)
a.tot++
a.avg += v.close
a.vol += v.volume
if (v.close > a.hi) a.hi = v.close
if (v.close < a.lo) a.lo = v.close
return a
},
{ prices: [], avg: 0, tot: 0, hi: -Infinity, lo: Infinity, vol: 0 },
)
// get realtime quote for last trade info
let quote
try {
quote = await getQuote(options)
} catch (e) {
ws.printLines(e)
return
}
const last = quote.latestPrice
// find average
avg = avg / tot
// find mean
let mean = [...prices].sort((l, r) => {
return l.close - r.close
})
mean = mean[Math.floor(mean.length / 2)].close
// calculate percentage delta from mean
const meanDiff = last - mean
const meanDiffPer = meanDiff / mean
// calc some other data for display
const hiLoDiff = hi - lo
const hiLoPer = ((hiLoDiff / last) * 100).toFixed(2)
// build msg
botOptions.msg = `over ${options.time}: avg: ${avg.toFixed(
2,
)} last: {#cf9-fg}${last}{/}
{bold}mean: ${mean} | {#cff-fg}${(meanDiffPer * 100).toFixed(2)}{/}%
hi: {#afa-fg}${hi}{/} - lo: {#faa-fg}${lo}{/} = ${hiLoDiff.toFixed(
2,
)} ~${hiLoPer}%
vol: ${vol.toLocaleString()}`
ws.printLines(
`${options.symbol} @ ${last} off ${(meanDiffPer * 100).toFixed(
2,
)}% from day mean`,
)
let deviation = 0.01 // = 1% deviation last price from mean price
// this is where the bot decides what to do. given that this is a mean
// reversion "algorithm", it will evaluate a stock based on how far the
// last price is from the _daily_ mean and decide to trade based on the
// thresholds set by the function. This bot uses 1% deviation, and 1%
// profit/loss (plAllowance). This bot will always try to revert back to
// the _daily_ mean, regardless of time of day. Simple tweaks to this
// algorithm would include shortening/lengthening the number of minutes of
// data the bot is evaluating, and widening or narrowing its trading
// range.
//
// This example shows a 1% mean reversion algorithm, which will execute
// trades in the opposite direction of the deviation when a stock is 1%
// above or below its _daily_ mean. It exits again when the profit/loss
// has exceeeded 1%. See above.
if (Math.abs(meanDiffPer) > deviation) {
let side = ''
if (meanDiff > 0) {
side = 'sell'
} else {
side = 'buy'
}
// without bringing in book data, it's safest to submit market orders,
// but a typical bot would probably query book data before executing a
// trade. this bot sends down market gtc orders by default. bot makers
// should be especially careful when testing algorithms based on "paper"
// trades. real life trades may have very different execution times and
// prices which should be factored into account. properly testing an
// algo with "paper" trades is best done with limit orders for a better
// level of accuracy, but for simplicity's sake we are using market
// orders.
let qty = 1
let order = {
symbol: options.symbol.toUpperCase(),
side,
qty,
time_in_force: 'gtc',
type: 'market',
}
ws.printLines(`${side}ing ${qty} shr of ${options.symbol}`)
try {
await submitOrder(ws, options, order)
} catch (e) {
ws.printLines('alpha bot err: could not submit order: ' + e.toString())
return
}
// log
log.log({ type: 'order', order, timestamp: new Date() })
// print trade message
botOptions.msg += `
order confirmed: {yellow-fg}${order.side} ${order.filled_qty}/${order.qty} ${order.symbol} @ ${order.filled_avg_price}{/}`
options.print(botOptions)
} else {
ws.printLines(`${options.symbol} is too close to mean to trade`)
}
options.print(botOptions)
}
// i call the function here first here in case there is a long interval; i
// want the bot to start running right away
await meanReversion()
// set interval
const interval = setInterval(
// wrapper function
meanReversion,
// 10 seconds
10000,
)
// return interval back to iexcli so that you can stop it from the repl with
// e.g. `bots start alpha $symbol`
// `bots stop alpha`
// or change its target with
// e.g. `bots start alpha $new_symbol`
return interval
}