UI Tabular Table Sorting

This should be easy. Just implement the Tabulator table the same way you've done with your other tables. When you go to reference the fields for the cells, just do the path to the field. If you have something like:

msg.payload.position.price.value

Point to it like this:

{title:"Value", field:"position.price.value", width:80}

Tabulator already knows to look into msg.payload to get the value. All you're telling it is where it is in the dot notation. If you're always going to extract the same positions regardless of the data set, that's the easiest way to do it. If the structure of the dataset changes all the time, you're going to want to use a function to filter through it using some kind of for loop or something equivalent to create an object structure that is uniform. But all you're really changing with your new table is the columns you're looking for and where the data for those cells are at. You just have to know how to parse your dataset.

I'm guessing you found where you were reading the payload, which is already an array, into another array making it two arrays deep. That was the first step to resolving the issue.

It was actually a bunch of small syntax misses (not re-naming div, etc)

Ah. I was going to point that out as well, but I figured it would still build the table without changing it. Glad you were able to find it all.

For this variable USDT_SOL I want to remove "USDT_" - looked at truncating formatting in Tabular, couldn't find anything - another option is change node with a jsonota expression?

It depends on if it's a constantly present variable or not. If there will always be a property with that name, you can just reassign it in the object.

msg.payload["SOL"] = msg.payload.USDT_SOL;
delete msg.payload.USDT_SOL;

That assigns the value of USDT_SOL to SOL and deletes USDT_SOL. If it's always changing (i.e. I've seen USDT_ETH in your example data), you can always use a formatter param to trim off the "USDT_" using the substr method. It all depends on what you have coming in and what you're going to do with it.

It's always different, so need to trim the USDT_ ; I attempted the substr in the change node but couldn't get the syntax right.

How do specify this: payload[0].take_profit.steps[0].price.value ... take_profit.steps[0].price.value didn't work.

I've not found good ways to reference arrays held within an object within an array. I'll always copy out the object from the array until I get what I want.

var temp = msg.payload[0];
temp = temp.take_profit.steps[0];
temp = temp.price.value;

Then I have the exact object or property I want and I know I'll always get it. I wish I knew how to do a direct reference, but I haven't found one yet.

Ok, sounds like I need to put a function in before the table ...

It seems the variables have to be defined differently in function? This test is not working

msg = {}

let pair = msg.payload.pair
msg.payload = {"pair":pair}
return [msg]

What does your input look like? That may make a difference.

Also, you can add console.log(something); to your function code after an assignment and see the result show up in your command line where you started Node-Red. Or if you have a debug node attached to the output of the function, you can use node.send(something); to send something impromptu to your debug node. Note that something can either be a variable, a quoted "string" or both like (variable + "string"). That way you can test that you've made it to a specific point in the code or that the value of a variable is a certain value.

It's the input that we have going into tabulator I want to adjust some of the fields, so wanted to pass it through a function.

[{"id":21277788,"version":2,"account":{"id":31430770,"type":"paper_trading","name":"Paper Account","market":"Paper trading account","link":"/accounts/31430770"},"pair":"USDT_ETH","instant":false,"status":{"type":"waiting_targets","basic_type":"waiting_targets","title":"Waiting Targets"},"leverage":{"enabled":false},"position":{"type":"buy","editable":false,"units":{"value":"0.02","editable":false},"price":{"value":"1243.85","value_without_commission":"1242.61","editable":false},"total":{"value":"24.8770522"},"order_type":"market","status":{"type":"finished","basic_type":"finished","title":"Finished"}},"take_profit":{"enabled":true,"steps":[{"id":88864270,"order_type":"market","editable":true,"units":{"value":"0.02"},"price":{"value":"1306.04","type":"last","percent":null},"volume":"100.0","total":"26.1208","trailing":{"enabled":true,"percent":"-2.0"},"status":{"type":"to_process","basic_type":"to_process","title":"Pending"},"data":{"cancelable":true,"panic_sell_available":true},"position":1}]},"stop_loss":{"enabled":true,"breakeven":false,"order_type":"market","editable":true,"price":{"value":null},"conditional":{"price":{"value":"1206.53","type":"last","percent":null},"trailing":{"enabled":true,"percent":null}},"timeout":{"enabled":true,"value":2},"status":{"type":"trailing_activated","basic_type":"trailing_activated","title":"Trailing Activated"}},"reduce_funds":{"steps":[]},"market_close":{},"note":"","note_raw":null,"skip_enter_step":false,"data":{"editable":true,"current_price":{"day_change_percent":"-0.138","bid":"1242.5","ask":"1242.51","last":"1242.5","quote_volume":"847233893.982426"},"target_price_type":"price","base_order_finished":true,"missing_funds_to_close":0,"liquidation_price":null,"average_enter_price":"1243.85","average_close_price":null,"average_enter_price_without_commission":"1242.61","average_close_price_without_commission":null,"panic_sell_available":true,"add_funds_available":true,"reduce_funds_available":true,"force_start_available":false,"force_process_available":true,"cancel_available":true,"finished":false,"base_position_step_finished":true,"entered_amount":"0.02","entered_total":"24.8770522","closed_amount":"0.0","closed_total":"0.0","created_at":"2022-11-15T18:58:45.507Z","updated_at":"2022-11-15T18:58:45.507Z","type":"smart_trade"},"profit":{"volume":"-0.0519022","usd":"-0.0519022","percent":"-0.21","roe":null},"margin":{"amount":null,"total":null},"is_position_not_filled":false},{"id":21277785,"version":2,"account":{"id":31430770,"type":"paper_trading","name":"Paper Account","market":"Paper trading account","link":"/accounts/31430770"},"pair":"USDT_SOL","instant":false,"status":{"type":"waiting_targets","basic_type":"waiting_targets","title":"Waiting Targets"},"leverage":{"enabled":false},"position":{"type":"buy","editable":false,"units":{"value":"1.41","editable":false},"price":{"value":"14.16","value_without_commission":"14.15","editable":false},"total":{"value":"19.9714515"},"order_type":"market","status":{"type":"finished","basic_type":"finished","title":"Finished"}},"take_profit":{"enabled":true,"steps":[{"id":88864262,"order_type":"market","editable":true,"units":{"value":"1.41"},"price":{"value":"14.92","type":"last","percent":null},"volume":"100.0","total":"21.0372","trailing":{"enabled":true,"percent":"-2.0"},"status":{"type":"to_process","basic_type":"to_process","title":"Pending"},"data":{"cancelable":true,"panic_sell_available":true},"position":1}]},"stop_loss":{"enabled":true,"breakeven":false,"order_type":"market","editable":true,"price":{"value":null},"conditional":{"price":{"value":"13.78","type":"last","percent":null},"trailing":{"enabled":true,"percent":null}},"timeout":{"enabled":true,"value":2},"status":{"type":"trailing_activated","basic_type":"trailing_activated","title":"Trailing Activated"}},"reduce_funds":{"steps":[]},"market_close":{},"note":"","note_raw":null,"skip_enter_step":false,"data":{"editable":true,"current_price":{"day_change_percent":"0.929","bid":"14.12","ask":"14.13","last":"14.12","quote_volume":"117341581.0138"},"target_price_type":"price","base_order_finished":true,"missing_funds_to_close":0,"liquidation_price":null,"average_enter_price":"14.16","average_close_price":null,"average_enter_price_without_commission":"14.15","average_close_price_without_commission":null,"panic_sell_available":true,"add_funds_available":true,"reduce_funds_available":true,"force_start_available":false,"force_process_available":true,"cancel_available":true,"finished":false,"base_position_step_finished":true,"entered_amount":"1.41","entered_total":"19.9714515","closed_amount":"0.0","closed_total":"0.0","created_at":"2022-11-15T18:58:30.977Z","updated_at":"2022-11-15T18:58:30.977Z","type":"smart_trade"},"profit":{"volume":"-0.0821607","usd":"-0.0821607","percent":"-0.41","roe":null},"margin":{"amount":null,"total":null},"is_position_not_filled":false}]
msg = {}

var pair = msg.payload[0];
msg.payload = {"pair":pair.pair};
return [msg];

You didn't first break out array position 0. When you tried to reference pair originally, you were trying to reference it as msg.payload.pair, which would work if payload was just an object. But it's an array. There is no .pair at that reference, only an array. You have to reference the array, even if it only houses one object (i.e. [{}] and not [{},{}] or more). Once you break the object out of the array, you can reference anything inside of it, such as .pair.

So you'll notice in the code above, the first thing I do is break the first object out of msg.payload and assign it into pair. Then I use the .pair reference available in pair to assign it in the return statement. That should give you what you're looking for. Though if you're looking to pass along the entire message as well, you're going to want to do something like this:

var pairTemp = msg.payload[0];
msg[pair] = pairTemp.pair;
return [msg];

That keeps a message of a single object array (the [{}] kind) intact while creating a property called "pair" that has the value associated with the value "pair" in the array.

I really hope that made sense and I didn't just confuse you more...

I really appreciate your help and explanations. I wish it was a bit easier to wrap my mind around this - I am (obviously )not a coder :slight_smile:

Wonder if its possible to do any processing in the tabulator function (lets say add two numeric fields) or is it the issue we discussed a while ago, that table values are just references at this point ?

<div id="stats"></div>
<script>



    var statsTable = new Tabulator("#stats", {
     	height:900, 
   // 	textSize : 10,
     	groupBy:"pair",
     	layout:"fitColumns",
     	columns:[ //Define Table Columns
    	 	
    	 	{title:"Pair", field:"pair", hozAlign:"center", width:85},
    	 	{title:"Entered", field:"data.average_enter_price", hozAlign:"right", width:70, formatter:"money", formatterParams:{thousand:",", precision:2}},
    	 	{title:"SL", field:"stop_loss.conditional.price.value", hozAlign:"right", width:70},
        	{title:"Current", field:"data.current_price.last", hozAlign:"right", width:70},
    	 	{title:"TP", field:"take_profit.steps[0].price.value", hozAlign:"right", width:70},

    	 	{title:"Tot Value", field:"test", hozAlign:"right", width:60, formatter:"money", formatterParams:{thousand:",", precision:2}},
    	 	

    	 	{title:"Profit", field:"profit.usd", hozAlign:"right", width:70, formatter:"money", formatterParams:{thousand:",", precision:2}},
    	 	{title:"Percent", field:"profit.percent", hozAlign:"right", width:70,
    	 	    formatter: function(cell, formatterParams){var value = cell.getValue(); if(value == null) {return ""}; if (value !== null) {if(value >= 0){cell.getElement().style.color ='#609f70'} else {cell.getElement().style.color ='#ff4a68'} return value +'%';}}    
    	 	},
        	{title:"Example", field:"profit.percent", hozAlign:"center", formatter:"progress", formatterParams:{min:-2, max:0,color:["red"]}},    	 	
    	 	{title:"Example", field:"profit.percent", hozAlign:"left", formatter:"progress", formatterParams:{min:0, max:5,color:["green"]}},
    	 	{title:"Accnt", field:"account.id", hozAlign:"center", width:70},
    	 		 
    	 		 	
     	],
    });

This code var pairTemp = msg.payload[0]; msg[pair] = pairTemp.pair;return [msg]; is giving : Cannot read property '0' of undefined"" error

That just means msg.payload isn't an array coming in, just an object. You should be able to reference the pair property directly. Take the [0] out and see if it will spit out the message with the pair property moved out of payload.

Yes! You can use a row function similar to how you change row colors and such. You just have to get values of the cells you want to use and set the value of the cell. You'll want to read up on mutators on the Tabulator web page as it's not something easily posted about here. But it's a simple concept.

msg = {}
var pairTemp = msg.payload;
msg[pair] = pairTemp.pair;
return [msg];

getting "ReferenceError: pair is not defined (line 3, col 5)"

FYI ... someone give this solution (for referencing array within array payload[0].take_profit.steps[0].price.value)

msg.payload.forEach(obj => {
    obj.take_profit.steps = obj.take_profit.steps[0]
})
return msg;payload.take_profit.steps

That's good to know. I'll have to hang on to that for future reference.

Looks like we're just getting the path incorrect for that specific property. If you use your debug window on the data output before the function, you can actually get the path directly from that using the Copy Path button.
image
Just put a msg. in front of it and you should be good to go.

Hello Again!

Working on yet another table where am trying to do a variation of what we did with storing data in memory to make it persistent. This time I am pulling in transaction history records and want to create a local file saved dataset for use in a table and several charts for analysis. One reason why it's necessary is because the API limits 100 records per call while I need the full history to be accessible at once.

Here is the function processing the data (similar to what I had before)

 
 msg.payload.forEach(obj => {

    let x = String(obj.pair)
    obj.pair = x.replace("USDT_", "")

    obj.trade_id = obj.id;
    if (obj.status.type === "stop_loss_finished")  {obj.status = "SL"} else if (obj.status.type === "finished")  {obj.status = "TP"} else if (obj.status.type === "panic_sold")  {obj.status = "➦"} else  {obj.status= obj.status.type}
    obj.profit_usd = (Number(obj.profit.usd)).toFixed(2); 
    obj.profit_percent = obj.profit.percent; 
    obj.position = (Number(obj.position.total.value)).toFixed(0); 

    

    if (obj.stop_loss.enabled) {
     obj.to_sl = ((1 - (obj.stop_loss.conditional.price.value / obj.data.average_enter_price)) * 100).toFixed(2);  
   //  obj.sl = ((1 - (obj.data.current_price.last/obj.stop_loss.conditional.price.value)) * 100).toFixed(2);   
 
    }  else {obj.to_sl = 0}
    
    obj.volume = obj.data.current_price.quote_volume;
   
    obj.close_price = obj.data.average_close_price;

    obj.closed = obj.data.closed_at;
 
  if (obj.status === "SL") {

     obj.S_L = (((obj.data.average_enter_price - obj.stop_loss.conditional.price.value)/obj.data.average_enter_price)*100).toFixed(2);  

   } else obj.S_L = ""

 if (obj.status === "TP") {

    obj.T_P = (((obj.take_profit.steps[0].price.value - obj.data.average_enter_price)/obj.data.average_enter_price)*100).toFixed(2);  

  } else obj.T_P = ""


    obj.in=obj.note.split("_");
   
    obj.frames = obj.in[0]
    obj.gain = obj.in[1]
    obj.gain_st = obj.in[2]
    obj.freq = obj.in[3]
    obj.analysis = obj.in[4]
    obj.tp_a = obj.in[5]
    obj.tp_trl_a = obj.in[6]
    obj.sl_a = obj.in[7]
    obj.sl_trl_a = obj.in[8]
    obj.tm_a = obj.in[9]
    
})

return msg;

I was experimenting with our previous code (simple to get data saved locally) but the functionality needs to be adjusted - now I need a method of getting the "latest" record(s) to add to the saved data set. Not sure if getting a single record at a time will work because in the time between calls there may be more than one generated, but getting 5-10 of the latest will certainly cover it.

Again, thanks so much for your help!

var scanArray = [];
context.set("scanArray", scanArray);
var temp = context.get("scanArray");
temp.unshift(msg.payload);
if(temp.length == 20){ //Whatever is one greater than the length of table you want
    temp.pop();
}
context.set("scanArray", temp);
msg.payload = temp;
return msg;