Files
Superalgos/Projects/Foundations/TS/Bot-Modules/Trading-Bot/Low-Frequency-Trading/Snapshots.js
T
Ferrox85 40566f6059 Update Snapshots.js
Added orders in base and quote asset adn revenue % for base and quoted asset
2021-09-16 16:58:06 +02:00

449 lines
22 KiB
JavaScript

exports.newFoundationsBotModulesSnapshots = function(processIndex) {
/*
This module encapsulates the snapshots functionality.
*/
const MODULE_NAME = 'Snapshots'
let thisObject = {
updateChart: updateChart,
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 snapshotAvailable = false
/*
These 3 are the main data structures available to users
when writing conditions and formulas.
*/
let chart
let exchange
let market
return thisObject
function initialize() {
tradingEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.tradingEngine
tradingSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.tradingSystem
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.tradingParameters
// first trade forces overwrite of the existing file
if (tradingEngine.tradingCurrent.tradingEpisode.tradingEpisodeCounters.positions.value > 1) {
snapshotAvailable = true
createHeaders = false // This is going to be true only once
createCloseHeaders = false
}
}
function finalize() {
writeSnapshotFiles()
tradingEngine = undefined
tradingSystem = undefined
sessionParameters = undefined
positionKeys = undefined
strategyKeys = undefined
chart = undefined
exchange = undefined
market = undefined
}
function updateChart(pChart, pExchange, pMarket) {
/*
We need these 3 data structures to be a local objects
accessible while evaluating conditions and formulas.
*/
chart = pChart
exchange = pExchange
market = pMarket
}
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.tradingCurrent.strategy.begin.value, tradingEngine.tradingCurrent.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.tradingCurrent.position.begin.value, tradingEngine.tradingCurrent.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 schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(node.project + '-' + node.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== 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 < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[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 {
const code = nodeWithCode.code
let instructionsArray = 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, '') //this wrecks something in JS markup?
instruction = instruction.replace(/}/g, '') //this wrecks something in JS markup?
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)
}
try {
let value = eval(instruction)
values.push(value)
} catch (err) {
const message = 'Snapshot Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Trading Bot Error - ' + message,
placeholder: {}
}
let contextInfo = {
instruction: instruction
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, nodeWithCode.name, nodeWithCode.code, undefined, undefined, contextInfo)
tradingSystem.addError([nodeWithCode.id, message, docs])
values.push(0)
}
}
}
}
}
} catch (err) {
const message = 'Snapshot Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Trading Bot Error - ' + message,
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, nodeWithCode.name, nodeWithCode.code, undefined)
tradingSystem.addError([nodeWithCode.id, message, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> nodeWithCode.code = ' + nodeWithCode.code)
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack)
}
}
function getResults(openDatetime, closeDatetime) {
let baseOpen = tradingEngine.tradingCurrent.strategyCloseStage.stageBaseAsset.sizeFilled.value - tradingEngine.tradingCurrent.strategyCloseStage.stageBaseAsset.feesPaid.value
let baseClose = tradingEngine.tradingCurrent.strategyOpenStage.stageBaseAsset.sizeFilled.value - tradingEngine.tradingCurrent.strategyOpenStage.stageBaseAsset.feesPaid.value
let quoteOpen = tradingEngine.tradingCurrent.strategyOpenStage.stageQuotedAsset.sizeFilled.value - tradingEngine.tradingCurrent.strategyOpenStage.stageQuotedAsset.feesPaid.value
let quoteClose = tradingEngine.tradingCurrent.strategyCloseStage.stageQuotedAsset.sizeFilled.value - tradingEngine.tradingCurrent.strategyCloseStage.stageQuotedAsset.feesPaid.value
let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Open Position in Base Asset', 'Close Position in Base Asset', 'Open Position in Quoted Asset', 'Close Position in Quoted Asset', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', '% Profit / Loss in Base Asset', 'ROI in Quoted Asset', '% Profit / Loss in Quoted Asset', 'Exit Type']
closeValues = [
tradingEngine.tradingCurrent.tradingEpisode.tradingEpisodeCounters.positions.value, // Position Number
(new Date(openDatetime)).toISOString(), // Open Datetime
(new Date(closeDatetime)).toISOString(), // Open Datetime
tradingEngine.tradingCurrent.strategy.strategyName.value, // Strategy Name
tradingEngine.tradingCurrent.strategy.situationName.value, // Trigger On Situation
tradingEngine.tradingCurrent.position.situationName.value, // Take Position Situation
baseOpen, // open position in base asset
baseClose, // close position in base asset
quoteOpen, //open quote in base asset
quoteClose, // close quote in base asset
tradingEngine.tradingCurrent.position.positionBaseAsset.hitFail.value, // Result in Base Asset
tradingEngine.tradingCurrent.position.positionQuotedAsset.hitFail.value, // Result in Base Asset
tradingEngine.tradingCurrent.position.positionBaseAsset.ROI.value, // ROI in Base Asset <- not sure this works as intended
((baseClose / baseOpen * 100) - 100) + '%', // % profit / loss in base Asset
tradingEngine.tradingCurrent.position.positionQuotedAsset.ROI.value, // ROI in Quoted Asset <- not sure this works as intended
((quoteClose / quoteOpen * 100) - 100) + '%', // % profit loss in quoted Asset
tradingEngine.tradingCurrent.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 {
let fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex);
let fileName = pFileName + '.csv'
let filePath = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).FILE_PATH_ROOT + '/Output/' + TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_FOLDER_NAME + '/' + SNAPSHOTS_FOLDER_NAME;
filePath += '/' + fileName
//Get contents of current file if available, otherwise start empty file
if (snapshotAvailable) {
getSnapShotFile(filePath)
} else {
writeOutput('')
}
function writeOutput(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 + "";
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated)
function onFileCreated(err) {
if (err.result !== TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> filePath = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> market = ' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + '_' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName);
}
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(
processIndex
).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(
MODULE_NAME,
'[ERROR] writeSnapshotFile -> err = ' + err.stack
)
}
//get file contents
function getSnapShotFile(filePath) {
try {
let fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex);
fileStorage.getTextFile(filePath, onFileReceived)
function onFileReceived(err, text) {
if (err.result === TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result) {
try {
writeOutput(text)
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> fileName = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> Asuming this is a temporary situation. Requesting a Retry.');
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
}
} else {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err;
callBackFunction(err)
}
}
} catch (error) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err;
callBackFunction(err);
}
}
}
}