algotrader
Version:
Algorithmically trade stocks and options using Robinhood, Yahoo Finance, and more.
278 lines (252 loc) • 8.01 kB
JavaScript
const Robinhood = require('./Robinhood');
const OptionInstrument = require('./OptionInstrument');
const request = require('request');
const _ = require('lodash');
const uuidv4 = require('uuid/v4');
const assert = require('assert');
/**
* Represents and executes an order for the given option contract.
*/
class OptionOrder extends Robinhood {
/**
* Creates a new OptionOrder.
* @author Torrey Leonard <https://github.com/Ladinn>
* @constructor
* @param {User} user
* @param {Object} object
* @property {String} side - buy/sell
* @property {String} type - market/limit. Note: market orders are not allowed if side = buy.
* @property {Number} price
* @property {String} timeInForce - gtc/gfd/ioc/opg
* @property {OptionInstrument|Null} option - Required if no legs are provided
* @property {Array|Null} legs - Required if no option is provided
* @property {Number} quantity
*/
constructor(user, object) {
super();
this.user = user;
if (object.state === undefined && object.cancel_url === undefined) { // This should be a new order
_validate();
this.executed = false;
let legs = null;
if (!object.legs) {
legs = [{
position_effect: object.side === "buy" ? "open" : "close",
side: object.side,
ratio_quantity: 1,
option: object.option.instrumentURL
}];
} else legs = object.legs;
this.form = {
account: this.url + "/accounts/" + this.user.getAccountNumber() + "/",
direction: object.side === 'buy' ? 'debit' : 'credit',
legs: legs,
price: object.price,
time_in_force: object.timeInForce,
trigger: 'immediate',
type: object.type,
quantity: object.quantity,
override_day_trade_checks: false,
override_dtbp_checks: false,
ref_id: object.ref_id !== undefined ? object.ref_id : uuidv4()
};
} else { // This is an existing order
this.executed = true;
[ 'opening_strategy', 'updated_at', 'ref_id', 'time_in_force', 'response_category', 'chain_symbol', 'id', 'chain_id',
'state', 'trigger', 'legs', 'type', 'direction', 'premium', 'price', 'pending_quantity', 'processed_quantity',
'closing_strategy', 'processed_premium', 'created_at', 'cancel_url', 'canceled_quantity', 'quantity'
].forEach(key => this[key] = object[key]);
}
function _validate() {
assert(typeof user.isAuthenticated === 'function' && user.isAuthenticated(), new Error("Parameter 'user' must be an instance of the User class and authenticated with Robinhood"));
assert(typeof object.side === 'string', new Error("Object property 'side' must be a string"));
assert(object.side === 'buy' || object.side === 'sell', new Error("Object property 'side' must be either 'buy' or 'sell'"));
assert(typeof object.type === 'string', new Error("Object property 'type' must be a string"));
assert(object.type === 'market' || object.type === 'limit', new Error("Object property 'type' must be either 'market' or 'limit'"));
assert(!(object.side === 'buy' && object.type === 'market'), new Error("Robinhood does not allow buy orders to be of type 'market'"));
assert(typeof object.price === 'number', new Error("Object property 'price' must be a number"));
assert(typeof object.timeInForce === 'string', new Error("Object property 'timeInForce' must be a string"));
assert(['gfd', 'gtc', 'ioc', 'opg'].indexOf(object.timeInForce.toLowerCase()) !== -1, new Error("Object property 'timeInForce' must be either GFD, GTC, IOC, or OPG"));
assert(object.option instanceof OptionInstrument || typeof object.legs !== 'undefined', new Error("Object property 'optionInstrument' must be an instance of the OptionInstrument class"));
assert(Array.isArray(object.legs) || typeof object.option !== 'undefined', new Error("Object property 'legs' must be an array. Required parameter if 'option' is undefined"));
assert(typeof object.quantity === 'number', new Error("Object property 'quantity' must be a number"))
}
}
/**
* Submits the OptionOrder to Robinhood and returns the executed OptionOrder.
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Promise<OptionOrder>}
*/
submit() {
const _this = this;
return new Promise((resolve, reject) => {
if (_this.executed) reject('This order has already been executed!');
else request.post({
uri: _this.url + "/options/orders/",
headers: {
'Authorization': 'Bearer ' + _this.user.getAuthToken()
},
json: _this.form,
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, JSON.stringify(body), _this.user.getAuthToken(), res => {
_this.executed = true;
resolve(new OptionOrder(_this.user, res));
}, reject);
})
})
}
/**
* Returns an array of executed OptionOrders.
* NOTE: See OptionInstrument.getPositions for an array of open positions.
* @author Torrey Leonard <https://github.com/Ladinn>
* @param {User} user
* @returns {Promise<OptionOrder[]>}
*/
static getOrders(user) {
return new Promise((resolve, reject) => {
request({
uri: "https://api.robinhood.com/options/orders/",
headers: {
'Authorization': 'Bearer ' + user.getAuthToken()
}
}, (error, response, body) => {
return Robinhood.handleResponse(error, response, body, user.getAuthToken(), res => {
let array = [];
res.forEach(o => {
if (o.state !== undefined) array.push(new OptionOrder(user, o));
});
resolve(_.sortBy(array, 'created_at'));
}, reject);
})
})
}
// GET
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Array}
*/
getLegs() {
return this.legs;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getDirection() {
return this.direction;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getPremium() {
return this.premium;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getProcessedPremium() {
return this.processed_premium;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getTimeInForce() {
return this.time_in_force;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getReferenceID() {
return this.ref_id;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getPrice() {
return Number(this.price);
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getTrigger() {
return this.trigger;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getType() {
return this.type;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getQuantity() {
return Number(this.quantity);
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getQuantityPending() {
return Number(this.pending_quantity);
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Number}
*/
getQuantityCanceled() {
return Number(this.canceled_quantity);
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getChainID() {
return this.chain_id;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {String}
*/
getSymbol() {
return this.chain_symbol;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Date}
*/
getDateCreated() {
return new Date(this.created_at);
}
// BOOLEANS
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Boolean}
*/
isExecuted() {
return this.executed;
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Boolean}
*/
isCredit() {
return this.direction === "credit";
}
/**
* @author Torrey Leonard <https://github.com/Ladinn>
* @returns {Boolean}
*/
isDebit() {
return this.direction === "debit";
}
}
module.exports = OptionOrder;