213 lines
9.5 KiB
JavaScript
213 lines
9.5 KiB
JavaScript
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() {
|
|
let record = scanRecordDefinition(product)
|
|
|
|
if (product.config.saveAsObjects === true) {
|
|
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)
|
|
}
|
|
}
|
|
/*
|
|
We will add a record everytime that proeprty value does not match this
|
|
*/
|
|
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
|
outputDatasetArray.push(record)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
/* We add every record to the existing file. */
|
|
outputDatasetArray.push(record)
|
|
}
|
|
}
|
|
}
|
|
|
|
function scanRecordDefinition(product) {
|
|
if (product.config.codeName === 'Position-Objects') {
|
|
let a = 1
|
|
}
|
|
/*
|
|
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
|
|
}
|
|
} |