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

[WIP] Add triangular arbitrage #25

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ module.exports = {
AccumulateDistribute: require('./lib/accumulate_distribute'),
PingPong: require('./lib/ping_pong'),
MACrossover: require('./lib/ma_crossover'),
NoDataError: require('./lib/errors/no_data')
NoDataError: require('./lib/errors/no_data'),
TriangularArbitrage: require('./lib/triangular_arbitrage')
}
30 changes: 30 additions & 0 deletions lib/triangular_arbitrage/events/data_managed_book.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'

const hasOBTarget = require('../util/has_ob_target')
const trySubmitOrder = require('../util/try_submit_order')

module.exports = async (instance = {}, book, meta) => {
const { state = {}, h = {} } = instance
const { args = {}, lastBook = {} } = state
const { symbol1, symbol2, symbol3 } = args
const { debug, updateState } = h
const { chanFilter } = meta
const chanSymbol = chanFilter.symbol

if (!hasOBTarget(args)) {
return
}

if (![symbol1, symbol2, symbol3].includes(chanSymbol)) {
return
}

debug('recv updated order book for %s', chanSymbol)

lastBook[chanSymbol] = book
await updateState(instance, {
lastBook: lastBook
})

trySubmitOrder(instance)
}
13 changes: 13 additions & 0 deletions lib/triangular_arbitrage/events/life_start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

const trySubmitOrder = require('../util/try_submit_order')

module.exports = async (instance = {}) => {
const { state = {}, h = {} } = instance
const { debug } = h
const { args = {} } = state
const { symbol1, symbol2, symbol3 } = args

debug(`Triangular ${symbol1}->${symbol2}->${symbol3} arbitrage complete`)
trySubmitOrder(instance)
}
10 changes: 10 additions & 0 deletions lib/triangular_arbitrage/events/life_stop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict'

module.exports = async (instance = {}) => {
const { state = {}, h = {} } = instance
const { args = {}, orders = {}, gid } = state
const { emit } = h
const { cancelDelay } = args

await emit('exec:order:cancel:all', gid, orders, cancelDelay)
}
13 changes: 13 additions & 0 deletions lib/triangular_arbitrage/events/orders_order_cancel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

module.exports = async (instance = {}, order) => {
const { state = {}, h = {} } = instance
const { args = {}, orders = {}, gid } = state
const { emit, debug } = h
const { cancelDelay } = args

debug('detected atomic cancelation, stopping...')

await emit('exec:order:cancel:all', gid, orders, cancelDelay)
await emit('exec:stop')
}
7 changes: 7 additions & 0 deletions lib/triangular_arbitrage/events/orders_order_fill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

const trySubmitOrder = require('../util/try_submit_order')

module.exports = async (instance = {}, order) => {
trySubmitOrder(instance)
}
72 changes: 72 additions & 0 deletions lib/triangular_arbitrage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict'

// meta
const defineAlgoOrder = require('../define_algo_order')
const validateParams = require('./meta/validate_params')
const genPreview = require('./meta/gen_preview')
const processParams = require('./meta/process_params')
const initState = require('./meta/init_state')
const getUIDef = require('./meta/get_ui_def')
const serialize = require('./meta/serialize')
const unserialize = require('./meta/unserialize')
const genOrderLabel = require('./meta/gen_order_label')

// events
const onLifeStart = require('./events/life_start')
const onLifeStop = require('./events/life_stop')
const onOrdersOrderFill = require('./events/orders_order_fill')
const onOrdersOrderCancel = require('./events/orders_order_cancel')
const onDataManagedBook = require('./events/data_managed_book')

/**
* Triangular arbitrage attempts to profit from the small differences in price
* between multiple markets. It submits a series of synchronous orders that
* execute on 3 different markets and end up back to the starting symbol thus
* creating a triangle pattern. For example:
* EOS:BTC (buy) -> EOS:ETH (sell) -> ETH:BTC (sell)
*
* Once the EOS:BTC buy order fills then a new order is executed on EOS:ETH to sell the EOS
* and finally, once that order is filled a sell order is placed on the ETH:BTC market in order
* to complete the full cycle back to BTC.
*
* The user is able to specify whether the orders execute as a taker or a maker by selecting
* the order types 'MARKET', 'BEST ASK' or 'BEST BID'
*
* @name PingPong
* @param {boolean} limit - if enabled all orders will be placed at best bid/ask
* @param {string} symbol1 - starting market
* @param {string} symbol2 - intermediate market
* @param {string} symbol3 - final market
* @param {number} amount - order size
*/
module.exports = defineAlgoOrder({
id: 'bfx-triangular_arbitrage',
name: 'Triangular Arbitrage',

meta: {
genOrderLabel,
validateParams,
processParams,
genPreview,
initState,
getUIDef,
serialize,
unserialize
},

events: {
life: {
start: onLifeStart,
stop: onLifeStop
},

orders: {
order_fill: onOrdersOrderFill,
order_cancel: onOrdersOrderCancel
},

data: {
managedBook: onDataManagedBook
}
}
})
34 changes: 34 additions & 0 deletions lib/triangular_arbitrage/meta/declare_channels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict'

const LIMIT_TYPES = ['BEST_ASK', 'BEST_BID']

module.exports = async (instance = {}, host) => {
const { h = {}, state = {} } = instance
const { args = {} } = state
const { symbol1, symbol2, symbol3, orderType1, orderType2, orderType3 } = args
const { declareChannel } = h
const len = 5
const prec = 'R0'

if (LIMIT_TYPES.includes(orderType1)) {
await declareChannel(instance, host, 'book', {
symbol: symbol1,
prec,
len
})
}
if (LIMIT_TYPES.includes(orderType2)) {
await declareChannel(instance, host, 'book', {
symbol: symbol2,
prec,
len
})
}
if (LIMIT_TYPES.includes(orderType3)) {
await declareChannel(instance, host, 'book', {
symbol: symbol3,
prec,
len
})
}
}
8 changes: 8 additions & 0 deletions lib/triangular_arbitrage/meta/declare_events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict'

module.exports = (instance = {}, host) => {
const { h = {} } = instance
const { declareEvent } = h

declareEvent(instance, host, 'self:submit_order', 'submit_order')
}
12 changes: 12 additions & 0 deletions lib/triangular_arbitrage/meta/gen_order_label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

module.exports = (state = {}) => {
const { args = {} } = state
const { orders, limit } = args
// TODO add proper table
return ['Triangular Arbitrage'].concat(
orders.map((o) => {
return ` | ${o.symbol} ${o.amount} @ ${o.price || o.type}`
})
).join('')
}
5 changes: 5 additions & 0 deletions lib/triangular_arbitrage/meta/gen_preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = (args = {}) => {
return []
}
126 changes: 126 additions & 0 deletions lib/triangular_arbitrage/meta/get_ui_def.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use strict'

module.exports = () => ({
id: 'bfx-triangular_arbitrage',
label: 'Triangular Arbitrage',

uiIcon: 'triangle-arbitrage-active',
customHelp: 'Trangular Arbitrage synchronously exchanges between 3 different markets to create full round trip back to the original starting currency.\n\nOrders will be submitted synchronously until the round trip is complete.',
connectionTimeout: 10000,
actionTimeout: 10000,

header: {
component: 'ui.checkbox_group',
fields: ['hidden']
},

sections: [{
title: '',
name: 'general',
rows: [
['action', null],
['amount', 'orderType1'],
['intermediateCcy', 'orderType2'],
[null, 'orderType3'],
['submitDelaySec', 'cancelDelaySec']
]
}, {
title: '',
name: 'lev',
fullWidth: true,
rows: [
['lev']
],

visible: {
_context: { eq: 'f' }
}
}],

fields: {
hidden: {
component: 'input.checkbox',
label: 'HIDDEN',
default: false,
help: 'trading.hideorder_tooltip'
},

submitDelaySec: {
component: 'input.number',
label: 'Submit Delay (sec)',
customHelp: 'Seconds to wait before submitting orders',
default: 1
},

cancelDelaySec: {
component: 'input.number',
label: 'Cancel Delay (sec)',
customHelp: 'Seconds to wait before cancelling orders',
default: 0
},

amount: {
component: 'input.amount',
label: 'Amount $BASE',
customHelp: 'Starting amount'
},

intermediateCcy: {
component: 'input.string',
label: 'Intermediate Currency',
customHelp: 'The intermediate market XXX:$BASE',
default: 'ETH'
},

lev: {
component: 'input.range',
label: 'Leverage',
min: 1,
max: 100,
default: 10
},

orderType1: {
component: 'input.dropdown',
label: 'Order Type',
default: 'LIMIT',
options: {
MARKET: 'Market',
BEST_BID: 'Best Bid',
BEST_ASK: 'Best Ask'
}
},

orderType2: {
component: 'input.dropdown',
label: 'Order Type',
default: 'LIMIT',
options: {
MARKET: 'Market',
BEST_BID: 'Best Bid',
BEST_ASK: 'Best Ask'
}
},

orderType3: {
component: 'input.dropdown',
label: 'Order Type',
default: 'LIMIT',
options: {
MARKET: 'Market',
BEST_BID: 'Best Bid',
BEST_ASK: 'Best Ask'
}
}
},

action: {
component: 'input.radio',
label: 'Action',
options: ['Buy', 'Sell'],
inline: true,
default: 'Buy'
},

actions: ['preview', 'submit']
})
5 changes: 5 additions & 0 deletions lib/triangular_arbitrage/meta/init_state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = (args = {}) => {
return { args }
}
Loading