Skip to content
This repository has been archived by the owner on Feb 15, 2022. It is now read-only.

Commit

Permalink
Always use lowest/highest price for buy/sell loss protection (#1471)
Browse files Browse the repository at this point in the history
* Always use lowest/highest price for buy/sell loss protection

On consecutive buys/sells the bot would always use the last price for loss protection, which could lead to a potential (sic) loss if the last price is below/above (depending on the order type) any previous price of the same order type.

To overcome this we will lookup all previous orders (of the same type) and adjust last price to the highest/lowest within the previous orders.

Will this isn't a true FIFO (as we don't account for any amount bought/sold) it goes in the same direction by always "aiming" for a profit.

This completely removes held_pct, which i presume was supposed to accomplish the same but seems to be causing a bug preventing the bot from doing consequtive buys.

Also some linter fixes will i am already at it.

* linter

Looks my master has different likings.

* Refactor sell/loss protection update

* Use lodash instead of legacy
* Completely replaces the semi static last_buy|sell_price for loss protection
* Include previous trades, if enabled/available
* Always use the lowest/highest (aka. worst) price from within previous orders of the opposite signal, even on repeating signals

* Typo in var

duh
  • Loading branch information
defkev authored and DeviaVir committed Mar 12, 2018
1 parent 10360cb commit 91cc45d
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 22 deletions.
2 changes: 1 addition & 1 deletion commands/trade.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ module.exports = function (program, conf) {
my_trades.find({selector: so.selector.normalized, time : {$gte : trades[0].time}}).limit(0).toArray(function (err, my_prev_trades) {
if (err) throw err
if (my_prev_trades.length) {
s.my_prev_trades = my_prev_trades.slice(0).sort(function(a,b){return a.time + a.execution_time > b.time + b.execution_time ? -1 : 1}) // simple copy, most recent executed first
s.my_prev_trades = my_prev_trades.slice(0).sort(function(a,b){return a.time + a.execution_time < b.time + b.execution_time ? -1 : 1}) // simple copy, less recent executed first
}
})
}
Expand Down
43 changes: 23 additions & 20 deletions lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ module.exports = function (s, conf) {
if (s.my_trades.length) {
last_trade = s.my_trades[s.my_trades.length - 1]
} else {
last_trade = s.my_prev_trades[0]
last_trade = s.my_prev_trades[s.my_prev_trades.length - 1]
}
s.last_trade_worth = last_trade.type === 'buy' ? (s.period.close - last_trade.price) / last_trade.price : (last_trade.price - s.period.close) / last_trade.price
if (!s.acted_on_stop) {
Expand Down Expand Up @@ -341,7 +341,7 @@ module.exports = function (s, conf) {
// 8. if not filled after timer, repeat process
// 9. if filled, record order stats
function executeSignal (signal, _cb, size, is_reorder, is_taker) {
let price, expected_fee, buy_pct, sell_pct
let price, expected_fee, buy_pct, sell_pct, trades
delete s[(signal === 'buy' ? 'sell' : 'buy') + '_order']
s.last_signal = signal
if (!is_reorder && s[signal + '_order']) {
Expand Down Expand Up @@ -392,6 +392,11 @@ module.exports = function (s, conf) {
msg('price changed, resizing order, ' + reorder_pct + '% remain')
size = null
}
if (s.my_prev_trades.length) {
trades = _.concat(s.my_prev_trades, s.my_trades)
} else {
trades = _.cloneDeep(s.my_trades)
}
if (signal === 'buy') {
price = nextBuyForQuote(s, quote)

Expand All @@ -406,10 +411,6 @@ module.exports = function (s, conf) {
let buy_max_as_pct = n(adjusted_buy_max_amt).divide(s.balance.currency).multiply(100).value()
buy_pct = buy_max_as_pct
}
} else { // account for held assets as %
let held_pct = n(s.asset_capital).divide(s.balance.currency).multiply(100).value()
let to_buy_pct = n(buy_pct).subtract(held_pct).value()
buy_pct = to_buy_pct
}
if (so.use_fee_asset) {
fee = 0
Expand All @@ -432,13 +433,8 @@ module.exports = function (s, conf) {
size = s.product.max_size
}
msg('preparing buy order over ' + fa(size) + ' of ' + fc(tradeable_balance) + ' (' + buy_pct + '%) tradeable balance with a expected fee of ' + fc(expected_fee) + ' (' + fee + '%)')
if (!s.last_sell_price && s.my_prev_trades.length) {
var prev_sells = s.my_prev_trades.filter(trade => trade.type === 'sell')
if (prev_sells.length) {
s.last_sell_price = prev_sells[0].price
}
}
let buy_loss = s.last_sell_price ? (s.last_sell_price - Number(price)) / s.last_sell_price * -100 : null
let latest_low_sell = _.chain(trades).dropRightWhile(['type','buy']).takeRightWhile(['type','sell']).sortBy(['price']).head().value() // return lowest price
let buy_loss = latest_low_sell ? (latest_low_sell.price - Number(price)) / latest_low_sell.price * -100 : null
if (so.max_buy_loss_pct != null && buy_loss > so.max_buy_loss_pct) {
let err = new Error('\nloss protection')
err.desc = 'refusing to buy at ' + fc(price) + ', buy loss of ' + pct(buy_loss / 100)
Expand Down Expand Up @@ -485,13 +481,8 @@ module.exports = function (s, conf) {
if (s.product.max_size && Number(size) > Number(s.product.max_size)) {
size = s.product.max_size
}
if (!s.last_buy_price && s.my_prev_trades.length) {
var prev_buys = s.my_prev_trades.filter(trade => trade.type === 'buy')
if (prev_buys.length) {
s.last_buy_price = prev_buys[0].price
}
}
let sell_loss = s.last_buy_price ? (Number(price) - s.last_buy_price) / s.last_buy_price * -100 : null
let latest_high_buy = _.chain(trades).dropRightWhile(['type','sell']).takeRightWhile(['type','buy']).sortBy(['price']).reverse().head().value() // return highest price
let sell_loss = latest_high_buy ? (Number(price) - latest_high_buy.price) / latest_high_buy.price * -100 : null
if (so.max_sell_loss_pct != null && sell_loss > so.max_sell_loss_pct) {
let err = new Error('\nloss protection')
err.desc = 'refusing to sell at ' + fc(price) + ', sell loss of ' + pct(sell_loss / 100)
Expand Down Expand Up @@ -588,6 +579,12 @@ module.exports = function (s, conf) {
}
}
s.action = 'bought'
if (!s.last_sell_price && s.my_prev_trades.length) {
let prev_sells = s.my_prev_trades.filter(trade => trade.type === 'sell')
if (prev_sells.length) {
s.last_sell_price = prev_sells[prev_sells.length - 1].price
}
}
let my_trade = {
order_id: trade.order_id,
time: trade.time,
Expand Down Expand Up @@ -631,6 +628,12 @@ module.exports = function (s, conf) {
}
}
s.action = 'sold'
if (!s.last_buy_price && s.my_prev_trades.length) {
let prev_buys = s.my_prev_trades.filter(trade => trade.type === 'buy')
if (prev_buys.length) {
s.last_buy_price = prev_buys[prev_buys.length - 1].price
}
}
let my_trade = {
order_id: trade.order_id,
time: trade.time,
Expand Down
2 changes: 1 addition & 1 deletion templates/dashboard.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@
<% }); %>
<% } %>
<% if (my_prev_trades) { %>
<% my_prev_trades.sort(function(a,b){return a.time > b.time ? -1 : 1;}).forEach(function(trade){ %>
<% my_prev_trades.reverse().forEach(function(trade){ %>
<tr class="text-muted">
<td><%= trade.type.toUpperCase() %></span></td>
<td><%= new Intl.NumberFormat("en-US", {useGrouping: false, minimumFractionDigits: 8, maximumFractionDigits: 8}).format(trade.size) %> <%= asset.toUpperCase() %></td>
Expand Down

0 comments on commit 91cc45d

Please sign in to comment.