Beta 6 without Execution
This commit is contained in:
1 parent
4033d4d07f
commit
d40b985f5e
123 files changed
+8114
-12392
No files matched your search
@@ -0,0 +1,61 @@
|
||||
exports.newAnnouncements = function newAnnouncements(bot, logger) {
|
||||
|
||||
const MODULE_NAME = 'Announcements'
|
||||
|
||||
let thisObject = {
|
||||
makeAnnoucements: makeAnnoucements,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingSystem
|
||||
let tradingEngine
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
bot.SESSION.messagesSent = undefined
|
||||
}
|
||||
|
||||
function makeAnnoucements(node) {
|
||||
if (node.announcements !== undefined) {
|
||||
for (let i = 0; i < node.announcements.length; i++) {
|
||||
let announcement = node.announcements[i]
|
||||
let canAnnounce = true
|
||||
if (announcement.announcementCondition !== undefined) {
|
||||
let conditionValue
|
||||
try {
|
||||
conditionValue = eval(announcement.announcementCondition.code)
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([announcement.announcementCondition.id, err.message])
|
||||
}
|
||||
if (conditionValue !== true) { canAnnounce = false }
|
||||
}
|
||||
if (canAnnounce === true) {
|
||||
let text = announcement.config.text
|
||||
if (announcement.formula !== undefined) {
|
||||
try {
|
||||
text = eval(announcement.formula.code)
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([announcement.formula.id, err.message])
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.SESSION.socialBots !== undefined) {
|
||||
bot.SESSION.socialBots.announce(text)
|
||||
tradingSystem.announcements.push([announcement.id, text])
|
||||
} else {
|
||||
tradingSystem.errors.push([announcement.id, 'Could not announce because session does not have Social Bots.'])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
exports.newSnapshots = function newSnapshots(bot, logger) {
|
||||
/*
|
||||
This module encapsulates the snapshots functionality.
|
||||
*/
|
||||
const MODULE_NAME = 'Snapshots'
|
||||
|
||||
let thisObject = {
|
||||
reset: reset,
|
||||
strategyEntry: strategyEntry,
|
||||
strategyExit: strategyExit,
|
||||
positionEntry: positionEntry,
|
||||
positionExit: positionExit,
|
||||
addCodeToSnapshot: addCodeToSnapshot,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
const SNAPSHOTS_FOLDER_NAME = 'Snapshots'
|
||||
|
||||
/* Snapshots of Strategies and Positions */
|
||||
let snapshots = {
|
||||
headers: [],
|
||||
strategies: [],
|
||||
positions: []
|
||||
}
|
||||
|
||||
let positionKeys = new Map() // This is to prevent adding the same indicator / variable to the output collection.
|
||||
let strategyKeys = new Map()
|
||||
let createHeaders = true // This is going to be true only once
|
||||
let createCloseHeaders = true // This is going to be true only once
|
||||
let closeValues
|
||||
let strategyValues
|
||||
let addToStrategyValues
|
||||
let positionValues
|
||||
let addToPositionValues
|
||||
|
||||
let chart
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
writeSnapshotFiles()
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
|
||||
positionKeys = undefined
|
||||
strategyKeys = undefined
|
||||
|
||||
chart = undefined
|
||||
}
|
||||
|
||||
function reset(pChart) {
|
||||
/* This function helps reset data structures at every cycle of the simulation loop/=. */
|
||||
chart = pChart // We need chat to be a local object accessible from conditions and formulas.
|
||||
}
|
||||
|
||||
function strategyEntry() {
|
||||
/*
|
||||
We will record here a snapshot of all values used in conditions and formulas within the Trading System.
|
||||
If the strategy exists with a trade, this information will be added to the snapshots file.
|
||||
*/
|
||||
|
||||
strategyKeys = new Map()
|
||||
strategyValues = []
|
||||
addToStrategyValues = true
|
||||
|
||||
evalNode(tradingSystem)
|
||||
|
||||
/* Turn off this */
|
||||
createHeaders = false
|
||||
addToStrategyValues = false
|
||||
}
|
||||
|
||||
function strategyExit() {
|
||||
getResults(tradingEngine.current.strategy.begin.value, tradingEngine.current.strategy.end.value)
|
||||
let valuesArray = closeValues.concat(strategyValues)
|
||||
snapshots.strategies.push(valuesArray)
|
||||
}
|
||||
|
||||
function positionEntry() {
|
||||
|
||||
positionKeys = new Map()
|
||||
positionValues = []
|
||||
addToPositionValues = true
|
||||
|
||||
evalNode(tradingSystem)
|
||||
|
||||
/* Turn off this */
|
||||
createHeaders = false
|
||||
addToPositionValues = false
|
||||
}
|
||||
|
||||
function positionExit() {
|
||||
getResults(tradingEngine.current.position.begin.value, tradingEngine.current.position.end.value)
|
||||
let valuesArray = closeValues.concat(positionValues)
|
||||
snapshots.positions.push(valuesArray)
|
||||
}
|
||||
|
||||
function evalNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we check if there is a codition to be evaluated */
|
||||
if (node.type === 'Condition') {
|
||||
/* We will eval this condition */
|
||||
evalCondition(node)
|
||||
}
|
||||
|
||||
/* Here we check if there is a formula to be evaluated */
|
||||
if (node.type === 'Formula') {
|
||||
if (node.code !== undefined) {
|
||||
/* We will eval this formula */
|
||||
evalFormula(node)
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
evalNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
evalNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function evalCondition(node) {
|
||||
|
||||
/*
|
||||
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
|
||||
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
|
||||
from there. Otherwise we will take the code from the Condition node.
|
||||
*/
|
||||
|
||||
let nodeWithCode = node
|
||||
if (node.javascriptCode !== undefined) {
|
||||
if (node.javascriptCode.code !== undefined) {
|
||||
nodeWithCode = node.javascriptCode
|
||||
}
|
||||
}
|
||||
addCodeToSnapshot(nodeWithCode)
|
||||
}
|
||||
|
||||
function evalFormula(node) {
|
||||
let nodeWithCode = node
|
||||
addCodeToSnapshot(nodeWithCode)
|
||||
}
|
||||
|
||||
function addCodeToSnapshot(nodeWithCode) {
|
||||
if (nodeWithCode === undefined) { return }
|
||||
if (nodeWithCode.code === undefined) { return }
|
||||
|
||||
try {
|
||||
let instructionsArray = nodeWithCode.code.split(' ')
|
||||
for (let i = 0; i < instructionsArray.length; i++) {
|
||||
let instruction = instructionsArray[i]
|
||||
instruction = instruction.replace('(', '')
|
||||
instruction = instruction.replace(')', '')
|
||||
instruction = instruction.replace(/</g, '')
|
||||
instruction = instruction.replace(/>/g, '')
|
||||
instruction = instruction.replace(/<=/g, '')
|
||||
instruction = instruction.replace(/>=/g, '')
|
||||
instruction = instruction.replace(/!=/g, '')
|
||||
instruction = instruction.replace(/!==/g, '')
|
||||
instruction = instruction.replace(/==/g, '')
|
||||
instruction = instruction.replace(/===/g, '')
|
||||
instruction = instruction.replace(/{/g, '')
|
||||
instruction = instruction.replace(/}/g, '')
|
||||
if (instruction.indexOf('chart') >= 0) {
|
||||
let parts = instruction.split('.')
|
||||
let timeFrame = parts[1]
|
||||
let product = parts[2]
|
||||
let property
|
||||
checkPrevious(3)
|
||||
function checkPrevious(index) {
|
||||
property = parts[index]
|
||||
if (property === 'previous') {
|
||||
product = product + '.previous'
|
||||
checkPrevious(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10
|
||||
if (timeFrame !== 'atAnyTimeFrame') {
|
||||
timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7)
|
||||
}
|
||||
let key = timeFrame + '-' + product + '-' + property
|
||||
let existingKey
|
||||
|
||||
if (addToStrategyValues === true) {
|
||||
addValues(strategyKeys, strategyValues)
|
||||
}
|
||||
|
||||
if (addToPositionValues === true) {
|
||||
addValues(positionKeys, positionValues)
|
||||
}
|
||||
|
||||
function addValues(keys, values) {
|
||||
existingKey = keys.get(key)
|
||||
|
||||
if (existingKey === undefined) { // means that at the current loop this property of this product was not used before.
|
||||
keys.set(key, key)
|
||||
if (createHeaders === true) {
|
||||
snapshots.headers.push(key)
|
||||
}
|
||||
|
||||
let value = eval(instruction)
|
||||
values.push(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push(nodeWithCode.id, err.message)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> nodeWithCode.code = ' + nodeWithCode.code)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
function getResults(openDatetime, closeDatetime) {
|
||||
|
||||
let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type']
|
||||
closeValues = [
|
||||
tradingEngine.episode.episodeCounters.positions.value, // Position Number
|
||||
(new Date(openDatetime)).toISOString(), // Open Datetime
|
||||
(new Date(closeDatetime)).toISOString(), // Open Datetime
|
||||
tradingEngine.current.strategy.strategyName.value, // Strategy Name
|
||||
tradingEngine.current.strategy.situationName.value, // Trigger On Situation
|
||||
tradingEngine.current.position.situationName.value, // Take Position Situation
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value, // Result
|
||||
tradingEngine.current.position.positionStatistics.ROI.value, // ROI
|
||||
tradingEngine.current.position.exitType.value // Exit Type
|
||||
]
|
||||
|
||||
if (createCloseHeaders === true) {
|
||||
snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshots.headers)))
|
||||
createCloseHeaders = false
|
||||
}
|
||||
}
|
||||
|
||||
function writeSnapshotFiles() {
|
||||
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
writeSnapshotFile(snapshots.strategies, 'Strategies')
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
writeSnapshotFile(snapshots.positions, 'Positions')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeSnapshotFile(snapshotArray, pFileName) {
|
||||
try {
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
let fileContent = "";
|
||||
let separator = "\r\n";
|
||||
|
||||
parseRecord(snapshots.headers)
|
||||
|
||||
for (let i = 0; i < snapshotArray.length; i++) {
|
||||
let record = snapshotArray[i];
|
||||
parseRecord(record)
|
||||
}
|
||||
|
||||
function parseRecord(record) {
|
||||
for (let j = 0; j < record.length; j++) {
|
||||
let property = record[j]
|
||||
|
||||
fileContent = fileContent + '' + property
|
||||
if (j !== record.length - 1) {
|
||||
fileContent = fileContent + ","
|
||||
}
|
||||
}
|
||||
fileContent = fileContent + separator
|
||||
}
|
||||
|
||||
fileContent = "" + fileContent + "";
|
||||
|
||||
let fileName = pFileName + '.csv';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated);
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> filePath = " + filePath);
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
/*
|
||||
We call the Trading Engine to the data structure that is needed in order to exevute the
|
||||
trading protocol with the specific rules defined at the Trading System.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Engine'
|
||||
let thisObject = {
|
||||
cloneValues: cloneValues,
|
||||
setCurrentCandle: setCurrentCandle,
|
||||
updateEpisodeCountersAndStatistics: updateEpisodeCountersAndStatistics,
|
||||
updateDistanceToEventsCounters: updateDistanceToEventsCounters,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
/* Here we will go through all the nodes in the Trading Engine hiriarchy and apply the initial value to the value property when needed */
|
||||
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
|
||||
if (bot.FIRST_EXECUTION === true) {
|
||||
initializeNode(tradingEngine)
|
||||
tradingEngine.current.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.current.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function setCurrentCandle(candle) {
|
||||
cloneValues(tradingEngine.current.candle, tradingEngine.previous.candle)
|
||||
|
||||
tradingEngine.current.candle.begin.value = candle.begin
|
||||
tradingEngine.current.candle.end.value = candle.end
|
||||
tradingEngine.current.candle.open.value = candle.open
|
||||
tradingEngine.current.candle.close.value = candle.close
|
||||
tradingEngine.current.candle.min.value = candle.min
|
||||
tradingEngine.current.candle.max.value = candle.max
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
if (originNode === undefined) { return }
|
||||
if (destinationNode === undefined) { return }
|
||||
|
||||
/* Here copy the node value */
|
||||
destinationNode.value = originNode.value
|
||||
|
||||
/* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/
|
||||
let nodeDefinition = bot.APP_SCHEMA_MAP.get(originNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
cloneValues(originNode[property.name], destinationNode[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
let originNodePropertyArray = originNode[property.name]
|
||||
let destinationNodePropertyArray = destinationNode[property.name]
|
||||
for (let m = 0; m < originNodePropertyArray.length; m++) {
|
||||
cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we initialize the node value */
|
||||
if (node.config !== undefined) {
|
||||
if (node.config.initialValue !== undefined) {
|
||||
node.value = node.config.initialValue
|
||||
}
|
||||
}
|
||||
node.initialize = initializeNode // This will allow anyone to initialize this node and its children.
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateEpisodeCountersAndStatistics() {
|
||||
tradingEngine.episode.episodeCounters.periods.value++
|
||||
tradingEngine.episode.episodeStatistics.days.value = tradingEngine.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
function updateDistanceToEventsCounters() {
|
||||
/* Keeping Distance Counters Up-to-date */
|
||||
if (
|
||||
tradingEngine.current.distanceToEvent.triggerOn.value > 0 // with this we avoind counting before the first event happens.
|
||||
) {
|
||||
tradingEngine.current.distanceToEvent.triggerOn.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.distanceToEvent.triggerOff.value > 0 // with this we avoind counting before the first event happens.
|
||||
) {
|
||||
tradingEngine.current.distanceToEvent.triggerOff.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.distanceToEvent.takePosition.value > 0 // with this we avoind counting before the first event happens.
|
||||
) {
|
||||
tradingEngine.current.distanceToEvent.takePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.distanceToEvent.closePosition.value > 0 // with this we avoind counting before the first event happens.
|
||||
) {
|
||||
tradingEngine.current.distanceToEvent.closePosition.value++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
/*
|
||||
This module will load if necesary all the data outputs so that they can be appended with new
|
||||
records if needed. After running the simulation, it will save all the data outputs.
|
||||
*/
|
||||
const FULL_LOG = true
|
||||
const MODULE_NAME = 'Trading Bot'
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
}
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger)
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger)
|
||||
|
||||
return thisObject
|
||||
|
||||
function finalize() {
|
||||
thisObject = undefined
|
||||
utilities = undefined
|
||||
fileStorage = undefined
|
||||
commons = undefined
|
||||
}
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME
|
||||
logger.initialize()
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function start(chart, timeFrame, timeFrameLabel, currentDay, callBackFunction) {
|
||||
try {
|
||||
bot.processingDailyFiles
|
||||
if (timeFrame > global.dailyFilePeriods[0][0]) {
|
||||
bot.processingDailyFiles = false
|
||||
} else {
|
||||
bot.processingDailyFiles = true
|
||||
}
|
||||
|
||||
/* Preparing everything for the Simulation */
|
||||
const TRADING_SIMULATION = require('./TradingSimulation.js')
|
||||
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES)
|
||||
|
||||
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
|
||||
let market = bot.market
|
||||
let totalFilesToBeRead = 0
|
||||
let totalFilesRead = 0
|
||||
let totalFilesToBeWritten = 0
|
||||
let totalFilesWritten = 0
|
||||
let outputDatasetsMap = new Map()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
initializeOutputs()
|
||||
} else {
|
||||
readFiles()
|
||||
}
|
||||
return
|
||||
|
||||
function initializeOutputs() {
|
||||
if (bot.processingDailyFiles) {
|
||||
initializeDailyFiles()
|
||||
} else {
|
||||
initializeMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
|
||||
}
|
||||
}
|
||||
runSimulation()
|
||||
}
|
||||
|
||||
function initializeMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
|
||||
}
|
||||
}
|
||||
runSimulation()
|
||||
}
|
||||
|
||||
function readFiles() {
|
||||
/*
|
||||
This bot have an output of files that it generates. At every call to the bot, it needs to read the previously generated
|
||||
files in order to later append more information after the execution is over. Here in this function we are going to
|
||||
read those output files and get them ready for appending content during the simulation.
|
||||
*/
|
||||
if (bot.processingDailyFiles) {
|
||||
readDailyFiles()
|
||||
} else {
|
||||
readMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
function readMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readOutputFile(fileName, filePath, productName) {
|
||||
totalFilesToBeRead++
|
||||
filePath += '/' + fileName
|
||||
fileStorage.getTextFile(filePath, onFileRead, true)
|
||||
|
||||
function onFileRead(err, text) {
|
||||
try {
|
||||
|
||||
if (err.message === 'File does not exist.') {
|
||||
outputDatasetsMap.set(productName, [])
|
||||
anotherFileRead()
|
||||
return
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath)
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset)
|
||||
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
|
||||
outputDatasetsMap.set(productName, JSON.parse(text))
|
||||
anotherFileRead()
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function anotherFileRead() {
|
||||
totalFilesRead++
|
||||
if (totalFilesRead === totalFilesToBeRead) {
|
||||
runSimulation()
|
||||
}
|
||||
}
|
||||
|
||||
function runSimulation() {
|
||||
tradingSimulation.runSimulation(
|
||||
chart,
|
||||
outputDatasetsMap,
|
||||
writeFiles,
|
||||
callBackFunction)
|
||||
}
|
||||
|
||||
function writeFiles() {
|
||||
/*
|
||||
The output of files which were appended with information during the simulation execution, now needs to be saved.
|
||||
*/
|
||||
if (bot.processingDailyFiles) {
|
||||
writeDailyFiles()
|
||||
} else {
|
||||
writeMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
function writeMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeOutputFile(fileName, filePath, productName) {
|
||||
totalFilesToBeWritten++
|
||||
filePath += '/' + fileName
|
||||
let fileContent = JSON.stringify(outputDatasetsMap.get(productName))
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent, onFileCreated)
|
||||
|
||||
function onFileCreated(err) {
|
||||
try {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath)
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset)
|
||||
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
|
||||
anotherFileWritten()
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function anotherFileWritten() {
|
||||
totalFilesWritten++
|
||||
if (totalFilesWritten === totalFilesToBeWritten) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
exports.newTradingPosition = function newTradingPosition(bot, logger) {
|
||||
/*
|
||||
This module packages all functions related to Positions.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Position'
|
||||
let thisObject = {
|
||||
openPosition: openPosition,
|
||||
closePosition: closePosition,
|
||||
applyStopLossFormula: applyStopLossFormula,
|
||||
applyTakeProfitFormula: applyTakeProfitFormula,
|
||||
preventStopLossDistortion: preventStopLossDistortion,
|
||||
applySlippageToStopLoss: applySlippageToStopLoss,
|
||||
preventTakeProfitDistortion: preventTakeProfitDistortion,
|
||||
applySlippageToTakeProfit: applySlippageToTakeProfit,
|
||||
updateStopLoss: updateStopLoss,
|
||||
updateTakeProfit: updateTakeProfit,
|
||||
updateSizeAndRate: updateSizeAndRate,
|
||||
applySlippageToRate: applySlippageToRate,
|
||||
updateEnds: updateEnds,
|
||||
updateStatus: updateStatus,
|
||||
updateCounters: updateCounters,
|
||||
updateStatistics: updateStatistics,
|
||||
resetPosition: resetPosition,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function openPosition(situationName) {
|
||||
tradingEngine.current.position.status.value = 'Open'
|
||||
tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
|
||||
|
||||
tradingEngine.current.position.situationName.value = situationName
|
||||
|
||||
tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage'
|
||||
tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage'
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = 0
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0
|
||||
}
|
||||
|
||||
function closePosition(exitType) {
|
||||
tradingEngine.current.position.status.value = 'Closed'
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
|
||||
tradingEngine.current.position.exitType.value = exitType
|
||||
/*
|
||||
Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure.
|
||||
*/
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
|
||||
tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position)
|
||||
}
|
||||
|
||||
function resetPosition() {
|
||||
tradingEngine.current.position.initialize(tradingEngine.current.position)
|
||||
}
|
||||
|
||||
function applyStopLossFormula(formulas, formulaId) {
|
||||
tradingEngine.current.position.stopLoss.value = formulas.get(formulaId)
|
||||
}
|
||||
|
||||
function applyTakeProfitFormula(formulas, formulaId) {
|
||||
tradingEngine.current.position.takeProfit.value = formulas.get(formulaId)
|
||||
}
|
||||
|
||||
function updateStopLoss(phase, stage) {
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = phase
|
||||
tradingEngine.current.position.stopLoss.stopLossStage.value = stage
|
||||
}
|
||||
|
||||
function updateTakeProfit(phase, stage) {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase
|
||||
tradingEngine.current.position.takeProfit.takeProfitStage.value = stage
|
||||
}
|
||||
|
||||
function updateSizeAndRate(size, rate) {
|
||||
tradingEngine.current.position.size.value = size
|
||||
tradingEngine.current.position.rate.value = rate
|
||||
}
|
||||
|
||||
function applySlippageToRate() {
|
||||
let slippageAmount = tradingEngine.current.position.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100
|
||||
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value - slippageAmount
|
||||
} else {
|
||||
tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value + slippageAmount
|
||||
}
|
||||
}
|
||||
|
||||
function preventStopLossDistortion() {
|
||||
/*
|
||||
Hit Point Validation
|
||||
|
||||
This prevents misscalculations when a formula places the stop loss in this case way beyond the market price.
|
||||
If we take the stop loss value at those situation would be a huge distortion of facts.
|
||||
*/
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
if (tradingEngine.current.position.stopLoss.value < tradingEngine.current.candle.min.value) {
|
||||
tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.min.value
|
||||
}
|
||||
} else {
|
||||
if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.candle.max.value) {
|
||||
tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.max.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applySlippageToStopLoss() {
|
||||
let slippedStopLoss = tradingEngine.current.position.stopLoss.value
|
||||
|
||||
/* Apply the Slippage */
|
||||
let slippageAmount = slippedStopLoss * bot.SESSION.parameters.slippage.config.stopLoss / 100
|
||||
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
slippedStopLoss = slippedStopLoss + slippageAmount
|
||||
} else {
|
||||
slippedStopLoss = slippedStopLoss - slippageAmount
|
||||
}
|
||||
|
||||
tradingEngine.current.position.endRate.value = slippedStopLoss
|
||||
}
|
||||
|
||||
function preventTakeProfitDistortion() {
|
||||
/*
|
||||
Hit Point Validation:
|
||||
|
||||
This prevents misscalculations when a formula places the take profit in this case way beyond the market price.
|
||||
If we take the stop loss value at those situation would be a huge distortion of facts.
|
||||
*/
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.candle.max.value) {
|
||||
tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.max.value
|
||||
}
|
||||
} else {
|
||||
if (tradingEngine.current.position.takeProfit.value < tradingEngine.current.candle.min.value) {
|
||||
tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.min.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applySlippageToTakeProfit() {
|
||||
let slippedTakeProfit = tradingEngine.current.position.takeProfit.value
|
||||
/* Apply the Slippage */
|
||||
let slippageAmount = slippedTakeProfit * bot.SESSION.parameters.slippage.config.takeProfit / 100
|
||||
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
slippedTakeProfit = slippedTakeProfit + slippageAmount
|
||||
} else {
|
||||
slippedTakeProfit = slippedTakeProfit - slippageAmount
|
||||
}
|
||||
|
||||
tradingEngine.current.position.endRate.value = slippedTakeProfit
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.position.status.value === 'Open') {
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
if (tradingEngine.current.position.status.value === 'Closed') {
|
||||
resetPosition()
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
if (tradingEngine.current.position.status.value === 'Open') {
|
||||
tradingEngine.current.position.positionCounters.periods.value++
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatistics() {
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value
|
||||
tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value
|
||||
} else {
|
||||
tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value
|
||||
tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value
|
||||
}
|
||||
tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
|
||||
if (tradingEngine.current.position.positionStatistics.ROI.value > 0) {
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT'
|
||||
} else {
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES, TRADING_OUTPUT_MODULE, USER_BOT_COMMONS) {
|
||||
/*
|
||||
This Module will load all the process data dependencies from files and send them downstream.
|
||||
After execution, will save the time range and status report of the process.
|
||||
*/
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = false;
|
||||
const MODULE_NAME = "Trading Process";
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger);
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map();
|
||||
let multiPeriodDataFiles = new Map();
|
||||
|
||||
let tradingOutputModule;
|
||||
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME;
|
||||
logger.initialize();
|
||||
|
||||
statusDependencies = pStatusDependencies;
|
||||
dataDependenciesModule = pDataDependencies;
|
||||
processConfig = pProcessConfig;
|
||||
|
||||
let TRADING_OUTPUT_MODULE = require("./TradingOutput")
|
||||
tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE);
|
||||
tradingOutputModule.initialize(callBackFunction);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
dataFiles = undefined
|
||||
multiPeriodDataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
tradingOutputModule.finalize()
|
||||
tradingOutputModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function start(callBackFunction) {
|
||||
try {
|
||||
let currentTimeFrame
|
||||
let currentTimeFrameLabel
|
||||
let market = bot.market;
|
||||
|
||||
/* Context Variables */
|
||||
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
let previousDay; // Holds the date of the previous day relative to the processing date.
|
||||
|
||||
getContextVariables();
|
||||
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let reportKey;
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)
|
||||
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport));
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency not ready."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message);
|
||||
callBackFunction(customOK);
|
||||
return;
|
||||
}
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf());
|
||||
|
||||
/* Validation that the data is not up-to-date. */
|
||||
if (bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) }
|
||||
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
bot.simulationState = thisReport.simulationState;
|
||||
if (bot.simulationState === undefined) { bot.simulationState = {} } // This should happen only when there is no status report
|
||||
|
||||
if (thisReport.lastFile !== undefined) {
|
||||
if (bot.RESUME !== true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); }
|
||||
startFromBegining();
|
||||
return;
|
||||
}
|
||||
contextVariables.lastFile = new Date(thisReport.lastFile);
|
||||
processSingleFiles();
|
||||
return;
|
||||
|
||||
} else {
|
||||
/*
|
||||
We are here becuse either:
|
||||
1. There is no status report
|
||||
2. There is no Last File (this happens on Market Files)
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); }
|
||||
startFromBegining();
|
||||
return;
|
||||
}
|
||||
|
||||
function startFromBegining() {
|
||||
contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); }
|
||||
processSingleFiles();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack);
|
||||
if (err.message === "Cannot read property 'file' of undefined") {
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. ");
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys));
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function processSingleFiles() {
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map();
|
||||
dependencyLoopBody();
|
||||
|
||||
function dependencyLoopBody() {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
getFile();
|
||||
|
||||
function getFile() {
|
||||
if (datasetModule.node.config.codeName !== "Single-File") {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName;
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); }
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (err.message === 'File does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message);
|
||||
callBackFunction(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
|
||||
dependencyControlLoop();
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dependencyControlLoop() {
|
||||
dependencyIndex++
|
||||
if (dependencyIndex < dataDependenciesModule.nodeArray.length) {
|
||||
dependencyLoopBody()
|
||||
} else {
|
||||
let mapKey = "Single Files"
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
processMarketFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
We do market files first since if the simulation is run on daily files, there will be a loop to getch each daus files and we do not need
|
||||
that loop to reload market files.
|
||||
*/
|
||||
function processMarketFiles() {
|
||||
let n;
|
||||
timeFramesLoop();
|
||||
|
||||
function timeFramesLoop() {
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
n = 0 // loop Variable representing each possible period as defined at the timeFrames array.
|
||||
timeFramesLoopBody();
|
||||
}
|
||||
|
||||
function timeFramesLoopBody() {
|
||||
const timeFrame = global.marketFilesPeriods[n][0];
|
||||
const timeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.marketFilesPeriods[n][0];
|
||||
currentTimeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
dependencyLoopBody();
|
||||
|
||||
function dependencyLoopBody() {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
getFile();
|
||||
|
||||
function getFile() {
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel;
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (err.message === 'File does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message);
|
||||
callBackFunction(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(text);
|
||||
let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record)
|
||||
dataFiles.set(dependency.id, trimmedDataFile);
|
||||
|
||||
dependencyControlLoop();
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dependencyControlLoop() {
|
||||
dependencyIndex++;
|
||||
if (dependencyIndex < dataDependenciesModule.nodeArray.length) {
|
||||
dependencyLoopBody();
|
||||
} else {
|
||||
let mapKey = global.marketFilesPeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
timeFramesControlLoop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function timeFramesControlLoop() {
|
||||
n++;
|
||||
if (n < global.marketFilesPeriods.length) {
|
||||
timeFramesLoopBody();
|
||||
} else {
|
||||
processDailyFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processDailyFiles() {
|
||||
let n = 0;
|
||||
if (currentTimeFrame) {
|
||||
buildCharts()
|
||||
return
|
||||
}
|
||||
bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop.
|
||||
advanceTime();
|
||||
|
||||
function advanceTime() {
|
||||
bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS);
|
||||
previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> previousDay = " + previousDay); }
|
||||
|
||||
/* Validation that we are not going past the user defined end date. */
|
||||
if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) {
|
||||
|
||||
const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); }
|
||||
|
||||
bot.SESSION.stop(logText)
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validation that we are not going past the head of the market. */
|
||||
if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) {
|
||||
|
||||
const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); }
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
checkStopTaskGracefully();
|
||||
}
|
||||
|
||||
function checkStopTaskGracefully() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
checkStopProcessing();
|
||||
}
|
||||
|
||||
function checkStopProcessing() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
timeFramesLoop();
|
||||
}
|
||||
|
||||
function timeFramesLoop() {
|
||||
/* Telling the world we are alive and doing well */
|
||||
let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDate)
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
n = 0 // loop Variable representing each possible period as defined at the timeFrames array.
|
||||
timeFramesLoopBody();
|
||||
}
|
||||
|
||||
function timeFramesLoopBody() {
|
||||
const timeFrame = global.dailyFilePeriods[n][0];
|
||||
const timeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validtimeFrames !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) {
|
||||
let period = processConfig.framework.validtimeFrames[i];
|
||||
if (period === timeFrameLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
timeFramesControlLoop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.dailyFilePeriods[n][0];
|
||||
currentTimeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map();
|
||||
dependencyLoopBody();
|
||||
|
||||
function dependencyLoopBody() {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
let file
|
||||
getFile()
|
||||
|
||||
function getFile() {
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
dependencyControlLoop();
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json";
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> Not Ok -> err = " + err.code);
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
file = JSON.parse(text);
|
||||
dataFiles.set(dependency.id, file);
|
||||
dependencyControlLoop();
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dependencyControlLoop() {
|
||||
dependencyIndex++;
|
||||
|
||||
if (dependencyIndex < dataDependenciesModule.nodeArray.length) {
|
||||
dependencyLoopBody();
|
||||
} else {
|
||||
let mapKey = global.dailyFilePeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
timeFramesControlLoop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function timeFramesControlLoop() {
|
||||
n++;
|
||||
if (n < global.dailyFilePeriods.length) {
|
||||
timeFramesLoopBody();
|
||||
} else {
|
||||
n = 0;
|
||||
if (currentTimeFrame !== undefined) {
|
||||
buildCharts(advanceTime);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildCharts(advanceTime) {
|
||||
const COMMONS = require('../Commons.js')
|
||||
let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let chart = {}
|
||||
let mainDependency = {}
|
||||
|
||||
/* The first phase here is about checking that we have everything we need at the definition level. */
|
||||
let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies
|
||||
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
|
||||
|
||||
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
|
||||
if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return }
|
||||
|
||||
/* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */
|
||||
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
|
||||
let timeFrameLabel = marketFilesPeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let timeFrameLabel = global.dailyFilePeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
/* Single Files */
|
||||
let dataFiles = multiPeriodDataFiles.get('Single Files')
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
chart[propertyName] = products
|
||||
}
|
||||
|
||||
tradingOutputModule.start(
|
||||
chart,
|
||||
currentTimeFrame,
|
||||
currentTimeFrameLabel,
|
||||
bot.tradingProcessDate,
|
||||
onOutputGenerated);
|
||||
|
||||
function onOutputGenerated(err) {
|
||||
try {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution.
|
||||
|
||||
if (currentTimeFrame > global.dailyFilePeriods[0][0]) {
|
||||
writeMarketStatusReport(onMarketStatusReport)
|
||||
} else {
|
||||
writeDataRanges(currentTimeFrameLabel, onWritten);
|
||||
}
|
||||
|
||||
function onWritten(err) {
|
||||
try {
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack);
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
writeDailyStatusReport(bot.tradingProcessDate, onDailyStatusReport);
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function onDailyStatusReport() {
|
||||
advanceTime();
|
||||
}
|
||||
|
||||
function onMarketStatusReport() {
|
||||
/* This is where we check if we need reached the user defined end datetime. */
|
||||
const now = new Date()
|
||||
if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) {
|
||||
const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onMarketStatusReport -> " + logText); }
|
||||
|
||||
bot.SESSION.stop('because it just finished.')
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
bot.SESSION.stop('Backtesting Session over Market Files Finished.')
|
||||
}
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeDataRanges(currentTimeFrameLabel, callBack) {
|
||||
let outputDatasetIndex = -1;
|
||||
controlLoop();
|
||||
|
||||
function productLoopBody() {
|
||||
let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel, controlLoop);
|
||||
}
|
||||
|
||||
function controlLoop() {
|
||||
outputDatasetIndex++;
|
||||
if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) {
|
||||
productLoopBody();
|
||||
} else {
|
||||
callBack(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel, callBack) {
|
||||
let dataRange = {
|
||||
begin: pBegin.valueOf(),
|
||||
end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
};
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated);
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack);
|
||||
callBack(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG_FILE_CONTENT === true) {
|
||||
logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent);
|
||||
}
|
||||
|
||||
let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
dateRange: dataRange
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event)
|
||||
|
||||
callBack(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function writeDailyStatusReport(lastFileDate, callBack) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey);
|
||||
|
||||
thisReport.file.lastExecution = bot.currentDaytime;
|
||||
thisReport.file.lastFile = lastFileDate;
|
||||
thisReport.file.simulationState = bot.simulationState;
|
||||
thisReport.save(callBack);
|
||||
}
|
||||
|
||||
function writeMarketStatusReport(callBack) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey);
|
||||
|
||||
thisReport.file.lastExecution = bot.processDatetime;
|
||||
thisReport.file.simulationState = bot.simulationState;
|
||||
|
||||
logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate);
|
||||
thisReport.save(callBack);
|
||||
}
|
||||
|
||||
function trimDataFile(dataFile, recordDefinition) {
|
||||
/*
|
||||
Here we will discard all the records in a file that are outside of the current time range.
|
||||
*/
|
||||
let beginIndex
|
||||
let endIndex
|
||||
let result = []
|
||||
for (let i = 0; i < recordDefinition.properties.length; i++) {
|
||||
let property = recordDefinition.properties[i]
|
||||
if (property.config.codeName === 'begin') {
|
||||
beginIndex = i
|
||||
}
|
||||
if (property.config.codeName === 'end') {
|
||||
endIndex = i
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < dataFile.length; i++) {
|
||||
let dataRecord = dataFile[i]
|
||||
let begin = dataRecord[beginIndex]
|
||||
let end = dataRecord[endIndex]
|
||||
if (end < bot.SESSION.parameters.timeRange.config.initialDatetime) { continue }
|
||||
if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue }
|
||||
result.push(dataRecord)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,264 @@
|
||||
exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
/*
|
||||
This module facilitates the appending of records to the output of the process.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Records'
|
||||
|
||||
let thisObject = {
|
||||
appendRecords: appendRecords,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
let outputDatasetsMap
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize(pOutputDatasetsMap) {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
outputDatasetsMap = pOutputDatasetsMap // These are the files turned into arrays, stored in a Map by Product codeName.
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
outputDatasetsMap = undefined
|
||||
}
|
||||
|
||||
function appendRecords() {
|
||||
/*
|
||||
Here we add records to the output files. At the product config property nodePath
|
||||
we have a pointer to the node that have the information we need to extract.
|
||||
Later, based on the product record definition we will extract each individual value.
|
||||
*/
|
||||
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
let product = dataset.parentNode
|
||||
let outputDatasetArray = outputDatasetsMap.get(product.config.codeName)
|
||||
|
||||
if (bot.processingDailyFiles === true && dataset.config.type === 'Daily Files') {
|
||||
persistRecord()
|
||||
}
|
||||
|
||||
if (bot.processingDailyFiles === false && dataset.config.type === 'Market Files') {
|
||||
persistRecord()
|
||||
}
|
||||
|
||||
function persistRecord() {
|
||||
pruneOutputFile(product, outputDatasetArray)
|
||||
let record = scanRecordDefinition(product)
|
||||
|
||||
if (product.config.saveAsObjects === true) {
|
||||
/* For saving objects we need to take care of a different set of rules. */
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
|
||||
let propertyValue = record[j]
|
||||
/*
|
||||
We check that the existing file does not contain at the end a record that
|
||||
does not signal that the object is closed.
|
||||
If it does, we replace it with the new one until at some point it will signal
|
||||
does the object is closed.
|
||||
*/
|
||||
let lastRecord = outputDatasetArray[outputDatasetArray.length - 1]
|
||||
if (lastRecord !== undefined) {
|
||||
let lastRecordValue = lastRecord[j]
|
||||
if (lastRecordValue !== product.config.propertyValueThatClosesObject) {
|
||||
outputDatasetArray.splice(outputDatasetArray.length - 1, 1)
|
||||
}
|
||||
}
|
||||
if (bot.processingDailyFiles) {
|
||||
/*
|
||||
When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day,
|
||||
since the object will be duplicated on the next day. How do we know we are positioned at the last candle
|
||||
of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any
|
||||
time frame.
|
||||
*/
|
||||
let currentDay = new Date(tradingEngine.current.candle.end.value)
|
||||
let nextDay = new Date(tradingEngine.current.candle.end.value + 1)
|
||||
if (currentDay.getUTCDate() !== nextDay.getUTCDate()) {
|
||||
/*
|
||||
We will save the object only if it is closed, becasuse we are at the last candle of the day.
|
||||
*/
|
||||
if (propertyValue === product.config.propertyValueThatClosesObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
When we are not at the end of the day, we will save the object normally, like in market files.
|
||||
*/
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
For Market Files we will add a record everytime that proeprty value does not match this
|
||||
*/
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* When we are not dealing with objects, we add every record to the existing file. */
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanRecordDefinition(product) {
|
||||
/*
|
||||
The Product Root Node is the root of the node hiriarchy branch that we need to look at
|
||||
in order to fnd the node where we are going to extract the value.
|
||||
*/
|
||||
let productRootNode = eval(product.config.nodePath)
|
||||
let record = []
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
/*
|
||||
The Property Root Node is the Root of the Hiriarchy branch we must find in order
|
||||
to get the node where we are going to extract the value. Initially
|
||||
we point it to the Product Root Node, because that is the default in case
|
||||
a property does not have at its configuration a different nodePath configured
|
||||
pointing to an specific Root for the property.
|
||||
*/
|
||||
let propertyRootNode = productRootNode
|
||||
/*
|
||||
If we find at the configuration a nodePath, then we take this path to find
|
||||
the Root Node specifically for this property only.
|
||||
*/
|
||||
if (recordProperty.config.nodePath !== undefined) {
|
||||
propertyRootNode = eval(recordProperty.config.nodePath)
|
||||
}
|
||||
/*
|
||||
The Target Node is the node from where we are going to exctract the value.
|
||||
We will use the codeName of the Record Property to match it with
|
||||
the any of the properties of the Root Node to get the Target Node.
|
||||
*/
|
||||
let targetNode = propertyRootNode[recordProperty.config.codeName]
|
||||
/*
|
||||
If the codeName of the Record Property can not match the name of the property at
|
||||
the target node, the user can explicitly specify the property name at the configuration,
|
||||
and in those cases we need to use that. This happens when there are many Record Properties
|
||||
pointing to the same property at the Target Node.
|
||||
*/
|
||||
if (recordProperty.config.childProperty !== undefined) {
|
||||
targetNode = propertyRootNode[recordProperty.config.childProperty]
|
||||
}
|
||||
/*
|
||||
It can happen that intead of having a Node in targetNode what we have is an
|
||||
array of nodes. We need to pick one of the elements of the array and for that
|
||||
we use the Index value we find at the configuration of the Record Property.
|
||||
*/
|
||||
if (recordProperty.config.index !== undefined) {
|
||||
targetNode = targetNode[recordProperty.config.index]
|
||||
}
|
||||
/*
|
||||
By Default the value is extracted from the value property of the Target Node.
|
||||
But it might happen the the Target Node does not exist for example when there is an Array
|
||||
of Nodes defined in the Record Properties but not all of them exist at the Root Node.
|
||||
We filter out those cases by not extracting the value from the value property.
|
||||
*/
|
||||
let value = 0 // This is a default value, since we do not want null in files because it breakes JSON format.
|
||||
if (targetNode !== undefined) {
|
||||
if (targetNode.type !== undefined) {
|
||||
/*
|
||||
In this case the Target Node is really a node (since it has a type), so we extract the value
|
||||
from its value property.
|
||||
*/
|
||||
value = targetNode.value
|
||||
|
||||
if (recordProperty.config.decimals !== undefined) {
|
||||
value = Number(value.toFixed(recordProperty.config.decimals))
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
In this case the Target Node is not really node, but the value itself. Se we return this as the
|
||||
value of the Record Property.
|
||||
*/
|
||||
value = targetNode
|
||||
}
|
||||
}
|
||||
if (recordProperty.config.isString !== true && Array.isArray(value) !== true) {
|
||||
value = safeNumericValue(value)
|
||||
}
|
||||
if (recordProperty.config.isString === true && Array.isArray(value) !== true) {
|
||||
value = safeStringValue(value)
|
||||
}
|
||||
record.push(value)
|
||||
}
|
||||
return record
|
||||
}
|
||||
}
|
||||
|
||||
function safeNumericValue(value) {
|
||||
/*
|
||||
The purpose of this function is to check that value variable does not have a value that
|
||||
will later break the JSON format of files where this is going to be stored at.
|
||||
*/
|
||||
if (value === Infinity) {
|
||||
value = Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
if (value === undefined) {
|
||||
value = 0
|
||||
}
|
||||
if (value === null) {
|
||||
value = 0
|
||||
}
|
||||
if (isNaN(value)) {
|
||||
value = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function safeStringValue(value) {
|
||||
/*
|
||||
The purpose of this function is to check that value variable does not have a value that
|
||||
will later break the JSON format of files where this is going to be stored at.
|
||||
*/
|
||||
if (value === undefined) {
|
||||
value = ''
|
||||
}
|
||||
if (value === null) {
|
||||
value = ''
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function pruneOutputFile(product, outputFile) {
|
||||
if (outputFile.isPrunned === true) { return }
|
||||
/*
|
||||
When a session is resumed, we will be potentially reading output files belonging to a previous session execution.
|
||||
For that reason we need to prune all the records that are beyond the current candle. We do not delete everything
|
||||
because we might be resuming a stopped session, which is fine.
|
||||
*/
|
||||
for (let i = 0; i < outputFile.length; i++) {
|
||||
let record = outputFile[i]
|
||||
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === 'end') {
|
||||
let end = record[j]
|
||||
if (end >= tradingEngine.current.candle.end.value) {
|
||||
outputFile.splice(i, 1)
|
||||
pruneOutputFile(product, outputFile)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
outputFile.isPrunned = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) {
|
||||
/*
|
||||
This Module represents the trading simulacion. Escentially a loop through a set of candles and
|
||||
the execution at each loop cycle of the Trading System Protocol.
|
||||
*/
|
||||
const FULL_LOG = true
|
||||
const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name
|
||||
|
||||
let thisObject = {
|
||||
finalize: finalize,
|
||||
runSimulation: runSimulation
|
||||
}
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(bot, logger)
|
||||
|
||||
return thisObject
|
||||
|
||||
function finalize() {
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function runSimulation(
|
||||
chart,
|
||||
outputDatasetsMap,
|
||||
callback,
|
||||
callBackFunction) {
|
||||
try {
|
||||
|
||||
let tradingSystem = bot.simulationState.tradingSystem
|
||||
let tradingEngine = bot.simulationState.tradingEngine
|
||||
let sessionParameters = bot.SESSION.parameters
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) }
|
||||
|
||||
/* Stop Loss Management TODO */
|
||||
const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result.
|
||||
const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER
|
||||
|
||||
/* Take Profit Management TODO */
|
||||
const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result.
|
||||
const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER
|
||||
|
||||
/* Variables to know when we need to open or close a position. */
|
||||
let triggeringOn
|
||||
let triggeringOff
|
||||
let takingPosition
|
||||
let closingPosition
|
||||
|
||||
/* These are the Modules we will need to run the Simulation */
|
||||
const EXCHANGE_API = require('../ExchangeAPI');
|
||||
exchangeAPI = EXCHANGE_API.newExchangeAPI(logger, bot);
|
||||
exchangeAPI.initialize();
|
||||
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
tradingEngineModule.initialize()
|
||||
|
||||
const TRADING_ENGINE_EXECUTION = require('./TradingExecution.js')
|
||||
let tradingExecutionModule = TRADING_ENGINE_EXECUTION.newTradingExecution(bot, logger)
|
||||
tradingExecutionModule.initialize()
|
||||
|
||||
const TRADING_RECORDS_MODULE = require('./TradingRecords.js')
|
||||
let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger)
|
||||
tradingRecordsModule.initialize(outputDatasetsMap)
|
||||
|
||||
const TRADING_SYSTEM_MODULE = require('./TradingSystem.js')
|
||||
let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger)
|
||||
tradingSystemModule.initialize()
|
||||
|
||||
/* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
/* Variables needed for heartbeat functionality */
|
||||
let heartBeatDate
|
||||
let previousHeartBeatDate
|
||||
|
||||
/* This determines where we are standing */
|
||||
bot.sessionAndMarketBaseAssetsAreEqual = (sessionParameters.sessionBaseAsset.referenceParent.referenceParent.config.codeName === bot.market.baseAsset)
|
||||
|
||||
initializeLoop()
|
||||
|
||||
function initializeLoop() {
|
||||
if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) {
|
||||
/* Estimate the Initial Candle based on the last candle saved at tradingEngine */
|
||||
let firstBegin = candles[0].begin
|
||||
let lastBegin = tradingEngine.last.candle.begin
|
||||
let diff = lastBegin - firstBegin
|
||||
let amount = diff / sessionParameters.timeFrame.config.value
|
||||
|
||||
tradingEngine.current.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle
|
||||
if (tradingEngine.current.candle.index.value < 0 || tradingEngine.current.candle.index.value > candles.length - 1) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.')
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.candle.index.value = ' + tradingEngine.current.candle.index.value)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount)
|
||||
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
/* Estimate Initial Candle based on the timeRage configured for the session. */
|
||||
let firstEnd = candles[0].end
|
||||
let targetEnd = sessionParameters.timeRange.config.initialDatetime
|
||||
let diff = targetEnd - firstEnd
|
||||
let amount = diff / sessionParameters.timeFrame.config.value
|
||||
|
||||
tradingEngine.current.candle.index.value = Math.trunc(amount)
|
||||
if (tradingEngine.current.candle.index.value < 0) { tradingEngine.current.candle.index.value = 0 }
|
||||
if (tradingEngine.current.candle.index.value > candles.length - 1) {
|
||||
/*
|
||||
This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available,
|
||||
meaning that the dataSet needs to be updated with more up-to-date data.
|
||||
*/
|
||||
tradingEngine.current.candle.index.value = candles.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
loop()
|
||||
}
|
||||
|
||||
function loop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.candle.index.value) }
|
||||
let candle = candles[tradingEngine.current.candle.index.value] // This is the current candle the Simulation is working at.
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) }
|
||||
|
||||
tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas.
|
||||
|
||||
if (checkIfWeAreAtTheHeadOfTheMarket() === false) {
|
||||
afterLoop()
|
||||
return
|
||||
}
|
||||
|
||||
if (checkMinimunAndMaximunBalance() === false) {
|
||||
afterLoop()
|
||||
return
|
||||
}
|
||||
|
||||
if (checkInitialAndFinalDatetime() === false) {
|
||||
controlLoop()
|
||||
return
|
||||
}
|
||||
|
||||
heartBeat()
|
||||
positionChartAtCurrentCandle()
|
||||
tradingSystemModule.reset(chart)
|
||||
tradingSystemModule.mantainStrategies()
|
||||
tradingSystemModule.mantainPositions()
|
||||
|
||||
tradingEngineModule.updateEpisodeCountersAndStatistics()
|
||||
/*
|
||||
The next step is about evaluationg Conditions and Formulas of the Trading System.
|
||||
*/
|
||||
tradingSystemModule.evalConditions()
|
||||
tradingSystemModule.evalFormulas()
|
||||
/*
|
||||
Next we check if we will be triggering on, off or taking position.
|
||||
*/
|
||||
triggeringOn = tradingSystemModule.checkTriggerOn()
|
||||
triggeringOff = tradingSystemModule.checkTriggerOff()
|
||||
takingPosition = tradingSystemModule.checkTakePosition()
|
||||
|
||||
/* Stop Loss Management */
|
||||
if (takingPosition !== true) {
|
||||
tradingSystemModule.checkStopPhasesEvents()
|
||||
tradingSystemModule.calculateStopLoss()
|
||||
}
|
||||
|
||||
/* Take Profit Management */
|
||||
if (takingPosition !== true) {
|
||||
tradingSystemModule.checkTakeProfitPhaseEvents()
|
||||
tradingSystemModule.calculateTakeProfit()
|
||||
}
|
||||
|
||||
tradingEngineModule.updateDistanceToEventsCounters()
|
||||
|
||||
/* Checking if Stop or Take Profit were hit */
|
||||
if (takingPosition !== true) {
|
||||
closingPosition = tradingSystemModule.checkStopLossOrTakeProfitWasHit()
|
||||
}
|
||||
|
||||
/* Taking a Position */
|
||||
if (takingPosition === true) {
|
||||
tradingSystemModule.getReadyToTakePosition()
|
||||
/*
|
||||
Whem we calculated all the formulas at the begining of the loop, we didn't know
|
||||
we were going to take position in this same loop cycle. Some might formulas depends on
|
||||
calculations that are triggered only when we are taking position. For that reason
|
||||
we will calculate all formulas again, and after that the Stop Loss and Take Profit.
|
||||
*/
|
||||
tradingSystemModule.evalFormulas()
|
||||
tradingSystemModule.calculateTakeProfit()
|
||||
tradingSystemModule.calculateStopLoss()
|
||||
|
||||
tradingExecutionModule.takePosition()
|
||||
tradingSystemModule.takePosition()
|
||||
takingPosition = false
|
||||
}
|
||||
|
||||
/* Closing a Position */
|
||||
if (closingPosition === true) {
|
||||
tradingSystemModule.getReadyToClosePosition()
|
||||
tradingExecutionModule.closePosition()
|
||||
tradingSystemModule.closePosition()
|
||||
closingPosition = false
|
||||
}
|
||||
|
||||
/* After a position was closed, we need to close the strategy. */
|
||||
tradingSystemModule.exitStrategyAfterPosition()
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
controlLoop()
|
||||
}
|
||||
|
||||
function controlLoop() {
|
||||
/* Checking if we should continue processing this loop or not. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') }
|
||||
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.')
|
||||
afterLoop()
|
||||
return
|
||||
}
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') }
|
||||
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.')
|
||||
afterLoop()
|
||||
return
|
||||
}
|
||||
|
||||
tradingEngine.current.candle.index.value++
|
||||
if (tradingEngine.current.candle.index.value < candles.length) {
|
||||
setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed.
|
||||
} else {
|
||||
afterLoop()
|
||||
}
|
||||
}
|
||||
|
||||
function afterLoop() {
|
||||
tradingEngineModule.finalize()
|
||||
tradingExecutionModule.finalize()
|
||||
tradingSystemModule.finalize()
|
||||
tradingRecordsModule.finalize()
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
function heartBeat() {
|
||||
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
|
||||
heartBeatDate = new Date(Math.trunc(tradingEngine.current.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
|
||||
if (heartBeatDate.valueOf() !== previousHeartBeatDate) {
|
||||
let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.candle.index.value + ' @ ' + processingDate) }
|
||||
|
||||
/* Telling the world we are alive and doing well */
|
||||
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime)
|
||||
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime)
|
||||
|
||||
let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
|
||||
let currentDate = new Date(heartBeatDate)
|
||||
let percentage = global.getPercentage(fromDate, currentDate, lastDate)
|
||||
bot.processHeartBeat(currentDateString, percentage)
|
||||
|
||||
if (global.areEqualDates(currentDate, new Date()) === false) {
|
||||
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
|
||||
}
|
||||
}
|
||||
previousHeartBeatDate = heartBeatDate.valueOf()
|
||||
}
|
||||
|
||||
function positionChartAtCurrentCandle() {
|
||||
/*
|
||||
In conditions and Formulas, we want users to have an easy sintax to refer to indicators. In order to achieve that, we need the user to have
|
||||
easy access to the current candle for instance, or the current bollinger band, meaning the one the Simulation is currently standing at.
|
||||
For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame.
|
||||
*/
|
||||
let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies
|
||||
|
||||
/* Finding the Current Element on Market Files */
|
||||
if (bot.processingDailyFiles) {
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let mapKey = dailyFilePeriods[j][1]
|
||||
let propertyName = 'at' + mapKey.replace('-', '')
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName)
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finding the Current Element on Market Files */
|
||||
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
|
||||
let mapKey = marketFilesPeriods[j][1]
|
||||
let propertyName = 'at' + mapKey.replace('-', '')
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName)
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
|
||||
/* Finding the Current Element At Single Files*/
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
let elementArray = thisChart[pluralVariableName]
|
||||
let currentElement
|
||||
if (elementArray !== undefined) {
|
||||
currentElement = elementArray[elementArray.length - 1]
|
||||
}
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
|
||||
function getElement(pArray, datasetName) {
|
||||
if (pArray === undefined) { return }
|
||||
try {
|
||||
let element
|
||||
for (let i = 0; i < pArray.length; i++) {
|
||||
element = pArray[i]
|
||||
|
||||
if (tradingEngine.current.candle.end.value === element.end) { // when there is an exact match at the end we take that element
|
||||
return element
|
||||
} else {
|
||||
if (
|
||||
i > 0 &&
|
||||
element.end > tradingEngine.current.candle.end.value
|
||||
) {
|
||||
let previousElement = pArray[i - 1]
|
||||
if (previousElement.end < tradingEngine.current.candle.end.value) {
|
||||
return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element.
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfWeAreAtTheHeadOfTheMarket() {
|
||||
/*
|
||||
We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different
|
||||
when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6
|
||||
*/
|
||||
if (bot.processingDailyFiles) { // We are processing Daily Files
|
||||
let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
|
||||
if (tradingEngine.current.candle.index.value === candles.length - 1) {
|
||||
if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
|
||||
return false
|
||||
/*
|
||||
Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators.
|
||||
if we skip these two periods, then there will be a hole in the file since the last period will be missing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
} else { // We are processing Market Files
|
||||
if (tradingEngine.current.candle.index.value === candles.length - 1) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkInitialAndFinalDatetime() {
|
||||
/* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/
|
||||
if (tradingEngine.current.candle.end.value < sessionParameters.timeRange.config.initialDatetime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') }
|
||||
return false
|
||||
}
|
||||
if (tradingEngine.current.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkMinimunAndMaximunBalance() {
|
||||
/* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */
|
||||
if (
|
||||
tradingEngine.current.strategy.stageType.value === 'No Stage' &&
|
||||
tradingEngine.current.strategy.index.value === -1
|
||||
) {
|
||||
/*
|
||||
First thing to do is to check if we are below the minimum balance, or above the maximun balance.
|
||||
If we are, we are not gooing to trigger on again.
|
||||
*/
|
||||
let minimumBalance
|
||||
let maximumBalance
|
||||
let balance
|
||||
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
balance = tradingEngine.current.balance.baseAsset.value
|
||||
minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance
|
||||
maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance
|
||||
} else {
|
||||
balance = tradingEngine.current.balance.quotedAsset.value
|
||||
minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance
|
||||
maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance
|
||||
}
|
||||
|
||||
let stopRunningDate = new Date(tradingEngine.current.candle.begin.value)
|
||||
if (balance <= minimumBalance) {
|
||||
tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString()
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
|
||||
return false
|
||||
}
|
||||
|
||||
if (balance >= maximumBalance) {
|
||||
tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString()
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
exports.newTradingStrategy = function newTradingStrategy(bot, logger) {
|
||||
/*
|
||||
This module packages all functions related to Strategies.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Strategy'
|
||||
let thisObject = {
|
||||
openStrategy: openStrategy,
|
||||
closeStrategy: closeStrategy,
|
||||
updateEnds: updateEnds,
|
||||
updateStageType: updateStageType,
|
||||
updateStatus: updateStatus,
|
||||
updateCounters: updateCounters,
|
||||
resetStrategy: resetStrategy,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
}
|
||||
|
||||
function openStrategy(stageType, index, situationName, strategyName) {
|
||||
tradingEngine.current.strategy.status.value = 'Open'
|
||||
tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value
|
||||
tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
|
||||
|
||||
tradingEngine.current.strategy.stageType.value = stageType
|
||||
tradingEngine.current.strategy.index.value = index
|
||||
tradingEngine.current.strategy.situationName.value = situationName
|
||||
tradingEngine.current.strategy.strategyName.value = strategyName
|
||||
}
|
||||
|
||||
function closeStrategy(exitType) {
|
||||
tradingEngine.current.strategy.status.value = 'Closed'
|
||||
tradingEngine.current.strategy.exitType.value = exitType
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
|
||||
/*
|
||||
Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure.
|
||||
*/
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
|
||||
tradingEngineModule.cloneValues(tradingEngine.current.strategy, tradingEngine.last.strategy)
|
||||
}
|
||||
|
||||
function resetStrategy() {
|
||||
tradingEngine.current.strategy.initialize(tradingEngine.current.strategy)
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Open') {
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateStageType(stageType) {
|
||||
tradingEngine.current.strategy.stageType.value = stageType
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Closed') {
|
||||
resetStrategy()
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Open') {
|
||||
tradingEngine.current.strategy.strategyCounters.periods.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,963 @@
|
||||
exports.newTradingSystem = function newTradingSystem(bot, logger) {
|
||||
/*
|
||||
The Trading System is the user defined set of rules compliant with the Trading Protocol that
|
||||
defines the trading logic to be applied during each cycle of the Simulation.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading System'
|
||||
let thisObject = {
|
||||
reset: reset,
|
||||
mantainStrategies: mantainStrategies,
|
||||
mantainPositions: mantainPositions,
|
||||
evalConditions: evalConditions,
|
||||
evalFormulas: evalFormulas,
|
||||
checkTriggerOn: checkTriggerOn,
|
||||
checkTriggerOff: checkTriggerOff,
|
||||
checkTakePosition: checkTakePosition,
|
||||
checkStopPhasesEvents: checkStopPhasesEvents,
|
||||
calculateStopLoss: calculateStopLoss,
|
||||
checkTakeProfitPhaseEvents: checkTakeProfitPhaseEvents,
|
||||
calculateTakeProfit: calculateTakeProfit,
|
||||
checkStopLossOrTakeProfitWasHit: checkStopLossOrTakeProfitWasHit,
|
||||
getReadyToTakePosition: getReadyToTakePosition,
|
||||
takePosition: takePosition,
|
||||
getReadyToClosePosition: getReadyToClosePosition,
|
||||
closePosition: closePosition,
|
||||
exitStrategyAfterPosition: exitStrategyAfterPosition,
|
||||
getPositionSize: getPositionSize,
|
||||
getPositionRate: getPositionRate,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let chart
|
||||
|
||||
let tradingSystem
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
|
||||
let conditions = new Map()
|
||||
let formulas = new Map()
|
||||
|
||||
const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js')
|
||||
let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger)
|
||||
|
||||
const TRADING_POSITION_MODULE = require('./TradingPosition.js')
|
||||
let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger)
|
||||
|
||||
const ANNOUNCEMENTS_MODULE = require('./Announcements.js')
|
||||
let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger)
|
||||
|
||||
const SNAPSHOTS_MODULE = require('./Snapshots.js')
|
||||
let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger)
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
|
||||
tradingStrategyModule.initialize()
|
||||
tradingPositionModule.initialize()
|
||||
announcementsModule.initialize()
|
||||
snapshotsModule.initialize()
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingStrategyModule.finalize()
|
||||
tradingStrategyModule = undefined
|
||||
|
||||
tradingPositionModule.finalize()
|
||||
tradingPositionModule = undefined
|
||||
|
||||
announcementsModule.finalize()
|
||||
announcementsModule = undefined
|
||||
|
||||
snapshotsModule.finalize()
|
||||
snapshotsModule = undefined
|
||||
|
||||
chart = undefined
|
||||
|
||||
conditions = undefined
|
||||
formulas = undefined
|
||||
|
||||
tradingSystem.highlights = undefined
|
||||
tradingSystem.errors = undefined
|
||||
tradingSystem.values = undefined
|
||||
tradingSystem.status = undefined
|
||||
tradingSystem.progress = undefined
|
||||
tradingSystem.running = undefined
|
||||
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function reset(pChart) {
|
||||
/* This function helps reset data structures at every cycle of the simulation loop/=. */
|
||||
chart = pChart // We need chat to be a local object accessible from conditions and formulas.
|
||||
snapshotsModule.reset(chart)
|
||||
|
||||
tradingSystem.highlights = []
|
||||
tradingSystem.errors = []
|
||||
tradingSystem.values = []
|
||||
tradingSystem.status = []
|
||||
tradingSystem.progress = []
|
||||
tradingSystem.running = []
|
||||
tradingSystem.announcements = []
|
||||
}
|
||||
|
||||
function mantainStrategies() {
|
||||
tradingStrategyModule.updateStatus()
|
||||
tradingStrategyModule.updateCounters()
|
||||
tradingStrategyModule.updateEnds()
|
||||
}
|
||||
|
||||
function mantainPositions() {
|
||||
tradingPositionModule.updateStatus()
|
||||
tradingPositionModule.updateCounters()
|
||||
tradingPositionModule.updateEnds()
|
||||
}
|
||||
|
||||
function evalConditions() {
|
||||
evalNode(bot.simulationState.tradingSystem, 'Conditions')
|
||||
}
|
||||
|
||||
function evalFormulas() {
|
||||
evalNode(bot.simulationState.tradingSystem, 'Formulas')
|
||||
}
|
||||
|
||||
function evalNode(node, evaluating) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we check if there is a codition to be evaluated */
|
||||
if (node.type === 'Condition' && evaluating === 'Conditions') {
|
||||
/* We will eval this condition */
|
||||
evalCondition(node)
|
||||
}
|
||||
|
||||
/* Here we check if there is a formula to be evaluated */
|
||||
if (node.type === 'Formula' && evaluating === 'Formulas') {
|
||||
if (node.code !== undefined) {
|
||||
/* We will eval this formula */
|
||||
evalFormula(node)
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
evalNode(node[property.name], evaluating)
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
evalNode(nodePropertyArray[m], evaluating)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function evalCondition(node) {
|
||||
let value
|
||||
let error
|
||||
/*
|
||||
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
|
||||
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
|
||||
from there. Otherwise we will take the code from the Condition node.
|
||||
*/
|
||||
let code = node.code
|
||||
if (node.javascriptCode !== undefined) {
|
||||
if (node.javascriptCode.code !== undefined) {
|
||||
code = node.javascriptCode.code
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> code = ' + code)
|
||||
value = eval(code)
|
||||
} catch (err) {
|
||||
/*
|
||||
One possible error is that the conditions references a .previous that is undefined. This
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = false
|
||||
|
||||
if (code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 ||
|
||||
code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1
|
||||
) {
|
||||
/*
|
||||
We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined.
|
||||
*/
|
||||
} else {
|
||||
error = err.message
|
||||
}
|
||||
}
|
||||
|
||||
conditions.set(node.id, value)
|
||||
|
||||
if (value === true) {
|
||||
tradingSystem.highlights.push(node.id)
|
||||
}
|
||||
if (error !== undefined) {
|
||||
tradingSystem.errors.push([node.id, error])
|
||||
}
|
||||
if (value !== undefined) {
|
||||
tradingSystem.values.push([node.id, value])
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> value = ' + value)
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> error = ' + error)
|
||||
}
|
||||
|
||||
function evalFormula(node) {
|
||||
let value
|
||||
let error
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> code = ' + node.code)
|
||||
value = eval(node.code)
|
||||
} catch (err) {
|
||||
/*
|
||||
One possible error is that the formula references a .previous that is undefined. This
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = 0
|
||||
|
||||
if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 ||
|
||||
node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1
|
||||
) {
|
||||
/*
|
||||
We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined.
|
||||
*/
|
||||
} else {
|
||||
error = err.message
|
||||
}
|
||||
}
|
||||
|
||||
formulas.set(node.id, value)
|
||||
|
||||
if (error !== undefined) {
|
||||
tradingSystem.errors.push([node.id, error])
|
||||
}
|
||||
if (value !== undefined) {
|
||||
tradingSystem.values.push([node.id, value])
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value)
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error)
|
||||
}
|
||||
|
||||
function checkTriggerOn() {
|
||||
if (
|
||||
tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue
|
||||
) {
|
||||
/*
|
||||
To pick a new strategy we will check that any of the situations of the trigger on is true.
|
||||
Once we enter into one strategy, we will ignore market conditions for others. However there is also
|
||||
a strategy trigger off which can be hit before taking a position. If hit, we would
|
||||
be outside a strategy again and looking for the conditions to enter all over again.
|
||||
*/
|
||||
|
||||
for (let j = 0; j < tradingSystem.tradingStrategies.length; j++) {
|
||||
if ( // If a strategy was already picked during the loop, we exit the loop
|
||||
tradingEngine.current.strategy.index.value !== tradingEngine.current.strategy.index.config.initialValue
|
||||
) { continue }
|
||||
|
||||
let strategy = tradingSystem.tradingStrategies[j]
|
||||
let triggerStage = strategy.triggerStage
|
||||
|
||||
if (triggerStage !== undefined) {
|
||||
if (triggerStage.triggerOn !== undefined) {
|
||||
for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) {
|
||||
let situation = triggerStage.triggerOn.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(triggerStage.triggerOn.id)
|
||||
tradingSystem.highlights.push(triggerStage.id)
|
||||
|
||||
tradingStrategyModule.openStrategy('Trigger Stage', j, situation.name, strategy.name)
|
||||
|
||||
tradingEngine.current.distanceToEvent.triggerOn.value = 1
|
||||
|
||||
announcementsModule.makeAnnoucements(triggerStage.triggerOn)
|
||||
announcementsModule.makeAnnoucements(triggerStage)
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
snapshotsModule.strategyEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] checkTriggerOn -> Entering into Strategy: ' + strategy.name)
|
||||
return true // This Means that we have just met the conditions to trigger on.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false // This Means that we have not met the conditions to trigger on.
|
||||
}
|
||||
|
||||
function checkTriggerOff() {
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Trigger Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let triggerStage = strategy.triggerStage
|
||||
|
||||
if (triggerStage !== undefined) {
|
||||
if (triggerStage.triggerOff !== undefined) {
|
||||
for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) {
|
||||
let situation = triggerStage.triggerOff.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(triggerStage.triggerOff.id)
|
||||
tradingSystem.highlights.push(triggerStage.id)
|
||||
|
||||
tradingStrategyModule.closeStrategy('Trigger Off')
|
||||
|
||||
tradingEngine.current.distanceToEvent.triggerOff.value = 1
|
||||
|
||||
announcementsModule.makeAnnoucements(triggerStage.triggerOff)
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] checkTriggerOff -> Closing Strategy: ' + strategy.name)
|
||||
return true // This Means that we have just met the conditions to trigger off.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false // This Means that we have not met the conditions to trigger off.
|
||||
}
|
||||
|
||||
function checkTakePosition() {
|
||||
/* Take Position Condition */
|
||||
if (
|
||||
tradingEngine.current.strategy.stageType.value === 'Trigger Stage' &&
|
||||
tradingEngine.current.strategy.status.value === 'Open'
|
||||
) {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let triggerStage = strategy.triggerStage
|
||||
|
||||
if (triggerStage !== undefined) {
|
||||
if (triggerStage.takePosition !== undefined) {
|
||||
for (let k = 0; k < triggerStage.takePosition.situations.length; k++) {
|
||||
let situation = triggerStage.takePosition.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(triggerStage.takePosition.id)
|
||||
tradingSystem.highlights.push(triggerStage.id)
|
||||
|
||||
tradingStrategyModule.updateStageType('Open Stage')
|
||||
tradingPositionModule.openPosition(situation.name)
|
||||
|
||||
announcementsModule.makeAnnoucements(triggerStage.takePosition)
|
||||
announcementsModule.makeAnnoucements(strategy.openStage)
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.position === true) {
|
||||
snapshotsModule.positionEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] checkTakePosition -> Conditions at the Take Position Event were met.')
|
||||
return true // This Means that we have just met the conditions to take position.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false // This Means that we have not met the conditions to take position.
|
||||
}
|
||||
|
||||
function checkStopPhasesEvents() {
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let stopLoss
|
||||
let stage
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
stage = openStage
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialStopLoss !== undefined) {
|
||||
parentNode = openStage.initialDefinition
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value
|
||||
stopLoss = openStage.initialDefinition.initialStopLoss
|
||||
phase = stopLoss.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
stage = manageStage
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1
|
||||
stopLoss = manageStage.managedStopLoss
|
||||
phase = stopLoss.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
|
||||
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage')
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitPhase.value > 0) {
|
||||
tradingStrategyModule.updateStageType('Manage Stage')
|
||||
announcementsModule.makeAnnoucements(manageStage)
|
||||
}
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < stopLoss.phases.length; q++) {
|
||||
if (stopLoss.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitPhase.value > 0) {
|
||||
tradingStrategyModule.updateStageType('Manage Stage')
|
||||
announcementsModule.makeAnnoucements(manageStage)
|
||||
}
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateStopLoss() {
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialStopLoss !== undefined) {
|
||||
phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.stopLoss.value
|
||||
tradingPositionModule.applyStopLossFormula(formulas, phase.formula.id)
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkTakeProfitPhaseEvents() {
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let takeProfit
|
||||
let stage
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
stage = openStage
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
|
||||
parentNode = openStage.initialDefinition
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value
|
||||
takeProfit = openStage.initialDefinition.initialTakeProfit
|
||||
phase = takeProfit.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
stage = manageStage
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1
|
||||
takeProfit = manageStage.managedTakeProfit
|
||||
phase = takeProfit.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
|
||||
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage')
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossPhase.value > 0) {
|
||||
tradingStrategyModule.updateStageType('Manage Stage')
|
||||
announcementsModule.makeAnnoucements(manageStage)
|
||||
}
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (conditions.get(condition.id) !== undefined) {
|
||||
value = conditions.get(condition.id)
|
||||
}
|
||||
if (value !== true) { passed = false }
|
||||
}
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < takeProfit.phases.length; q++) {
|
||||
if (takeProfit.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossPhase.value > 0) {
|
||||
tradingStrategyModule.updateStageType('Manage Stage')
|
||||
announcementsModule.makeAnnoucements(manageStage)
|
||||
}
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTakeProfit() {
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
|
||||
phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.takeProfit.value
|
||||
tradingPositionModule.applyTakeProfitFormula(formulas, phase.formula.id)
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopLossOrTakeProfitWasHit() {
|
||||
{
|
||||
if (tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
/*
|
||||
Checking what happened since the last execution. We need to know if the Stop Loss
|
||||
or our Take Profit were hit.
|
||||
*/
|
||||
|
||||
/* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */
|
||||
if (
|
||||
(bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value) ||
|
||||
(!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.')
|
||||
|
||||
tradingPositionModule.preventStopLossDistortion()
|
||||
tradingPositionModule.applySlippageToStopLoss()
|
||||
tradingPositionModule.closePosition('Stop Loss')
|
||||
tradingStrategyModule.updateStageType('Close Stage')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
return true // This means that the STOP was hit.
|
||||
}
|
||||
|
||||
/* Take Profit condition: Here we verify if the Take Profit was hit or not. */
|
||||
if (
|
||||
(bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value) ||
|
||||
(!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.')
|
||||
|
||||
tradingPositionModule.preventTakeProfitDistortion()
|
||||
tradingPositionModule.applySlippageToTakeProfit()
|
||||
tradingPositionModule.closePosition('Take Profit')
|
||||
tradingStrategyModule.updateStageType('Close Stage')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
return true // This means that the Take Profit was hit.
|
||||
}
|
||||
}
|
||||
return false // This means that the neither the STOP nor the Take Profit were hit.
|
||||
}
|
||||
}
|
||||
|
||||
function getReadyToTakePosition() {
|
||||
/* Updating Episode Counters */
|
||||
tradingEngine.episode.episodeCounters.positions.value++
|
||||
|
||||
/* Inicializing this counter */
|
||||
tradingEngine.current.distanceToEvent.takePosition.value = 1
|
||||
|
||||
/* Position size and rate */
|
||||
tradingPositionModule.updateSizeAndRate(getPositionSize(), getPositionRate())
|
||||
|
||||
/* We take what was calculated at the formula of position rate and apply the slippage. */
|
||||
tradingPositionModule.applySlippageToRate()
|
||||
}
|
||||
|
||||
function takePosition() {
|
||||
tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value
|
||||
tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value
|
||||
|
||||
let feePaid = 0
|
||||
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
feePaid = tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
|
||||
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value - feePaid
|
||||
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.current.position.size.value
|
||||
} else {
|
||||
feePaid = tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
|
||||
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value - feePaid
|
||||
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.current.position.size.value
|
||||
}
|
||||
}
|
||||
|
||||
function getReadyToClosePosition() {
|
||||
/* Inicializing this counter */
|
||||
tradingEngine.current.distanceToEvent.closePosition.value = 1
|
||||
|
||||
/* Position size and rate */
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
}
|
||||
|
||||
function closePosition() {
|
||||
/* We calculate the new balances after the position is closed. */
|
||||
let feePaid = 0
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
feePaid = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value - feePaid
|
||||
tradingEngine.current.balance.quotedAsset.value = 0
|
||||
} else {
|
||||
feePaid = tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value - feePaid
|
||||
tradingEngine.current.balance.baseAsset.value = 0
|
||||
}
|
||||
|
||||
tradingPositionModule.updateStatistics()
|
||||
|
||||
/* Recalculating Episode Counters */
|
||||
if (tradingEngine.current.position.positionStatistics.profitLoss.value > 0) {
|
||||
tradingEngine.episode.episodeCounters.hits.value++
|
||||
} else {
|
||||
tradingEngine.episode.episodeCounters.fails.value++
|
||||
}
|
||||
|
||||
/* Recalculating Episode Statistics */
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1
|
||||
tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value
|
||||
tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365
|
||||
} else {
|
||||
tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1
|
||||
tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value
|
||||
tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365
|
||||
}
|
||||
}
|
||||
|
||||
function exitStrategyAfterPosition() {
|
||||
if (tradingEngine.current.strategy.stageType.value !== 'Close Stage') { return }
|
||||
|
||||
tradingStrategyModule.closeStrategy('Position Closed')
|
||||
tradingEngine.current.distanceToEvent.triggerOff.value = 1
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
snapshotsModule.strategyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.position === true) {
|
||||
snapshotsModule.positionExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPositionSize() {
|
||||
let balance
|
||||
if (bot.sessionAndMarketBaseAssetsAreEqual) {
|
||||
balance = tradingEngine.current.balance.baseAsset.value
|
||||
} else {
|
||||
balance = tradingEngine.current.balance.quotedAsset.value
|
||||
}
|
||||
const DEFAULT_VALUE = balance
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
|
||||
if (strategy.openStage === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition.positionSize === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition.positionSize.formula === undefined) return DEFAULT_VALUE
|
||||
|
||||
let value = formulas.get(strategy.openStage.initialDefinition.positionSize.formula.id)
|
||||
if (value === undefined) return DEFAULT_VALUE
|
||||
return value
|
||||
}
|
||||
|
||||
function getPositionRate() {
|
||||
const DEFAULT_VALUE = tradingEngine.current.candle.close.value
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
|
||||
if (strategy.openStage === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE
|
||||
if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE
|
||||
|
||||
let value = formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id)
|
||||
if (value === undefined) return DEFAULT_VALUE
|
||||
return value
|
||||
}
|
||||
|
||||
function isItInside(elementWithTimestamp, elementWithBeginEnd) {
|
||||
/*
|
||||
The function is to allow in Conditions and Formulas to easily know when an element with timestamp (like the ones of single files) are inside the time range
|
||||
of an element with a time range, like a candle.
|
||||
*/
|
||||
if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user