algotrader
Version:
Algorithmically trade stocks and options using Robinhood, Yahoo Finance, and more.
333 lines (306 loc) • 9.64 kB
JavaScript
const LibraryError = require('../../globals/LibraryError');
const Robinhood = require('./Robinhood');
const Order = require('./Order');
const Instrument = require('./Instrument');
const async = require('async');
/**
* Represents all of the user's holdings on Robinhood and allows for various queries.
*/
class Portfolio extends Robinhood {
/**
* Creates a new Portfolio object.
* @author Torrey Leonard <https://github.com/Ladinn>
* @constructor
* @param {User} user
* @param {Array} array - Created via User.getPortfolio()
*/
constructor(user, array) {
if (!array instanceof Array) throw new Error("Parameter 'array' must be an array.");
else {
super();
this.user = user;
this.array = [];
const _this = this;
array.forEach(p => {
_this.array.push({
symbol: String(p.InstrumentObject.getSymbol()),
quantity: Number(p.quantity),
averageBuy: {
price: Number(p.average_buy_price),
pending: Number(p.pending_average_buy_price),
intraday: Number(p.intraday_average_buy_price)
},
dates: {
originalPurchase: new Date(p.created_at),
lastTrade: new Date(p.updated_at)
},
sharesHeld: {
stockGrants: Number(p.shares_held_for_stock_grants),
optionsEvents: Number(p.shares_held_for_options_events),
optionsCollateral: Number(p.shares_held_for_options_collateral),
forBuys: Number(p.shares_held_for_buys),
forSells: Number(p.shares_held_for_sells),
pendingFromOptionsEvents: Number(p.shares_pending_from_options_events)
},
InstrumentObject: p.InstrumentObject
})
});
}
}
/**
* Sells all positions in the user's portfolio at the market price.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Promise<Boolean|Error>}
*/
sellAll() {
const _this = this;
return new Promise((resolve, reject) => {
async.forEachOf(_this.array, (position, key, callback) => {
position.InstrumentObject.getQuote().then(quote => {
const order = new Order(_this.user, {
instrument: position.InstrumentObject,
quote: quote,
type: "market",
timeInForce: "gfd",
trigger: "immediate",
quantity: position.quantity,
side: "sell"
});
order.submit().then(res => {
callback();
}).catch(error => callback(error));
}).catch(error => callback(error));
}, error => {
resolve(error !== null ? true : error);
})
})
}
/**
* Executes a new order to reduce or increase the user's position in the given symbol by the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @param {Number} targetQuantity
* @returns {Promise<Order>}
*/
setQuantity(symbol, targetQuantity) {
const _this = this;
return new Promise(async (resolve, reject) => {
const position = _this.getBySymbol(symbol);
let instrument = position !== undefined ? position.InstrumentObject : await Instrument.getBySymbol(symbol);
let currentQuantity = position !== undefined ? position.quantity : 0;
const orderQuantity = targetQuantity - currentQuantity;
instrument.getQuote().then(quote => {
const order = new Order(_this.user, {
instrument: instrument,
quote: quote,
type: "market",
timeInForce: "gfd",
trigger: "immediate",
quantity: Math.abs(orderQuantity),
side: orderQuantity > 0 ? "buy" : "sell"
});
order.submit().then(res => {
resolve(res);
}).catch(error => reject(error));
}).catch(error => reject(error));
})
}
// GET
/**
* Returns the total market value of all stocks held by the user.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Promise<Number>}
*/
getStockValue() {
const _this = this;
return new Promise((resolve, reject) => {
let value = 0;
async.forEachOf(_this.array, (position, key, callback) => {
position.InstrumentObject.getQuote().then(quote => {
value += quote.getLast() * position.quantity;
callback();
}).catch(error => callback(error));
}, error => {
if (error) reject(error);
resolve(value);
})
})
}
/**
* Returns an array of all instruments in the user's portfolio.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Array}
*/
getInstrumentArray() {
let array = [];
this.array.forEach(p => {
array.push(p.InstrumentObject);
});
return array;
}
/**
* Returns an array of all symbols in the user's portfolio.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Array}
*/
getSymbols() {
let array = [];
this.array.forEach(p => {
array.push(p.symbol);
});
return array;
}
/**
* Returns the average buy price for the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Number}
*/
getBuyPrice(symbol) {
return this.getBySymbol(symbol).averageBuy.price;
}
/**
* Returns the quantity owned of the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Number}
*/
getQuantity(symbol) {
const shares = this.getBySymbol(symbol);
if (shares !== undefined) return shares.quantity;
else return 0;
}
/**
* Get total shares held for the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Number}
*/
getSharesHeld(symbol) {
const held = this.getBySymbol(symbol).sharesHeld;
return held.stockGrants + held.optionsEvents + held.optionsCollateral + held.forBuys + held.forSells + held.pendingFromOptionsEvents;
}
/**
* Returns the date of original purchase for the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Date}
*/
getPurchaseDate(symbol) {
return this.getBySymbol(symbol).dates.originalPurchase;
}
/**
* Returns the date of last trade for the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Date}
*/
getLastTradeDate(symbol) {
return this.getBySymbol(symbol).dates.lastTrade;
}
// FILTER / FIND
/**
* Returns an object containing the user's position in the given symbol.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {String} symbol
* @returns {Object}
*/
getBySymbol(symbol) {
return this.array.find(p => p.symbol === symbol);
}
/**
* Returns an array of objects containing the user's positions in the given symbols.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Array} array
* @returns {Array<Object>}
*/
getBySymbols(array) {
return this.array.filter(p => array.indexOf(p.symbol) !== -1);
}
/**
* Returns an array of all positions greater than the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} size
* @returns {Array<Object>}
*/
getQuantityGreaterThan(size) {
return this.array.filter(p => p.quantity > size);
}
/**
* Returns an array of all positions less than the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} size
* @returns {Array<Object>}
*/
getQuantityLessThan(size) {
return this.array.filter(p => p.quantity < size);
}
/**
* Returns an array of all positions equal to than the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} size
* @returns {Array<Object>}
*/
getQuantityEqualTo(size) {
return this.array.filter(p => p.quantity === size);
}
/**
* Returns an array of all positions opened after the given date (UTC).
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Date} date - Compared with UTC time.
* @returns {Array<Object>}
*/
getPurchasedAfter(date) {
return this.array.filter(p => p.dates["originalPurchase"].getTime() - date.getTime() > 0);
}
/**
* Returns an array of all positions opened before the given date (UTC).
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Date} date - Compared with UTC time.
* @returns {Array<Object>}
*/
getPurchasedBefore(date) {
return this.array.filter(p => p.dates["originalPurchase"].getTime() - date.getTime() < 0);
}
/**
* Returns an array of all positions opened on the given date (UTC).
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Date} date - Compared with UTC time.
* @returns {Array<Object>}
*/
getPurchasedOn(date) {
return this.array.filter(p =>
p.dates["originalPurchase"].getFullYear() === date.getFullYear() &&
p.dates["originalPurchase"].getMonth() === date.getMonth() &&
p.dates["originalPurchase"].getDate() === date.getDate()
);
}
/**
* Returns an array of all positions with an average buy price greater than the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} amount
* @returns {Array<Object>}
*/
getPriceGreaterThan(amount) {
return this.array.filter(p => p.averageBuy.price > amount);
}
/**
* Returns an array of all positions with an average buy price less than the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} amount
* @returns {Array<Object>}
*/
getPriceLessThan(amount) {
return this.array.filter(p => p.averageBuy.price < amount);
}
/**
* Returns an array of all positions with an average buy price equal to the given amount.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {Number} amount
* @returns {Array<Object>}
*/
getPriceEqualTo(amount) {
return this.array.filter(p => p.averageBuy.price === amount);
}
}
module.exports = Portfolio;