oanda-stats
Version:
A CLI offering trading stats based on your Oanda Forex account.
254 lines (207 loc) • 7.07 kB
JavaScript
var _ = require("underscore");
var request = require("request");
var path = require("path");
var pstack = require("pstack");
var fs = require("fs");
var fstool = require("fs-tool");
var querystring = require('querystring');
Trading = function(core, params, req, res) {
var lib = {};
lib = {
trades: function(callback) {
fstool.file.readJson(core.cli.filename(core.settings, 'history-'+core.settings.OANDA_ACC+'.json'), function(transactions) {
callback(transactions);
});
},
processTrades: function(transactions) {
var stats = {};
/*
** How that clusterfuck of a schema works (thanks for nothing Oanda engineers) **
Position open (open):
type: MARKET_ORDER
tradeClose: Doesn't exist
Open Order fill (fill_open):
type: ORDER_FILL
tradeOpened: Exists
tradesClosed: Doesn't exist
orderID: open.id
Take Profit Settings (tp):
type: TAKE_PROFIT_ORDER
tradeID: fill_open.id
Stop Loss Settings (sl):
type: STOP_LOSS_ORDER
tradeID: fill_open.id
Position close (close):
type: MARKET_ORDER
tradeClose.tradeID: fill_open.id
Close Order fill (fill_close):
type: ORDER_FILL
tradeOpened: Doesn't exist
tradesClosed: Exists
orderID: close.id
Take Profit Cancel (tp_cancel):
type: ORDER_CANCEL
orderID: tp.id
Stop Loss Cancel (tp_cancel):
type: ORDER_CANCEL
orderID: sl.id
Order Cancel (order_cancel):
type: ORDER_CANCEL
orderID: open.id
*/
var filterAndGroup = function(data, filter, group) {
var items = _.filter(data, filter);
return _.indexBy(items, group);
}
var open = _.filter(transactions, function(item) {
return item.type == 'MARKET_ORDER' && !item.tradeClose;
});
var fill_open = filterAndGroup(transactions, function(item) {
return item.type == 'ORDER_FILL' && item.tradeOpened && !item.tradesClosed;
}, function(item) {
return item.orderID;
});
var fill_close = filterAndGroup(transactions, function(item) {
return item.type == 'ORDER_FILL' && !item.tradeOpened && item.tradesClosed;
}, function(item) {
return item.orderID;
});
var tp = filterAndGroup(transactions, function(item) {
return item.type == 'TAKE_PROFIT_ORDER';
}, function(item) {
return item.tradeID;
});
var sl = filterAndGroup(transactions, function(item) {
return item.type == 'STOP_LOSS_ORDER';
}, function(item) {
return item.tradeID;
});
var close = filterAndGroup(transactions, function(item) {
return item.type == 'MARKET_ORDER' && item.tradeClose && item.tradeClose.tradeID;
}, function(item) {
return item.tradeClose.tradeID;
});
var cancel = filterAndGroup(transactions, function(item) {
return item.type == 'ORDER_CANCEL';
}, function(item) {
return item.orderID;
});
var positions = _.map(open, function(item) {
var obj = {
instrument: item.instrument,
open_time: item.time,
close_time: null,
cancel_time: null,
cancel: null,
units: parseFloat(item.units),
type: parseFloat(item.units)>0?'BUY':'SELL',
open: null,
close: null,
position_value: null,
margin_used: null,
pl: null,
pip: null,
close_reason: null,
balance_open: null,
balance_close: null,
take_profit: null,
stop_loss: null,
financing: null,
spreadCost: null,
ids: {
open: item.id,
open_fill: null,
close: null,
close_fill: null,
tp: null,
sl: null
}
};
// Assemble the data
// Open Fill
var _fill_open = fill_open[item.id];
if (_fill_open) {
obj.open = parseFloat(_fill_open.price);
obj.balance_open = parseFloat(_fill_open.accountBalance);
obj.ids.open_fill = _fill_open.id;
obj.position_value = obj.open*parseFloat(item.units);
obj.margin_used = obj.position_value/obj.balance_open*100;
// Take Profit
var _tp = tp[_fill_open.id];
if (_tp) {
obj.take_profit = parseFloat(_tp.price);
obj.ids.tp = _tp.id;
// Did it execute?
var _fill_close = fill_close[_tp.id];
if (_fill_close) {
obj.close = parseFloat(_fill_close.price);
obj.balance_close = parseFloat(_fill_close.accountBalance);
obj.close_time = _fill_close.time;
obj.close_reason = _fill_close.reason||'take-profit';
obj.pl = parseFloat(_fill_close.pl);
obj.spread = parseFloat(_fill_close.halfSpreadCost);
obj.financing = Math.abs(parseFloat(_fill_close.financing));
obj.ids.close_fill = _fill_close.id;
}
}
// Stop Loss
var _sl = sl[_fill_open.id];
if (_sl) {
obj.stop_loss = parseFloat(_sl.price);
obj.ids.sl = _sl.id;
// Did it execute?
var _fill_close = fill_close[_sl.id];
if (_fill_close) {
obj.close = parseFloat(_fill_close.price);
obj.balance_close = parseFloat(_fill_close.accountBalance);
obj.close_time = _fill_close.time;
obj.close_reason = _fill_close.reason||'stop-loss';
obj.pl = parseFloat(_fill_close.pl);
obj.spread = parseFloat(_fill_close.halfSpreadCost);
obj.financing = Math.abs(parseFloat(_fill_close.financing));
obj.ids.close_fill = _fill_close.id;
}
}
// Close
var _close = close[_fill_open.id];
if (_close) {
obj.ids.close = _close.id;
// Close Fill
var _fill_close = fill_close[_close.id];
if (_fill_close) {
obj.close = parseFloat(_fill_close.price);
obj.balance_close = parseFloat(_fill_close.accountBalance);
obj.close_time = _fill_close.time;
obj.close_reason = _fill_close.reason||'unknown';
obj.pl = parseFloat(_fill_close.pl);
obj.spread = parseFloat(_fill_close.halfSpreadCost);
obj.financing = Math.abs(parseFloat(_fill_close.financing));
obj.ids.close_fill = _fill_close.id;
}
}
}
// Cancel
var _cancel = cancel[item.id];
if (_cancel) {
obj.cancel_time = _cancel.time;
obj.cancel = _cancel.reason;
}
// Pip calculation
var jpy_regex = /jpy/gmi;
if (jpy_regex.test(obj.instrument)) {
var pip = 0.01;
} else {
var pip = 0.0001;
}
obj.pip = Math.floor((obj.close-obj.open)/pip) * (parseFloat(item.units)>0?1:-1);
return obj;
});
positions.sort(function(a, b) {
return new Date(a.open_time).getTime()-new Date(b.open_time).getTime();
});
return positions;
},
};
return lib;
}
module.exports = Trading;