Skip to content

Instantly share code, notes, and snippets.

@andrewkim316
Last active August 23, 2019 22:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewkim316/75edd8f8728dbbcb289b417302930940 to your computer and use it in GitHub Desktop.
Save andrewkim316/75edd8f8728dbbcb289b417302930940 to your computer and use it in GitHub Desktop.
// Rebalance our position after an update.
async rebalance(){
await this.rerank();
// Clear existing orders again.
var orders;
await this.alpaca.getOrders({
status: 'open',
direction: 'desc'
}).then((resp) => {
orders = resp;
}).catch((err) => {console.log(err.error);});
var promOrders = [];
orders.forEach((order) => {
promOrders.push(new Promise(async (resolve, reject) => {
await this.alpaca.cancelOrder(order.id).catch((err) => {console.log(err.error);});
resolve();
}));
});
await Promise.all(promOrders);
console.log("We are taking a long position in: " + this.long.toString());
console.log("We are taking a short position in: " + this.short.toString());
// Remove positions that are no longer in the short or long list, and make a list of positions that do not need to change. Adjust position quantities if needed.
var positions;
await this.alpaca.getPositions().then((resp) => {
positions = resp;
}).catch((err) => {console.log(err.error);});
var promPositions = [];
var executed = {long:[], short:[]};
var side;
this.blacklist.clear();
positions.forEach((position) => {
promPositions.push(new Promise(async (resolve, reject) => {
if(this.long.indexOf(position.symbol) < 0){
// Position is not in long list.
if(this.short.indexOf(position.symbol) < 0){
// Position not in short list either. Clear position.
if(position.side == "long") side = "sell";
else side = "buy";
var promCO = this.submitOrder(Math.abs(position.qty), position.symbol, side);
await promCO.then(() => {
resolve();
});
}
else{
// Position in short list.
if(position.side == "long") {
// Position changed from long to short. Clear long position and short instead
var promCS = this.submitOrder(position.qty, position.symbol, "sell");
await promCS.then(() => {
resolve();
});
}
else {
if(Math.abs(position.qty) == this.qShort){
// Position is where we want it. Pass for now.
}
else{
// Need to adjust position amount
var diff = Number(Math.abs(position.qty)) - Number(this.qShort);
if(diff > 0){
// Too many short positions. Buy some back to rebalance.
side = "buy";
}
else{
// Too little short positions. Sell some more.
side = "sell";
}
var promRebalance = this.submitOrder(Math.abs(diff), position.symbol, side);
await promRebalance;
}
executed.short.push(position.symbol);
this.blacklist.add(position.symbol);
resolve();
}
}
}
else{
// Position in long list.
if(position.side == "short"){
// Position changed from short to long. Clear short position and long instead.
var promCS = this.submitOrder(Math.abs(position.qty), position.symbol, "buy");
await promCS.then(() => {
resolve();
});
}
else{
if(position.qty == this.qLong){
// Position is where we want it. Pass for now.
}
else{
// Need to adjust position amount.
var diff = Number(position.qty) - Number(this.qLong);
if(diff > 0){
// Too many long positions. Sell some to rebalance.
side = "sell";
}
else{
// Too little long positions. Buy some more.
side = "buy";
}
var promRebalance = this.submitOrder(Math.abs(diff), position.symbol, side);
await promRebalance;
}
executed.long.push(position.symbol);
this.blacklist.add(position.symbol);
resolve();
}
}
}));
});
await Promise.all(promPositions);
// Send orders to all remaining stocks in the long and short list.
var promLong = this.sendBatchOrder(this.qLong, this.long, 'buy');
var promShort = this.sendBatchOrder(this.qShort, this.short, 'sell');
var promBatches = [];
this.adjustedQLong = -1;
this.adjustedQShort = -1;
await Promise.all([promLong, promShort]).then(async (resp) => {
// Handle rejected/incomplete orders.
resp.forEach(async (arrays, i) => {
promBatches.push(new Promise(async (resolve, reject) => {
if(i == 0) {
arrays[1] = arrays[1].concat(executed.long);
executed.long = arrays[1].slice();
}
else {
arrays[1] = arrays[1].concat(executed.short);
executed.short = arrays[1].slice();
}
// Return orders that didn't complete, and determine new quantities to purchase.
if(arrays[0].length > 0 && arrays[1].length > 0){
var promPrices = this.getTotalPrice(arrays[1]);
await Promise.all(promPrices).then((resp) => {
var completeTotal = resp.reduce((a, b) => a + b, 0);
if(completeTotal != 0){
if(i == 0){
this.adjustedQLong = Math.floor(this.longAmount / completeTotal);
}
else{
this.adjustedQShort = Math.floor(this.shortAmount / completeTotal);
}
}
});
}
resolve();
}));
});
await Promise.all(promBatches);
}).then(async () => {
// Reorder stocks that didn't throw an error so that the equity quota is reached.
var promReorder = new Promise(async (resolve, reject) => {
var promLong = [];
if(this.adjustedQLong >= 0){
this.qLong = this.adjustedQLong - this.qLong;
executed.long.forEach(async (stock) => {
promLong.push(new Promise(async (resolve, reject) => {
var promLong = this.submitOrder(this.qLong, stock, 'buy');
await promLong;
resolve();
}));
});
}
var promShort = [];
if(this.adjustedQShort >= 0){
this.qShort = this.adjustedQShort - this.qShort;
executed.short.forEach(async(stock) => {
promShort.push(new Promise(async (resolve, reject) => {
var promShort = this.submitOrder(this.qShort, stock, 'sell');
await promShort;
resolve();
}));
});
}
var allProms = promLong.concat(promShort);
if(allProms.length > 0){
await Promise.all(allProms);
}
resolve();
});
await promReorder;
});
}
// Re-rank all stocks to adjust longs and shorts.
async rerank(){
await this.rank();
// Grabs the top and bottom quarter of the sorted stock list to get the long and short lists.
var longShortAmount = Math.floor(this.allStocks.length / 4);
this.long = [];
this.short = [];
for(var i = 0; i < this.allStocks.length; i++){
if(i < longShortAmount) this.short.push(this.allStocks[i].name);
else if(i > (this.allStocks.length - 1 - longShortAmount)) this.long.push(this.allStocks[i].name);
else continue;
}
// Determine amount to long/short based on total stock price of each bucket.
var equity;
await this.alpaca.getAccount().then((resp) => {
equity = resp.equity;
}).catch((err) => {console.log(err.error);});
this.shortAmount = 0.30 * equity;
this.longAmount = Number(this.shortAmount) + Number(equity);
var promLong = await this.getTotalPrice(this.long);
var promShort = await this.getTotalPrice(this.short);
var longTotal;
var shortTotal;
await Promise.all(promLong).then((resp) => {
longTotal = resp.reduce((a, b) => a + b, 0);
});
await Promise.all(promShort).then((resp) => {
shortTotal = resp.reduce((a, b) => a + b, 0);
});
this.qLong = Math.floor(this.longAmount / longTotal);
this.qShort = Math.floor(this.shortAmount / shortTotal);
}
// Mechanism used to rank the stocks, the basis of the Long-Short Equity Strategy.
async rank(){
// Ranks all stocks by percent change over the past 10 minutes (higher is better).
var promStocks = this.getPercentChanges(this.allStocks);
await Promise.all(promStocks);
// Sort the stocks in place by the percent change field (marked by pc).
this.allStocks.sort((a, b) => {return a.pc - b.pc;});
}
// Get the total price of the array of input stocks.
getTotalPrice(stocks){
var proms = [];
stocks.forEach(async (stock) => {
proms.push(new Promise(async (resolve, reject) => {
await this.alpaca.getBars('minute', stock, {limit: 1}).then((resp) => {
resolve(resp[stock][0].c);
}).catch((err) => {console.log(err.error);});
}));
});
return proms;
}
// Submit a batch order that returns completed and uncompleted orders.
async sendBatchOrder(quantity, stocks, side){
var prom = new Promise(async (resolve, reject) => {
var incomplete = [];
var executed = [];
var promOrders = [];
stocks.forEach(async (stock) => {
promOrders.push(new Promise(async (resolve, reject) => {
if(!this.blacklist.has(stock)) {
var promSO = this.submitOrder(quantity, stock, side);
await promSO.then((resp) => {
if(resp) executed.push(stock);
else incomplete.push(stock);
resolve();
});
}
else resolve();
}));
});
await Promise.all(promOrders).then(() => {
resolve([incomplete, executed]);
});
});
return prom;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment