robinhood-yolo
Version:
NodeJS API wrapper for Robinhood Options trading and CLI to trade from your terminal
1,016 lines (961 loc) • 33.3 kB
JavaScript
;
var ON_DEATH, addAccountCom, api, b64Dec, b64Enc, buyCom, cancelCom, chalk, colorPrint, com, configData, dashboardCom, dataStore, dayTrades, defaults, deleteAccountCom, editAccountCom, editSettingsCom, findCom, inquirer, isNumber, main, moment, notEmpty, overwriteJson, p, parseInt, parsePrice, posCom, posWatchCom, printFind, printInPlace, printOrder, printPos, printQuotes, quoteCom, replaceCom, roundNum, sellCom, showAccountsCom, stopLossWatch, term, terminatePosition, titleCase, tradeCom, updateJson,
indexOf = [].indexOf;
chalk = require('chalk');
com = require('commander');
inquirer = require('inquirer');
p = require('print-tools-js');
moment = require('moment');
term = require('terminal-kit').terminal;
ON_DEATH = require('death');
roundNum = require('./miscFunctions').roundNum;
titleCase = require('./miscFunctions').titleCase;
colorPrint = require('./miscFunctions').colorPrint;
b64Dec = require('./miscFunctions').b64Dec;
b64Enc = require('./miscFunctions').b64Enc;
dataStore = require('./dataStore')({
initData: true
});
updateJson = dataStore.updateJson.bind(dataStore);
overwriteJson = dataStore.overwriteJson.bind(dataStore);
defaults = dataStore.defaults;
configData = dataStore.configData;
//: Init API
api = require('./apiMaster')({
newLogin: false,
configIndex: 0,
print: true
});
//: Main Program
main = function() {
var keys;
//: Option Keys
keys = ['login', 'ticker', 'url', 'id', 'expiry', 'option_type', 'strike_type', 'quantity', 'price', 'depth', 'range', 'command'];
//: Options
return com.option('-l, --login_index <login_index>', 'Change login config index', parseInt).option('-t, --ticker <ticker>', 'Add option ticker').option('-u, --url <url>', 'Add option URL').option('-i, --id <id>', 'Add ID').option('-e, --expiry <expiry>', 'Add option expiry').option('-o, --option_type <option_type>', 'Add option type').option('-s, --strike_type <strike_type>', 'Add option strike type').option('-q, --quantity <quantity>', 'Add option contracts quantity', parseInt).option('-p, --price <price>', 'Add option price', parsePrice).option('-d, --depth <depth>', 'Add option depth', parseInt).option('-r, --range <range>', 'Add option range', parseInt).option('-c, --command_name <command_name>', 'Run command(s) [dashboard, show_accounts, add_account, edit_account, ' + 'delete_account, edit_settings, trades, watch, quote, position, find, buy, sell, cancel, replace]').action(async function() {
var c, error, j, k, key, len, len1, ref, results;
try {
if (com.login_index == null) {
com.login_index = 0;
}
if (configData.length > 0 && (com.command_name != null) && !com.command_name.includes('account') && !['edit_settings'].includes(com.command_name)) {
await api.login({
configIndex: com.login_index
});
}
//: List-type options parsing
for (j = 0, len = keys.length; j < len; j++) {
key = keys[j];
if ((com[key] != null) && indexOf.call(com[key], ',') >= 0) {
com[key] = com[key].split(',');
}
}
//: Commands
if (com.command_name != null) {
if (!Array.isArray(com.command_name)) {
com.command_name = [com.command_name];
}
ref = com.command_name;
results = [];
for (k = 0, len1 = ref.length; k < len1; k++) {
c = ref[k];
//: Dashboard
if (c === 'dashboard') {
results.push(dashboardCom(com));
//: Show Accounts
} else if (c === 'show_accounts') {
results.push(showAccountsCom(com));
//: Add Account
} else if (c === 'add_account') {
results.push(addAccountCom(com));
//: Edit Account
} else if (c === 'edit_account') {
results.push(editAccountCom(com));
//: Delete Account
} else if (c === 'delete_account') {
results.push(deleteAccountCom(com));
//: Edit Settings
} else if (c === 'edit_settings') {
results.push(editSettingsCom(com));
//: Trades
} else if (c === 'trades') {
results.push(tradeCom(com));
//: Position Watch
} else if (c === 'watch') {
results.push(posWatchCom(com));
//: [***DEPRECATED***] Stop Loss
} else if (c === 'stop_loss') {
results.push(p.warning('Stop Loss functionality has been DEPRECATED'));
// stopLossWatch(com, true)
//: [***DEPRECATED***] Stop Loss Simulation (logs only, WILL NOT SELL)
} else if (c === 'stop_loss_sim') {
results.push(p.warning('Stop Loss functionality has been DEPRECATED'));
// stopLossWatch(com, false)
//: Quotes
} else if (c === 'quote') {
results.push(quoteCom(com));
//: Position
} else if (c === 'position') {
results.push(posCom(com));
//: Find Options
} else if (c === 'find') {
results.push(findCom(com));
//: Buy Option
} else if (c === 'buy') {
results.push(buyCom(com));
//: Sell Option
} else if (c === 'sell') {
results.push(sellCom(com));
//: Cancel Option Order
} else if (c === 'cancel') {
results.push(cancelCom(com));
//: Replace Option Order
} else if (c === 'replace') {
results.push(replaceCom(com));
} else {
results.push(void 0);
}
}
return results;
} else {
return console.log(com.helpInformation());
}
} catch (error1) {
error = error1;
throw error;
}
}).parse(process.argv);
};
//::: Commands :::
//: Dashboard Command
dashboardCom = async function(com) {
var account, day_trades, deposits, j, len, results, trade, transfer, transfers, url, withdraws;
account = (await api.getAccount());
transfers = (await api.getTransfers());
withdraws = deposits = 0;
// Transfers
for (j = 0, len = transfers.length; j < len; j++) {
transfer = transfers[j];
if (!transfer.scheduled && transfer.rhs_state === 'submitted' && transfer.direction === 'withdraw') {
withdraws += Number(transfer.amount);
}
if (!transfer.scheduled && transfer.rhs_state === 'submitted' && transfer.direction === 'deposit') {
deposits += Number(transfer.amount);
}
}
// Stats
p.chevron(`Buying Power: ${account.margin_balances.day_trade_buying_power}`, {
log: false
});
p.chevron(`Available for Withdrawal: ${account.margin_balances.cash_available_for_withdrawal}`, {
log: false
});
p.chevron(`Unsettled Funds: ${account.margin_balances.unsettled_funds}`, {
log: false
});
p.chevron(chalk`Deposits: {red $${deposits}}`, {
log: false
});
p.chevron(chalk`Withdrawals: {green $${withdraws}}`, {
log: false
});
// Day Trades
day_trades = (await dayTrades());
p.chevron(`Day Trades: ${(Object.keys(day_trades).length)}`, {
log: false
});
results = [];
for (url in day_trades) {
trade = day_trades[url];
results.push(p.bullet(`${trade.symbol}: ${trade.date}`, {
indent: 1,
log: false
}));
}
return results;
};
//: Show Accounts Command
showAccountsCom = function(com) {
var i, j, ref, results;
results = [];
for (i = j = 0, ref = configData.length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
results.push(p.chevron(`Index ${i}: ${b64Dec(configData[i].u_n)} | ${b64Dec(configData[i].d_t)}`));
}
return results;
};
//: Add Account Commmand
addAccountCom = async function(com) {
var answer, error, newConfig;
p.success('This will add a new account to the config.');
try {
answer = (await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Enter username:'
},
{
type: 'password',
name: 'password',
message: 'Enter password:',
mask: true
},
{
type: 'input',
name: 'device_token',
message: 'Enter device token:'
}
]));
newConfig = {
d_t: b64Enc(answer.device_token),
u_n: b64Enc(answer.username),
p_w: b64Enc(answer.password),
a_t: '',
r_t: '',
a_b: '',
a_u: '',
t_s: 0
};
configData.push(newConfig);
updateJson('yolo_config', configData);
return p.success(`${answer.username} added successfully.`);
} catch (error1) {
error = error1;
return p.error('Could not add account.');
}
};
//: Edit Account Command
editAccountCom = async function(com) {
var acc, account, accounts, answer, error, j, len, newConfig;
p.success('This will edit account details in the config.');
try {
accounts = [];
for (j = 0, len = configData.length; j < len; j++) {
account = configData[j];
accounts.push(b64Dec(account.u_n));
}
//: Get Account
acc = (await inquirer.prompt([
{
type: 'rawlist',
name: 'account',
message: 'Which account would you like to edit?',
choices: accounts
}
]));
//: Edit Account
answer = (await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Enter username:'
},
{
type: 'password',
name: 'password',
message: 'Enter password:',
mask: true
},
{
type: 'input',
name: 'device_token',
message: 'Enter device token:'
}
]));
newConfig = {
d_t: b64Enc(answer.device_token),
u_n: b64Enc(answer.username),
p_w: b64Enc(answer.password)
};
configData[accounts.indexOf(acc.account)] = {...configData[accounts.indexOf(acc.account)], ...newConfig};
updateJson('yolo_config', configData);
return p.success(`${answer.username} edited successfully.`);
} catch (error1) {
error = error1;
return p.error('Could not edit account.');
}
};
//: Delete Account Command
deleteAccountCom = async function(com) {
var acc, account, accounts, error, j, len;
p.success('This will delete an account from the config.');
try {
accounts = [];
for (j = 0, len = configData.length; j < len; j++) {
account = configData[j];
accounts.push(b64Dec(account.u_n));
}
//: Get Account
if (accounts.length > 0) {
acc = (await inquirer.prompt([
{
type: 'rawlist',
name: 'account',
message: 'Which account would you like to delete from the config?',
choices: accounts
}
]));
configData.splice(accounts.indexOf(acc.account), 1);
overwriteJson('yolo_config', configData);
return p.success(`${acc.account} deleted successfully.`);
} else {
return p.error("Config has no accounts.");
}
} catch (error1) {
error = error1;
return p.error('Could not delete account.');
}
};
//: Edit Settings Command
editSettingsCom = async function(com) {
var answer, error, key;
p.success('This will edit settings.');
try {
answer = (await inquirer.prompt([
{
type: 'input',
name: 'stopLoss',
message: 'Enter stop-loss percentage:',
default: defaults.stopLoss,
validate: isNumber,
filter: parsePrice
},
{
type: 'input',
name: 'poorFillTime',
message: 'Enter cut-off time to prevent poor stop-loss fills:',
default: defaults.poorFillTime,
validate: isNumber,
filter: parseInt
}
]));
for (key in answer) {
defaults[key] = answer[key];
}
updateJson('yolo_defaults', defaults);
return p.success('Settings updated successfully.');
} catch (error1) {
error = error1;
return p.error('Could not edit settings.');
}
};
//: Trades Command
tradeCom = async function(com) {
var action, actions, activity, amt, amtColor, defaultAction, defeats, desc, gains, j, k, key, legExpiration, legIndex, len, len1, losses, maxExpiration, net, strategy, strike, strikesText, ticker, trade, uniqueId, wins;
actions = {};
defaultAction = {
date: '',
desc: '',
color: '',
amt: 0
};
gains = losses = wins = defeats = 0;
p.success(`Getting account history for ${api.username}`);
activity = (await api.getHistory({
banking: false,
stock: false
}));
for (j = 0, len = activity.length; j < len; j++) {
action = activity[j];
if (action.type === 'option') {
maxExpiration = '';
ticker = action.ticker;
uniqueId = ticker;
strategy = titleCase(action.strategy);
desc = `${ticker} ${strategy} `;
strikesText = [];
for (legIndex in action.legs) {
if (action.strategy.includes('spread') || action.strategy.includes('condor')) {
legExpiration = action.legs[legIndex].expiration;
if (legExpiration > maxExpiration) {
maxExpiration = legExpiration;
}
} else {
maxExpiration = action.legs[legIndex].expiration;
}
if (Number(legIndex) === 0) {
desc += moment(maxExpiration).format('MM-DD-YYYY') + ' | ';
}
strike = `${action.legs[legIndex].strike}${(action.legs[legIndex].type === 'call' ? 'C' : 'P')}`;
desc += `${strike}${(Number(legIndex) !== action.legs.length - 1 ? ',' : '')} `;
strikesText.push(String(strike) + action.legs[legIndex].expiration);
}
strikesText.sort((a, b) => {
if (a > b) {
return 1;
} else {
return -1;
}
});
for (k = 0, len1 = strikesText.length; k < len1; k++) {
strike = strikesText[k];
uniqueId += strike;
}
if (action.direction === 'credit') {
amt = action.amount;
} else {
amt = action.amount > 0 ? action.amount * -1 : 0;
}
if (actions[uniqueId] == null) {
actions[uniqueId] = {
...defaultAction,
date: action.date,
desc: desc,
amt: amt
};
} else {
if (action.date > actions[uniqueId].date) {
actions[uniqueId].date = action.date;
}
actions[uniqueId].amt += amt;
}
}
}
for (key in actions) {
trade = actions[key];
amtColor = 'green';
if (trade.amt >= 0) {
gains += trade.amt;
wins += 1;
} else if (trade.amt < 0) {
amtColor = 'red';
losses += trade.amt;
defeats += 1;
}
amt = roundNum(trade.amt);
p.bullet(chalk`${trade.desc}| {${amtColor} $${amt}}`, {
log: false
});
}
net = gains + losses;
p.chevron(chalk`{green Gains}: ${gains}`, {
log: false
});
p.chevron(chalk`{red Losses}: ${losses}`, {
log: false
});
if (wins >= defeats) {
p.arrow(chalk`Record: {green ${wins}-${defeats}}`, {
log: false
});
} else {
p.arrow(chalk`Record: {red ${wins}-${defeats}}`, {
log: false
});
}
if (net >= 0) {
return p.arrow(chalk`Net: {green ${net}}`, {
log: false
});
} else {
return p.arrow(chalk`Net: {red ${net}}`, {
log: false
});
}
};
//: Position Watch Command
posWatchCom = async function(com) {
var positions;
p.success('Getting open positions');
positions = (await api.optionsPositions({
marketData: true,
orderData: true
}));
return setInterval(async() => {
var ask_count, ask_price, bid_count, bid_price, cur_time, delta, error, expiry, j, len, option_type, pos, posDet, posLog, posText, posUrl, pos_market, pos_quote, price, quote, spyLog, spyPrice, spy_quote, strike, symbol, theta, volatility, volume;
try {
cur_time = Number(moment().format('HHmmss'));
spy_quote = (await api.quotes('SPY'));
spyPrice = (cur_time >= 93000 && cur_time < 160000) ? roundNum(spy_quote.last_trade_price, 3) : roundNum(spy_quote.last_extended_hours_trade_price, 3);
spyLog = p.bullet(`SPY: ${spyPrice} | Bid - ${spy_quote.bid_size} | Ask - ${spy_quote.ask_size}`, {
ret: true
});
posText = '';
for (j = 0, len = positions.length; j < len; j++) {
pos = positions[j];
pos_quote = (await api.quotes(pos.chain_symbol));
pos_market = (await api.marketData(pos.option_data.id));
symbol = pos.chain_symbol;
option_type = pos.option_data.type === 'call' ? 'C' : 'P';
strike = roundNum(pos.option_data.strike_price, 1);
expiry = pos.option_data.expiration_date.replace(/-/g, '/');
quote = (cur_time >= 93000 && cur_time < 160000) ? roundNum(pos_quote.last_trade_price, 3) : roundNum(pos_quote.last_extended_hours_trade_price, 3);
price = roundNum(pos_market.adjusted_mark_price, 3);
ask_price = roundNum(pos_market.ask_price);
ask_count = pos_market.ask_size;
bid_price = roundNum(pos_market.bid_price);
bid_count = pos_market.bid_size;
delta = roundNum(pos_market.delta, 3);
theta = roundNum(pos_market.theta, 3);
volatility = roundNum(pos_market.implied_volatility, 3);
volume = pos_market.volume;
posLog = p.bullet(`${symbol}-${option_type}-${strike}-${expiry} | Price: ${price} | Quote: ${quote}`, {
ret: true,
log: false
});
posDet = p.chevron(`Bid: ${bid_price} x ${bid_count} | Ask: ${ask_price} x ${ask_count} | Volume: ${volume} | Δ: ${delta} | θ: ${theta} | IV: ${volatility}`, {
ret: true,
log: false,
indent: 1
});
posUrl = p.arrow(`URL: ${pos.option}`, {
ret: true,
log: false,
indent: 1
});
posText += `${posLog}\n${posDet}\n${posUrl}\n`;
}
return printInPlace(`${spyLog}\n${posText}`);
} catch (error1) {
error = error1;
}
}, 2000);
};
//: Stop Loss Watch
stopLossWatch = async function(com, placeOrder = false) {
var MAX_LOSS, TRADE_COUNT, answer, day_trades, market_time, pos_data;
p.success('Getting open positions');
day_trades = (await dayTrades());
TRADE_COUNT = Object.keys(day_trades).length;
MAX_LOSS = defaults.stopLoss;
market_time = moment();
pos_data = {};
p.success(`Starting stop-loss watch: ${market_time.format('HH:mm:ss')}`);
market_time = market_time.format('HHmmss');
if (market_time < 93000 || market_time > 160000) {
p.error('Exiting stop-loss watch, market is not currently open.');
return false;
} else if (market_time >= 93000 && market_time < defaults.poorFillTime) {
answer = (await inquirer.prompt([
{
type: 'rawlist',
name: 'continue',
message: 'Market has just opened and stop-loss could trigger poor fills due to low volume. Continue?',
choices: ['Yes',
'No']
}
]));
if (answer.continue === 'No') {
p.error('Exiting stop-loss watch.');
return false;
}
}
return setInterval(async() => {
var bid_price, buy_price, cur_pos, current_price, error, expiry, high, id, j, len, openPos, option_type, pos, posDet, posLog, posText, positions, sell_args, soldPosition, stop_loss, strike, symbol;
try {
positions = (await api.optionsPositions({
marketData: true
}));
posText = '';
openPos = [];
for (j = 0, len = positions.length; j < len; j++) {
pos = positions[j];
// Data
symbol = pos.chain_symbol;
option_type = pos.option_data.type === 'call' ? 'C' : 'P';
strike = roundNum(pos.option_data.strike_price, 1);
expiry = pos.option_data.expiration_date.replace(/-/g, '/');
current_price = Number(pos.market_data.adjusted_mark_price);
buy_price = Number(pos.average_price) / 100;
bid_price = Number(pos.market_data.bid_price);
high = current_price > buy_price ? current_price : buy_price;
stop_loss = 0;
openPos.push(pos.id);
if (pos_data[pos.id] == null) {
pos_data[pos.id] = {
price: buy_price,
high: high,
status: 'open',
sell_id: null
};
} else {
if (pos_data[pos.id].high < high) {
pos_data[pos.id].high = high;
}
}
// Sell Arguments
sell_args = {
url: pos.option,
quantity: pos.quantity,
price: bid_price,
id: pos_data[pos.id].id
};
cur_pos = pos_data[pos.id];
// High < Price * 1.1
if (cur_pos.high < (cur_pos.price * (1 + MAX_LOSS))) {
stop_loss = roundNum(cur_pos.high - (cur_pos.price * MAX_LOSS));
if (current_price <= stop_loss) {
posText += p.error(`Stop-Loss (Max Loss) triggered: Symbol: ${symbol} | Current Price: ${current_price} | Bid Price: ${bid_price} | Stop Loss: ${stop_loss}`, {
ret: true,
log: true
}) + '\n';
if (TRADE_COUNT < 3 && placeOrder) {
terminatePosition(cur_pos, sell_args);
}
}
// High >= Price * 1.2
} else if (cur_pos.high >= (cur_pos.price * (1 + MAX_LOSS))) {
stop_loss = (cur_pos.high - (cur_pos.price * MAX_LOSS)) >= (cur_pos.price + 0.01) ? roundNum(cur_pos.high - (cur_pos.price * MAX_LOSS)) : roundNum(cur_pos.price + 0.01);
if (current_price <= stop_loss) {
posText += p.error(`Stop-Loss (Preserve Gains) triggered: Symbol: ${symbol} | Current Price: ${current_price} | Bid Price: ${bid_price} | Stop Loss: ${stop_loss}`, {
ret: true,
log: true
}) + '\n';
if (TRADE_COUNT < 3 && placeOrder) {
terminatePosition(cur_pos, sell_args);
}
}
}
// Log
posLog = p.chevron(`${symbol}-${option_type}-${strike}-${expiry} | Buy Price: ${buy_price}`, {
ret: true,
log: true
});
posDet = p.bullet(`Current Price: ${current_price} | Bid Price: ${bid_price} | Stop Loss: ${stop_loss}`, {
ret: true,
log: false,
indent: 1
});
posText += `${posLog}\n${posDet}\n`;
}
soldPosition = false;
for (id in pos_data) {
pos = pos_data[id];
if (!openPos.includes(id) && pos.status === 'selling') {
soldPosition = true;
delete pos_data[id];
}
}
if (soldPosition) {
day_trades = (await dayTrades());
TRADE_COUNT = Object.keys(day_trades).length;
}
return printInPlace(posText);
} catch (error1) {
error = error1;
}
}, 5000);
};
//: Quote Command
quoteCom = async function(com) {
var data;
p.success(`Getting quote for ${com.ticker}`);
data = (await api.quotes(com.ticker));
return printQuotes(data);
};
//: Position Command
posCom = async function(com) {
var data;
p.success('Getting open positions');
data = (await api.optionsPositions({
marketData: true,
orderData: true
}));
printPos(data);
p.success('Getting unfilled orders');
data = (await api.optionsPositions({
marketData: true,
orderData: true,
openOnly: false,
notFilled: true
}));
return printPos(data, true);
};
//: Find Command
findCom = async function(com) {
var args, data;
args = {
symbol: com.ticker,
expiry: com.expiry,
optionType: com.option_type || 'call',
strikeType: com.strike_type || 'itm',
strikeDepth: com.depth || 0,
range: com.range,
price: com.price
};
if ((args.range == null) && (args.price == null)) {
p.success(`Finding option ${args.symbol}-${args.expiry.replace(/-/g, '/')}-${args.optionType}-${args.strikeType}-${args.strikeDepth}`);
} else if (args.price != null) {
p.success(`Finding option ${args.symbol}-${args.expiry.replace(/-/g, '/')}-${args.optionType}-${args.price}`);
} else if (args.range != null) {
p.success(`Finding option ${args.symbol}-${args.expiry.replace(/-/g, '/')}-${args.optionType}-(+-${args.range})`);
}
data = (await api.findOptions(args.symbol, args.expiry, {
optionType: args.optionType,
strikeType: args.strikeType,
strikeDepth: args.strikeDepth,
range: args.range,
strike: args.price,
marketData: true
}));
return printFind(data);
};
//: Buy Command
buyCom = async function(com) {
var buy, option, quote;
option = (await api.getUrl(com.url));
p.success(`Attempting to buy ${option.chain_symbol}-${option.expiration_date}-${option.type}-${option.strike_price}`);
buy = (await api.placeOptionOrder(com.url, com.quantity, com.price));
quote = (await api.quotes(option.chain_symbol));
if (buy.id != null) {
p.success('Order placed successfully');
printOrder(option, quote, buy);
return buy;
} else {
p.error('Could not place order!');
return false;
}
};
//: Sell Command
sellCom = async function(com) {
var option, quote, sell;
option = (await api.getUrl(com.url));
p.success(`Attempting to sell ${option.chain_symbol}-${option.expiration_date}-${option.type}-${option.strike_price}`);
sell = (await api.placeOptionOrder(com.url, com.quantity, com.price, {
direction: 'credit',
side: 'sell',
positionEffect: 'close'
}));
quote = (await api.quotes(option.chain_symbol));
if (sell.id != null) {
p.success('Order placed successfully');
printOrder(option, quote, sell);
return sell;
} else {
p.error('Could not place order!');
return false;
}
};
//: Cancel Command
cancelCom = async function(com) {
var data;
p.success("Attempting to cancel option order");
data = (await api.cancelOptionOrder(com.url));
if (data) {
p.success('Order cancelled successfully');
return true;
}
};
//: Replace Command
replaceCom = async function(com) {
var buy, option, order, quote;
order = (await api.optionsOrders({
id: com.id
}));
option = (await api.getUrl(order.legs[0].option));
p.success(`Attempting to replace order ${option.chain_symbol}-${option.expiration_date}-${option.type}-${option.strike_price}`);
buy = (await api.replaceOptionOrder(com.quantity, com.price, {
order: order
}));
quote = (await api.quotes(option.chain_symbol));
if (buy.id != null) {
p.success('Order replaced successfully');
printOrder(option, quote, buy);
return buy;
} else {
p.error('Could not replace order!');
return false;
}
};
//::: Print Methods :::
//: Print Quotes
printQuotes = function(data) {
var j, len, obj, objOrder, rec, results;
if (!Array.isArray(data)) {
data = [data];
}
results = [];
for (j = 0, len = data.length; j < len; j++) {
rec = data[j];
obj = {
symbol: rec.symbol,
askSize: rec.ask_size,
bidSize: rec.bid_size,
price: rec.last_trade_price,
extendedPrice: rec.last_extended_hours_trade_price
};
objOrder = ['symbol', 'price', 'extendedPrice', 'askSize', 'bidSize'];
results.push(colorPrint(obj, objOrder));
}
return results;
};
//: Print Position
printPos = function(data, notFilled = false) {
var j, len, obj, objOrder, rec, results;
results = [];
for (j = 0, len = data.length; j < len; j++) {
rec = data[j];
obj = {
symbol: rec.chain_symbol,
buyPrice: rec.average_price,
strikePrice: rec.option_data.strike_price,
expiry: rec.option_data.expiration_date,
quantity: rec.quantity,
type: rec.order_data.opening_strategy,
bidPrice: rec.market_data.bid_price,
bidSize: rec.market_data.bid_size,
askPrice: rec.market_data.ask_price,
askSize: rec.market_data.ask_size,
optionPrice: rec.market_data.adjusted_mark_price,
volume: rec.market_data.volume,
openInterest: rec.market_data.open_interest,
impliedVolatility: rec.market_data.implied_volatility,
delta: rec.market_data.delta,
theta: rec.market_data.theta,
orderId: rec.order_data.id,
optionUrl: rec.option
};
objOrder = ['symbol', 'buyPrice', 'strikePrice', 'expiry', 'quantity', 'type', 'delta', 'theta', 'bidPrice', 'bidSize', 'askPrice', 'askSize', 'optionPrice', 'volume', 'openInterest', 'impliedVolatility', 'orderId', 'optionUrl'];
if (notFilled) {
obj.cancelUrl = rec.order_data.cancel_url;
objOrder.push('cancelUrl');
}
results.push(colorPrint(obj, objOrder));
}
return results;
};
//: Print Find Options
printFind = function(data) {
var j, len, obj, objOrder, rec, results;
if (!Array.isArray(data)) {
data = [data];
}
results = [];
for (j = 0, len = data.length; j < len; j++) {
rec = data[j];
obj = {
symbol: rec.chain_symbol,
strikePrice: rec.strike_price,
expiry: rec.expiration_date,
bidPrice: rec.market_data.bid_price,
bidSize: rec.market_data.bid_size,
askPrice: rec.market_data.ask_price,
askSize: rec.market_data.ask_size,
optionPrice: rec.market_data.adjusted_mark_price,
volume: rec.market_data.volume,
openInterest: rec.market_data.open_interest,
impliedVolatility: rec.market_data.implied_volatility,
delta: rec.market_data.delta,
theta: rec.market_data.theta,
url: rec.url
};
objOrder = ['symbol', 'strikePrice', 'expiry', 'delta', 'theta', 'bidPrice', 'bidSize', 'askPrice', 'askSize', 'optionPrice', 'volume', 'openInterest', 'impliedVolatility', 'url'];
results.push(colorPrint(obj, objOrder));
}
return results;
};
//: Print Order
printOrder = function(option, quote, buy) {
var obj, objOrder;
obj = {
symbol: buy.chain_symbol,
strategy: buy.opening_strategy,
strikePrice: option.strike_price,
expiry: option.expiration_date,
price: buy.price,
quote: quote.last_trade_price,
quantity: buy.quantity,
cancelUrl: buy.cancel_url,
orderId: buy.id
};
objOrder = ['symbol', 'strategy', 'expiry', 'strikePrice', 'price', 'quote', 'quantity', 'cancelUrl', 'orderId'];
return colorPrint(obj, objOrder);
};
//::: Helpers :::
//: Check if number
isNumber = function(text) {
if (text.length === 0 || !isNaN(text)) {
return true;
} else {
return 'Please enter a valid number.';
}
};
//: Check if not empty
notEmpty = function(text) {
if (text.length > 0) {
return true;
} else {
return 'Please enter a valid string.';
}
};
//: Parse Int
parseInt = function(val) {
return roundNum(val, 0);
};
//: Parse Price
parsePrice = function(val) {
return roundNum(val);
};
//: Print in Place
printInPlace = function(text) {
term.saveCursor();
process.stdout.write(' '.repeat(process.stdout.columns).repeat(text.split('\n').length));
term.restoreCursor();
process.stdout.write(`${text}\r`);
return term.restoreCursor();
};
//: Terminate Position
terminatePosition = async function(cur_pos, sell_args) {
var order;
if (cur_pos.status === 'open') {
order = (await sellCom(sell_args));
cur_pos.status = 'selling';
return cur_pos.sell_id = order.id;
} else if (cur_pos.status === 'selling') {
order = (await replaceCom(sell_args));
return cur_pos.sell_id = order.id;
}
};
//: Get Day Trades
dayTrades = async function() {
var buy_date, cutoff_date, date, date_status, day_trades, days, i, j, k, l, orders, ref, ref1, ref2, sell_date, x;
orders = (await api.optionsOrders());
cutoff_date = 0;
day_trades = {};
days = [];
for (i = j = 0; j < 14; i = ++j) {
if (days.length < 5) {
date = moment().subtract(i, 'days').format('YYYY-MM-DD');
date_status = (await api.getMarketHours(date));
if ((date_status.is_open != null) && date_status.is_open) {
days.push(date_status.date);
}
if (days.length === 5) {
cutoff_date = Number(moment().subtract(i, 'days').format('YYYYMMDD'));
}
} else {
break;
}
}
for (i = k = 0, ref = orders.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
if (orders[i].legs[0].executions.length > 0 && Number(moment(orders[i].legs[0].executions[0].timestamp).format('YYYYMMDD')) < cutoff_date) {
break;
}
if (orders[i].state === 'filled' && orders[i].legs[0].executions.length > 0 && orders[i].legs[0].side === 'sell' && orders[i].legs[0].position_effect === 'close') {
sell_date = moment(orders[i].legs[0].executions[0].timestamp).format('YYYY-MM-DD');
if (days.includes(sell_date)) {
for (x = l = ref1 = i + 1, ref2 = orders.length; (ref1 <= ref2 ? l < ref2 : l > ref2); x = ref1 <= ref2 ? ++l : --l) {
if (orders[x].state === 'filled' && orders[i].legs[0].option === orders[x].legs[0].option && orders[x].legs[0].executions.length > 0 && orders[x].legs[0].side === 'buy' && orders[x].legs[0].position_effect === 'open') {
buy_date = moment(orders[x].legs[0].executions[0].timestamp).format('YYYY-MM-DD');
if (buy_date === sell_date) {
day_trades[orders[i].legs[0].option] = {
date: sell_date,
symbol: orders[i].chain_symbol
};
}
break;
}
}
}
}
}
return day_trades;
};
//: Run Program
ON_DEATH(function(signal, err) {
term.eraseDisplayBelow();
return process.exit();
});
main();
//::: End Program :::