320 lines
16 KiB
JavaScript
320 lines
16 KiB
JavaScript
exports.newTradingExecution = function newTradingExecution(bot, logger) {
|
|
/*
|
|
The Trading Execution modules manages the execution of orders against the exchanges.
|
|
*/
|
|
const MODULE_NAME = 'Trading Execution'
|
|
|
|
let thisObject = {
|
|
takePosition: takePosition,
|
|
closePosition: closePosition,
|
|
initialize: initialize,
|
|
finalize: finalize
|
|
}
|
|
|
|
let tradingEngine
|
|
|
|
return thisObject
|
|
|
|
function initialize() {
|
|
|
|
tradingEngine = bot.simulationState.tradingEngine
|
|
|
|
}
|
|
|
|
function finalize() {
|
|
tradingEngine = undefined
|
|
}
|
|
|
|
function takePosition() {
|
|
/* Check that we are in LIVE or FORWARD MODE */
|
|
if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') {
|
|
logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> Taking a Position in ' + bot.SESSION.type)
|
|
logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.size.value = ' + tradingEngine.current.position.size.value)
|
|
logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value)
|
|
logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> slippageAmount = ' + slippageAmount)
|
|
logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value)
|
|
|
|
/* We see if we need to put the actual order at the exchange. */
|
|
if (variable.executionContext !== undefined) {
|
|
switch (variable.executionContext.status) {
|
|
case 'Without a Position': { // We need to put the order because It was not put yet.
|
|
if (strategy.openStage !== undefined) {
|
|
if (strategy.openStage.openExecution !== undefined) {
|
|
putOpeningOrder()
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case 'Position Closed': { // Waiting for a confirmation that the position was closed.
|
|
if (strategy.openStage !== undefined) {
|
|
if (strategy.openStage.openExecution !== undefined) {
|
|
putOpeningOrder()
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case 'Taking Position': { // Waiting for a confirmation that the position was taken.
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position.')
|
|
break
|
|
}
|
|
case 'In a Position': { // This should mean that we already put the order at the exchange.
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position.')
|
|
break
|
|
}
|
|
}
|
|
} else { // The context does not exist so it means we are not in a position.
|
|
if (strategy.openStage !== undefined) {
|
|
if (strategy.openStage.openExecution !== undefined) {
|
|
putOpeningOrder()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.')
|
|
}
|
|
|
|
}
|
|
|
|
function putOpeningOrder() {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.')
|
|
|
|
/* We wont take a position unless we are withing the sessionParameters.timeRange.config.initialDatetime and the sessionParameters.timeRange.config.finalDatetime range */
|
|
if (sessionParameters.timeRange.config.initialDatetime !== undefined) {
|
|
if (candle.end < sessionParameters.timeRange.config.initialDatetime) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> sessionParameters.timeRange.config.initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime)
|
|
takePositionAtSimulation()
|
|
return
|
|
}
|
|
}
|
|
|
|
/* We wont take a position if we are past the final datetime */
|
|
if (sessionParameters.timeRange.config.finalDatetime !== undefined) {
|
|
if (candle.begin > sessionParameters.timeRange.config.finalDatetime) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> sessionParameters.timeRange.config.finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime)
|
|
takePositionAtSimulation()
|
|
return
|
|
}
|
|
}
|
|
|
|
/* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */
|
|
if (variable.executionContext !== undefined) {
|
|
if (variable.executionContext.periods !== undefined) {
|
|
if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution.')
|
|
takePositionAtSimulation()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are not going to place orders based on outdated information. The next filter prevents firing orders when backtesting. */
|
|
if (currentDay) {
|
|
let today = new Date(Math.trunc((new Date().valueOf()) / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
|
|
let processDay = new Date(Math.trunc(currentDay.valueOf() / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
|
|
if (today.valueOf() !== processDay.valueOf()) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading.')
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today)
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay)
|
|
takePositionAtSimulation()
|
|
return
|
|
}
|
|
}
|
|
|
|
let orderPrice
|
|
let amountA
|
|
let amountB
|
|
let orderSide
|
|
|
|
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
|
orderSide = 'sell'
|
|
|
|
orderPrice = tradingEngine.current.position.rate.value
|
|
|
|
amountA = tradingEngine.current.position.size.value * orderPrice
|
|
amountB = tradingEngine.current.position.size.value
|
|
} else {
|
|
orderSide = 'buy'
|
|
|
|
orderPrice = tradingEngine.current.position.rate.value
|
|
|
|
amountA = tradingEngine.current.position.size.value
|
|
amountB = tradingEngine.current.position.size.value / orderPrice
|
|
}
|
|
|
|
variable.executionContext = {
|
|
status: 'Taking Position',
|
|
periods: tradingEngine.episode.episodeCounters.periods.value
|
|
}
|
|
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order.')
|
|
exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated)
|
|
|
|
function onOrderCreated(err, order) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.')
|
|
|
|
try {
|
|
switch (err.result) {
|
|
case global.DEFAULT_OK_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ')
|
|
variable.executionContext = {
|
|
status: 'In a Position',
|
|
periods: tradingEngine.episode.episodeCounters.periods.value,
|
|
amountA: amountA,
|
|
amountB: amountB,
|
|
orderId: order.id
|
|
}
|
|
takePositionAtSimulation()
|
|
return
|
|
}
|
|
case global.DEFAULT_FAIL_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ')
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message)
|
|
strategy.openStage.openExecution.error = err.message
|
|
afterLoop()
|
|
return
|
|
}
|
|
case global.DEFAULT_RETRY_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ')
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message)
|
|
strategy.openStage.openExecution.error = err.message
|
|
afterLoop()
|
|
return
|
|
}
|
|
}
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message)
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
|
return
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = ' + err.stack)
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function closePosition() {
|
|
/* Check that we are in LIVE MODE */
|
|
if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') {
|
|
/* We see if we need to put the actual order at the exchange. */
|
|
if (variable.executionContext !== undefined) {
|
|
switch (variable.executionContext.status) {
|
|
case 'Without a Position': { // No way to close anything at the exchange.
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position.')
|
|
break
|
|
}
|
|
case 'In a Position': { // This should mean that we already put the order at the exchange.
|
|
if (strategy.closeStage !== undefined) {
|
|
if (strategy.closeStage.closeExecution !== undefined) {
|
|
putClosingOrder()
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case 'Closing Position': { // Waiting for a confirmation that the position was taken.
|
|
if (strategy.closeStage !== undefined) {
|
|
if (strategy.closeStage.closeExecution !== undefined) {
|
|
putClosingOrder()
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
case 'Position Closed': { //
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.')
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function putClosingOrder() {
|
|
/* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */
|
|
if (variable.executionContext !== undefined) {
|
|
if (variable.executionContext.periods !== undefined) {
|
|
if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution.')
|
|
closePositionAtSimulation()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
let orderPrice
|
|
let amountA
|
|
let amountB
|
|
let orderSide
|
|
|
|
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
|
orderSide = 'buy'
|
|
|
|
orderPrice = ticker.last + 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff.
|
|
|
|
amountA = tradingEngine.current.balance.quotedAsset.value
|
|
amountB = tradingEngine.current.balance.quotedAsset.value / orderPrice
|
|
} else {
|
|
orderSide = 'sell'
|
|
|
|
orderPrice = ticker.last - 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff.
|
|
|
|
amountA = tradingEngine.current.balance.baseAsset.value * orderPrice
|
|
amountB = tradingEngine.current.balance.baseAsset.value
|
|
}
|
|
|
|
variable.executionContext = {
|
|
status: 'Closing Position',
|
|
periods: tradingEngine.episode.episodeCounters.periods.value
|
|
}
|
|
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange.')
|
|
exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated)
|
|
|
|
function onOrderCreated(err, order) {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.')
|
|
|
|
try {
|
|
switch (err.result) {
|
|
case global.DEFAULT_OK_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ')
|
|
variable.executionContext = {
|
|
status: 'Position Closed',
|
|
periods: tradingEngine.episode.episodeCounters.periods.value,
|
|
amountA: amountA,
|
|
amountB: amountB,
|
|
orderId: order.id
|
|
}
|
|
closePositionAtSimulation()
|
|
return
|
|
}
|
|
case global.DEFAULT_FAIL_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ')
|
|
/* We will assume that the problem is temporary, and expect that it will work at the next execution. */
|
|
strategy.closeStage.closeExecution.error = err.message
|
|
afterLoop()
|
|
return
|
|
}
|
|
case global.DEFAULT_RETRY_RESPONSE.result: {
|
|
logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ')
|
|
strategy.closeStage.closeExecution.error = err.message
|
|
afterLoop()
|
|
return
|
|
}
|
|
}
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message)
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
|
return
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = ' + err.stack)
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|