UNPKG

@alpacahq/alpaca-trade-api

Version:

Javascript library for the Alpaca Trade API

230 lines (212 loc) 8.43 kB
const API_KEY = 'YOUR_API_KEY_HERE'; const API_SECRET = 'YOUR_API_SECRET_HERE'; const PAPER = true; class MeanReversion { constructor(API_KEY, API_SECRET, PAPER){ this.Alpaca = require('@alpacahq/alpaca-trade-api'); this.alpaca = new this.Alpaca({ keyId: API_KEY, secretKey: API_SECRET, paper: PAPER }); this.runningAverage = 0; this.lastOrder = null; this.timeToClose = null; // Stock that the algo will trade. this.stock = "AAPL"; } async run(){ // First, cancel any existing orders so they don't impact our buying power. var orders; await this.alpaca.getOrders({ status:'all', direction:'asc' }).then((resp) => { orders = resp; }).catch((err) => {console.log(err.error);}); orders.forEach(async (order) => { this.alpaca.cancelOrder(order.id).catch((err) => {console.log(err.error);}); }); // Wait for market to open. console.log("Waiting for market to open..."); var promMarket = this.awaitMarketOpen(); await promMarket; console.log("Market opened."); // Get the running average of prices of the last 20 minutes, waiting until we have 20 bars from market open. var promBars = new Promise((resolve, reject) => { var barChecker = setInterval(async () => { await this.alpaca.getCalendar(Date.now()).then(async (resp) => { var marketOpen = resp[0].open; await this.alpaca.getBars('minute', this.stock, {start: marketOpen}).then((resp) => { var bars = resp[this.stock]; if(bars.length >= 20) { clearInterval(barChecker); resolve(); } }).catch((err) => {console.log(err.error);}); }); }, 60000); }); console.log("Waiting for 20 bars..."); await promBars; console.log("We have 20 bars."); // Rebalance our portfolio every minute based off running average data. var spin = setInterval(async () => { // Clear the last order so that we only have 1 hanging order. if(this.lastOrder != null) await this.alpaca.cancelOrder(this.lastOrder.id).catch((err) => {console.log(err.error);}); // Figure out when the market will close so we can prepare to sell beforehand. var closingTime; var currTime; await this.alpaca.getClock().then((resp) =>{ closingTime = new Date(resp.next_close.substring(0, resp.next_close.length - 6)); currTime = new Date(resp.timestamp.substring(0, resp.timestamp.length - 6)); }).catch((err) => {console.log(err.error);}); this.timeToClose = closingTime - currTime; if(this.timeToClose < (60000 * 15)) { // Close all positions when 15 minutes til market close. console.log("Market closing soon. Closing positions."); try{ await this.alpaca.getPosition(this.stock).then(async (resp) => { var positionQuantity = resp.qty; var promOrder = this.submitMarketOrder(positionQuantity, this.stock, "sell"); await promOrder; }).catch((err) => {console.log(err.error);}); } catch(err){/*console.log(err.error);*/} clearInterval(spin); console.log("Sleeping until market close (15 minutes)."); setTimeout(() => { // Run script again after market close for next trading day. this.run(); }, 60000*15); } else { // Rebalance the portfolio. await this.rebalance(); } }, 60000); } // Spin until the market is open awaitMarketOpen(){ var prom = new Promise((resolve, reject) => { var isOpen = false; var marketChecker = setInterval(async ()=>{ await this.alpaca.getClock().then(async (resp) => { isOpen = resp.is_open; if(isOpen) { clearInterval(marketChecker); resolve(); } else { var openTime, currTime; await this.alpaca.getClock().then((resp) =>{ openTime = new Date(resp.next_open.substring(0, resp.next_close.length - 6)); currTime = new Date(resp.timestamp.substring(0, resp.timestamp.length - 6)); }).then(() => { this.timeToClose = Math.floor((openTime - currTime) / 1000 / 60); }).catch((err) => {console.log(err.error);}); console.log(this.timeToClose + " minutes til next market open.") } }).catch((err) => {console.log(err.error);}); }, 60000); }); return prom; } // Rebalance our position after an update. async rebalance(){ var positionQuantity = 0; var positionValue = 0; // Get our position, if any. try{ await this.alpaca.getPosition(this.stock).then((resp) => { positionQuantity = resp.qty; positionValue = resp.market_value; }); } catch (err){/*console.log(err.error);*/} // Get the new updated price and running average. var bars; await this.alpaca.getBars('minute', this.stock,{limit: 20}).then((resp) => { bars = resp[this.stock]; }).catch((err) => {console.log(err.error);}); var currPrice = bars[bars.length - 1].closePrice; this.runningAverage = 0; bars.forEach((bar) => { this.runningAverage += bar.closePrice; }) this.runningAverage /= 20; if(currPrice > this.runningAverage){ // Sell our position if the price is above the running average, if any. if(positionQuantity > 0){ console.log("Setting position to zero."); await this.submitLimitOrder(positionQuantity, this.stock, currPrice, 'sell'); } else console.log("No position in the stock. No action required."); } else if(currPrice < this.runningAverage){ // Determine optimal amount of shares based on portfolio and market data. var portfolioValue; var buyingPower; await this.alpaca.getAccount().then((resp) => { portfolioValue = resp.portfolio_value; buyingPower = resp.buying_power; }).catch((err) => {console.log(err.error);}); var portfolioShare = (this.runningAverage - currPrice) / currPrice * 200; var targetPositionValue = portfolioValue * portfolioShare; var amountToAdd = targetPositionValue - positionValue; // Add to our position, constrained by our buying power; or, sell down to optimal amount of shares. if(amountToAdd > 0){ if(amountToAdd > buyingPower) amountToAdd = buyingPower; var qtyToBuy = Math.floor(amountToAdd / currPrice); await this.submitLimitOrder(qtyToBuy, this.stock, currPrice, 'buy'); } else{ amountToAdd *= -1; var qtyToSell = Math.floor(amountToAdd / currPrice); if(qtyToSell > positionQuantity) qtyToSell = positionQuantity; await this.submitLimitOrder(qtyToSell, this.stock, currPrice, 'sell'); } } } // Submit a limit order if quantity is above 0. async submitLimitOrder(quantity, stock, price, side){ if(quantity > 0){ await this.alpaca.createOrder({ symbol: stock, qty: quantity, side: side, type: 'limit', time_in_force: 'day', limit_price: price }).then((resp) => { this.lastOrder = resp; console.log("Limit order of |" + quantity + " " + stock + " " + side + "| sent."); }).catch((err) => { console.log("Order of |" + quantity + " " + stock + " " + side + "| did not go through."); }); } else { console.log("Quantity is <=0, order of |" + quantity + " " + stock + " " + side + "| not sent."); } } // Submit a market order if quantity is above 0. async submitMarketOrder(quantity, stock, side){ if(quantity > 0){ await this.alpaca.createOrder({ symbol: stock, qty: quantity, side: side, type: 'market', time_in_force: 'day' }).then((resp) => { this.lastOrder = resp; console.log("Market order of |" + quantity + " " + stock + " " + side + "| completed."); }).catch((err) => { console.log("Order of |" + quantity + " " + stock + " " + side + "| did not go through."); }); } else { console.log("Quantity is <=0, order of |" + quantity + " " + stock + " " + side + "| not sent."); } } } // Run the mean reversion class. var MR = new MeanReversion(API_KEY, API_SECRET, PAPER); MR.run();