algotrader
Version:
Algorithmically trade stocks and options using Robinhood, Yahoo Finance, and more.
227 lines (215 loc) • 6.98 kB
JavaScript
const LibraryError = require('../../globals/LibraryError');
const Robinhood = require('./Robinhood');
const request = require('request');
const async = require('async');
/**
* Represents and executes an order for the given instrument.
*/
class Order extends Robinhood {
/**
* Creates a new Order object.
* @author Torrey Leonard <https://github.com/Ladinn>
* @constructor
* @param {User} user
* @param {Object} object
* @property {Instrument} instrument
* @property {Quote} quote
* @property {String} type - 'limit' | 'market
* @property {String} timeInForce - 'gfd' | 'gtc' | 'ioc' | 'opg'
* @property {String} trigger - 'immediate' | 'stop'
* @property {Number|Null} stopPrice - If trigger is 'stop,' this must be specified. If not, this should be null.
* @property {Number} quantity
* @property {String} side - 'buy' | 'sell'
* @property {Boolean} extendedHours - Whether the order should be allowed to execute when exchanges are closed (9-9:30 AM, 4-6 PM)
* @property {Boolean} overrideDayTradeCheck - Whether to override Pattern Day Trader protection.
*/
constructor(user, object) {
super();
this.user = user;
if (object.created_at) {
this.response = this._parse(object);
this.executed = true;
} else this.order = object;
}
/**
* Parse an executed order.
* @author Torrey Leonard <https://github.com/Ladinn>
* @private
*/
_parse(object) {
return {
executions: new Array(object.executions),
timeInForce: String(object.time_in_force),
fees: Number(object.fees),
id: String(object.id),
quantity: Number(object.quantity),
averagePrice: Number(object.average_price),
cumulativeQuantity: Number(object.cumulative_quantity),
stopPrice: Number(object.stop_price),
rejectReason: String(object.reject_reason),
state: String(object.state),
trigger: String(object.trigger),
type: String(object.type),
overrideDayTradeCheck: Boolean(object.override_dtbp_checks),
price: Number(object.price),
clientID: String(object.client_id),
extendedHours: Boolean(object.extended_hours),
side: String(object.side),
dates: {
created: new Date(object.created_at),
lastTransaction: new Date(object.last_transaction_at),
updated: new Date(object.updated_at)
},
urls: {
cancel: String(object.cancel),
instrument: String(object.instrument),
account: String(object.account),
order: String(object.url),
position: String(object.position)
}
}
}
/**
* Submits an order to Robinhood to be executed by the exchange.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Promise<Object>}
*/
submit() {
const _this = this;
return new Promise((resolve, reject) => {
if (_this.executed) reject(new Error("Order has already been executed."));
else request.post({
uri: _this.url + "/orders/",
headers: {
'Authorization': 'Bearer ' + _this.user.getAuthToken()
},
form: {
account: _this.url + "/accounts/" + _this.user.getAccountNumber() + "/",
instrument: _this.order.instrument.urls.instrument,
symbol: _this.order.instrument.getSymbol(),
type: _this.order.type,
time_in_force: _this.order.timeInForce,
trigger: _this.order.trigger,
price:
(_this.order.price && _this.order.price.toFixed(2)) ||
_this.order.quote.getLast().toFixed(2),
stop_price: _this.order.stopPrice,
quantity: _this.order.quantity,
side: _this.order.side,
extendedHours: _this.order.extendedHours,
override_day_trade_checks: _this.order.overrideDayTradeCheck,
override_dtbp_checks: _this.order.overrideDayTradeCheck
}
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, body, _this.user.getAuthToken(), res => {
if (res.detail !== undefined || res.reject_reason !== null) reject(new LibraryError(res));
else {
_this.executed = true;
_this.response = this._parse(res);
resolve(res);
}
}, reject);
})
})
}
/**
* Attempts to cancel an order.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Promise<Object>}
*/
cancel() {
const _this = this;
return new Promise((resolve, reject) => {
if (!_this.executed) reject(new Error("Order has not yet been executed."));
else if (_this.response.urls.cancel === null) reject(new Error("Order has been executed and cannot be cancelled."));
else {
request.post({
uri: _this.response.urls.cancel,
headers: {
'Authorization': 'Bearer ' + _this.user.getAuthToken()
}
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, body, _this.user.getAuthToken(), resolve, reject);
})
}
})
}
/**
* Returns a new order object for the specified order ID, if found.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {User} user
* @param {String} orderID
* @returns {Promise<Order>}
*/
static getByOrderID(user, orderID) {
return new Promise((resolve, reject) => {
request({
uri: "https://api.robinhood.com/orders/" + orderID + "/",
headers: {
'Authorization': 'Bearer ' + user.getAuthToken()
}
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, body, user.getAuthToken(), res => {
resolve(new Order(user, res));
}, reject);
})
})
}
/**
* Returns an array of recent orders for the given user object.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {User} user
* @returns {Promise<Array>}
*/
static getRecentOrders(user) {
return new Promise((resolve, reject) => {
request({
uri: "https://api.robinhood.com/orders/",
headers: {
'Authorization': 'Bearer ' + user.getAuthToken()
}
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, body, user.getAuthToken(), res => {
let array = [];
res.forEach(o => {
if (!Array.isArray(o)) array.push(new Order(user, o));
else o.forEach(o2 => {
array.push(new Order(user, o2));
})
});
resolve(array);
}, reject);
})
})
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @param user
* @returns {Promise<any>}
*/
static cancelOpenOrders(user) {
return new Promise((resolve, reject) => {
Order.getRecentOrders(user).then(array => {
async.forEachOf(array, (order, key, callback) => {
if (order.response.urls.cancel !== 'null') {
order.cancel().then(() => {
callback();
}).catch(error => { reject(error) });
} else callback();
}, () => {
resolve();
})
}).catch(error => reject(error));
})
}
/**
* If an order has been executed, this will return the response object.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Object|Null}
*/
getResponse() {
if (this.executed === true) return this.response;
else return null;
}
}
module.exports = Order;