Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle min amt for acc/dis algo orders #155

Merged
merged 11 commits into from
Aug 12, 2021
16 changes: 10 additions & 6 deletions lib/accumulate_distribute/events/life_start.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict'

const HFI = require('bfx-hf-indicators')
const _isEmpty = require('lodash/isEmpty')
const scheduleTick = require('../util/schedule_tick')
const hasIndicatorCap = require('../util/has_indicator_cap')
const hasIndicatorOffset = require('../util/has_indicator_offset')
const getMinMaxDistortedAmount = require('../../util/get_min_max_distorted_amount')

/**
* If needed, creates necessary indicators for price offset & cap calculation
Expand All @@ -23,7 +23,7 @@ const hasIndicatorOffset = require('../util/has_indicator_offset')
*/
const onLifeStart = async (instance = {}) => {
const { state = {}, h = {} } = instance
const { args = {}, orderAmounts, remainingAmount } = state
const { args = {}, orderAmounts, remainingAmount, pairConfig } = state
const { debug, updateState, sendPing, subscribeDataChannels } = h
const { amount, relativeCap, relativeOffset } = args

Expand All @@ -32,7 +32,13 @@ const onLifeStart = async (instance = {}) => {
amount, orderAmounts, remainingAmount
)

const updateOpts = {}
const { minDistortedAmount, maxDistortedAmount } = getMinMaxDistortedAmount(args, pairConfig)

const updateOpts = {
minDistortedAmount,
maxDistortedAmount
}

const hasOffsetIndicator = hasIndicatorOffset(args)
const hasCapIndicator = hasIndicatorCap(args)

Expand Down Expand Up @@ -62,9 +68,7 @@ const onLifeStart = async (instance = {}) => {
updateOpts.capIndicator = capIndicator
}

if (!_isEmpty(updateOpts)) {
await updateState(instance, updateOpts)
}
await updateState(instance, updateOpts)

subscribeDataChannels(state)
.catch(e => debug('failed to subscribe to data channels: %s', e.message))
Expand Down
9 changes: 5 additions & 4 deletions lib/accumulate_distribute/events/orders_order_fill.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const { Config } = require('bfx-api-node-core')
const { nBN } = require('@bitfinex/lib-js-util-math')
const { DUST } = Config

const scheduleTick = require('../util/schedule_tick')
Expand All @@ -23,14 +24,14 @@ const scheduleTick = require('../util/schedule_tick')
*/
const onOrdersOrderFill = async (instance = {}, order) => {
const { state = {}, h = {} } = instance
const { args = {}, ordersBehind, timeout, currentOrder } = state
const { args = {}, ordersBehind, timeout, currentOrder, minDistortedAmount } = state
const { emit, updateState, debug } = h
const { catchUp } = args

const newOrdersBehind = Math.max(0, ordersBehind - 1)
const fillAmount = order.getLastFillAmount()
const remainingAmount = state.remainingAmount - fillAmount
const absRem = Math.abs(remainingAmount)
const remainingAmount = nBN(state.remainingAmount).minus(fillAmount).toNumber()
const absRem = nBN(state.remainingAmount).abs().minus(nBN(fillAmount).abs()).toNumber()

order.resetFilledAmount()

Expand All @@ -42,7 +43,7 @@ const onOrdersOrderFill = async (instance = {}, order) => {
currentOrder: currentOrder + 1
})

if (absRem <= DUST) { // stop if finished
if (absRem <= DUST || absRem < Math.abs(minDistortedAmount)) { // stop if finished
vigan-abd marked this conversation as resolved.
Show resolved Hide resolved
if (absRem < 0) {
debug('warning: overfill! %f', absRem)
}
Expand Down
6 changes: 4 additions & 2 deletions lib/accumulate_distribute/meta/get_ui_def.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ const getUIDef = () => ({
'subsequent orders.\n\nEnabling the \'Catch Up\' flag will cause the',
'algorithm to ignore the slice interval for the next order if previous',
'orders have taken longer than expected to fill, thereby ensuring the',
'time-to-fill for the entire order is not adversely affected.\n\nNote:',
'When \'catching up\', the slice interval is hard-coded to 0.2 seconds'
'time-to-fill for the entire order is not adversely affected. Furthermore,',
'when \'catching up\', the slice interval is hard-coded to 0.2 seconds.',
'\n\nNote: If the remaining order amount is less than the minimum order size,',
'it will be ignored.'
].join(' '),

connectionTimeout: 10000,
Expand Down
10 changes: 6 additions & 4 deletions lib/accumulate_distribute/meta/init_state.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { nBN } = require('@bitfinex/lib-js-util-math')
const { Config } = require('bfx-api-node-core')
const { DUST } = Config

Expand All @@ -17,19 +18,20 @@ const genOrderAmounts = require('../util/gen_order_amounts')
* @param {object} args - instance execution parameters
* @returns {object} initialState
*/
const initState = (args = {}) => {
const initState = (args = {}, pairConfig = {}) => {
const { amount } = args
const orderAmounts = genOrderAmounts.gen(args)
const orderAmounts = genOrderAmounts.gen(args, pairConfig)

let totalAmount = 0
orderAmounts.forEach(a => { totalAmount += a })
orderAmounts.forEach(a => { totalAmount = nBN(totalAmount).plus(a).toNumber() })

if (Math.abs(totalAmount - amount) > DUST) {
if (Math.abs(totalAmount) - Math.abs(amount) > DUST) {
vigan-abd marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`total order amount is too large: ${totalAmount} > ${amount}`)
}

return {
args,
pairConfig,
orderAmounts,
currentOrder: 0,
ordersBehind: 0,
Expand Down
50 changes: 28 additions & 22 deletions lib/accumulate_distribute/util/gen_order_amounts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
'use strict'

const _isFinite = require('lodash/isFinite')
const { prepareAmount } = require('bfx-api-node-util')
const { Config } = require('bfx-api-node-core')
const { prepareAmount } = require('bfx-api-node-util')
const getMinMaxDistortedAmount = require('../../util/get_min_max_distorted_amount')
const getRandomNumberInRange = require('../../util/get_random_number_in_range')

const { DUST } = Config

/**
Expand All @@ -15,30 +18,33 @@ const { DUST } = Config
* @param {number} args.amount - total order amount
* @param {number} args.sliceAmount - individual slice amount
* @param {number} args.amountDistortion - desired distortion in %
* @param {object} pairConfig - configs of the selected market pair
* @param {number} pairConfig.minSize - minimum order size of the selected market pair
* @param {number} pairConfig.maxSize - maximum order size of the selected market pair
* @returns {number[]} orderAmounts
*/
const generateOrderAmounts = (args = {}) => {
const { amount, sliceAmount, amountDistortion } = args
let orderAmounts = []

if (_isFinite(amountDistortion)) {
let totalAmount = 0

while (Math.abs(amount - totalAmount) > DUST) {
const m = Math.random() > 0.5 ? 1 : -1
const orderAmount = sliceAmount * (1 + (Math.random() * amountDistortion * m))
const remAmount = amount - totalAmount
const cappedOrderAmount = +prepareAmount(remAmount < 0
? Math.max(remAmount, orderAmount)
: Math.min(remAmount, orderAmount)
)

orderAmounts.push(cappedOrderAmount)
totalAmount += cappedOrderAmount
const generateOrderAmounts = (args = {}, pairConfig = {}) => {
const { amount } = args
const { minSize } = pairConfig

const { minDistortedAmount, maxDistortedAmount } = getMinMaxDistortedAmount(args, pairConfig)

let totalAmount = 0
const orderAmounts = []

while (Math.abs(amount) - Math.abs(totalAmount) > DUST) {
avsek477 marked this conversation as resolved.
Show resolved Hide resolved
const orderAmount = getRandomNumberInRange(minDistortedAmount, maxDistortedAmount)
const remAmount = +prepareAmount(amount - totalAmount)
const cappedOrderAmount = remAmount < 0
? Math.max(remAmount, orderAmount)
: Math.min(remAmount, orderAmount)

if (_isFinite(minSize) && Math.abs(cappedOrderAmount) < minSize) {
break
}
} else {
const n = Math.ceil(amount / sliceAmount)
orderAmounts = Array.apply(null, Array(n)).map(() => sliceAmount)

orderAmounts.push(cappedOrderAmount)
totalAmount = +prepareAmount(totalAmount + cappedOrderAmount)
}

return orderAmounts
Expand Down
5 changes: 3 additions & 2 deletions lib/accumulate_distribute/util/generate_order.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _isFinite = require('lodash/isFinite')
const _isObject = require('lodash/isObject')
const { Order } = require('bfx-api-node-models')
const genCID = require('../../util/gen_client_id')
const { nBN } = require('@bitfinex/lib-js-util-math')

/**
* Generates an atomic order to fill one slice of an AccumulateDistribute
Expand Down Expand Up @@ -114,7 +115,7 @@ const generateOrder = (instance = {}) => {

debug('resolved offset price %f', offsetPrice)

let finalPrice = offsetPrice + relativeOffset.delta
let finalPrice = nBN(offsetPrice).plus(relativeOffset.delta).toNumber()

if (_isObject(relativeCap) && relativeCap.type !== 'none') {
let priceCap
Expand Down Expand Up @@ -167,7 +168,7 @@ const generateOrder = (instance = {}) => {
return null
}

priceCap += relativeCap.delta
priceCap = nBN(priceCap).plus(relativeCap.delta).toNumber()

debug('resolved cap price %f', priceCap)

Expand Down
59 changes: 58 additions & 1 deletion test/lib/accumulate_distribute/events/orders_order_fill.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-env mocha */
'use strict'

const sinon = require('sinon')
const assert = require('assert')
const Promise = require('bluebird')
const _isObject = require('lodash/isObject')
Expand Down Expand Up @@ -41,11 +42,67 @@ describe('accumulate_distribute:events:orders_order_fill', () => {
o.amount = 40
assert.strictEqual(o.getLastFillAmount(), 2, 'sanity check failed')

const i = getInstance({})
const i = getInstance({
stateParams: {
remainingAmount: 40
}
})
await ordersOrderFill(i, o)
assert.strictEqual(o.getLastFillAmount(), 0, 'order fill amount not reset')
})

it('warns when there\'s an overfill', async () => {
let warnedAboutOverfill = false
const o = new Order({ amount: 0.3 })
o.amount = 0.2

const stubbedFillAmount = sinon.stub(o, 'getLastFillAmount').returns(0.2)

const i = getInstance({
stateParams: {
remainingAmount: 0.1,
ordersBehind: 2,
currentOrder: 3
},

helperParams: {
debug: (msg) => {
if (/warning: overfill/.test(msg)) {
warnedAboutOverfill = true
}
}
}
})

await ordersOrderFill(i, o)
assert.ok(warnedAboutOverfill, 'did not warn about the overfill')
stubbedFillAmount.restore()
})

it('updates state with the new remaining amount for float amounts', async () => {
const o = new Order({ amount: 0.3 })
o.amount = 0.1

const stubbedFillAmount = sinon.stub(o, 'getLastFillAmount').returns(0.1)

const i = getInstance({
stateParams: {
remainingAmount: 0.3,
ordersBehind: 2,
currentOrder: 3
},

helperParams: {
updateState: async (instance, packet) => {
assert.strictEqual(packet.remainingAmount, 0.2, 'incorrect remaining amount')
}
}
})

await ordersOrderFill(i, o)
stubbedFillAmount.restore()
})

it('updates state with the new remaining amount & timeline position', async () => {
const o = new Order({ amount: 42 })
o.amount = 40
Expand Down
11 changes: 11 additions & 0 deletions test/lib/accumulate_distribute/meta/init_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ describe('accumulate_distribute:meta:init_state', () => {
stubOrderAmountsGen.restore()
})

it('initializes float order amounts', () => {
const stubOrderAmountsGen = sinon.stub(orderAmounts, 'gen').returns([0.2, 0.1])
const state = initState({ amount: 0.3 })

assert.deepStrictEqual(state.orderAmounts, [0.2, 0.1])
assert.deepStrictEqual(state.args, { amount: 0.3 })
assert.strictEqual(state.remainingAmount, 0.3)

stubOrderAmountsGen.restore()
})

it('throws an error if gen_order_amounts returns a greater total amount than requested by the user', () => {
const stubOrderAmountsGen = sinon.stub(orderAmounts, 'gen').returns([1, 2, 1])

Expand Down
46 changes: 44 additions & 2 deletions test/lib/accumulate_distribute/util/gen_order_amounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,57 @@

const assert = require('assert')
const { Config } = require('bfx-api-node-core')
const genOrderAmounts = require('../../../../lib/accumulate_distribute/util/gen_order_amounts')
const { gen: genOrderAmounts } = require('../../../../lib/accumulate_distribute/util/gen_order_amounts')

const { DUST } = Config

const pairConfig = {
minSize: 0.002,
maxSize: 0.004
}

describe('accumulate_distribute:util:gen_order_amounts', () => {
it('returns a total amount equal to or DUST-difference from the requested total', () => {
const amounts = genOrderAmounts.gen({ amount: 10, sliceAmount: 1, amountDistortion: 0.25 })
const amounts = genOrderAmounts({ amount: 10, sliceAmount: 1, amountDistortion: 0.25 })
const total = amounts.reduce((prev, curr) => prev + curr, 0)

assert.ok(Math.abs(total - 10) <= DUST, 'deviation greater than DUST')
})

describe('when pairConfig is provided', () => {
it('returns a total amount equal to or DUST-difference from the requested total for buy order', () => {
const amounts = genOrderAmounts({ amount: 10, sliceAmount: 1, amountDistortion: 0.25 }, pairConfig)
const total = amounts.reduce((prev, curr) => prev + curr, 0)

assert.ok(Math.abs(total - 10) <= DUST, 'deviation greater than DUST')
})

it('returns a total amount equal to or DUST-difference from the requested total for sell order', () => {
const amounts = genOrderAmounts({ amount: -10, sliceAmount: -1, amountDistortion: 0.25 }, pairConfig)
const total = amounts.reduce((prev, curr) => prev + curr, 0)

assert.ok(Math.abs(total + 10) <= DUST, 'deviation greater than DUST')
})

it('leaves out the remaining amount if the maximum possible total amount is less than the input amount', () => {
const amounts = genOrderAmounts({ amount: 0.0051, sliceAmount: 0.002, amountDistortion: 0.25 }, pairConfig)
const total = amounts.reduce((prev, curr) => prev + curr, 0)

assert.ok(Math.abs(total - 0.0051) < pairConfig.minSize, 'deviation greater than minimum order size')
})

it('does not generate the sliced order amount less than the minimum size', () => {
const amounts = genOrderAmounts({ amount: 0.005, sliceAmount: 0.002, amountDistortion: 0.25 }, pairConfig)
const filteredOutAmounts = amounts.filter(v => v < pairConfig.minSize)

assert.deepStrictEqual(filteredOutAmounts.length, 0, 'contains amounts less than the minimum order size')
})

it('does not generate the sliced order amount greater than the maximum size', () => {
const amounts = genOrderAmounts({ amount: 0.1, sliceAmount: 0.004, amountDistortion: 0.25 }, pairConfig)
const filteredOutAmounts = amounts.filter(v => v > pairConfig.maxSize)

assert.deepStrictEqual(filteredOutAmounts.length, 0, 'contains amounts greater than the maximum order size')
})
})
})
Loading