Javascript code portion not running in the right order - javascript

Im creating a trading bot on Javascript (I got no prior experience with this language). The way the trailing stoploss function runs is:
Websocket receives current market price
"active" is a boolean variable, if true, run the code
If price rises a %, cancel old stoploss and add a new one higher.
The problem I am getting is the code doesn't run in the right order.
If you look at the picture, I don't understand why the blue box still executes if active is false. And because the program runs in the wrong order at times, the websocket stops or acts how it isn't supposed to.
This is my trailing stoploss websocket code:
function binanceTrailingSLOrder(symbol, orderId, quantity, oldPrice, percentage, active) {
const clean_trade = client.ws.trades([symbol], trade => { //run websocket
var livePrice = parseFloat(binance_symbols[symbol]["close"]); //set new price to live price
if (active == true) {
binanceCheckOrderStatus(symbol, orderId).then(r => {
switch (r.status) {
case "PENDING":
if (livePrice >= (oldPrice * ((100 + percentage) / 100)) && active == true) {
active = false;
binanceCancelOrder(symbol, orderId).then((r4) => { //Cancel previous SL
var newSL = livePrice * ((100 - percentage) / 100);
binanceStopOrder(symbol, 'SELL', r4.origQty, newSL, newSL).then((r5) => { //Set new SL
orderId = r5.orderId; quantity = r5.origQty; oldPrice = r5.price;
active = true;
}).catch((err) => {
console.log(err);
});
});
}
break;
default:
break;
}
});
}
});
}
Check order status function:
//Get specific order status
function binanceCheckOrderStatus(symbol, orderId) {
if(!orderId){
console.log("order Id not found");
return false;
} else {
var client = loadBinanceKeys2();
return client.getOrder({
symbol: symbol,
orderId: orderId,
recvWindow: 1000000
}).then((order) => {
return order;
}).catch((err) => {
console.log(err);
});
}
}

Javascript is asynchronous in nature. The function binanceCheckOrderStatus() returns a promise. The execution engine will call this function, and then move on to the next line. The code block after .then(r => only executes after the binanceCheckOrderStatus's getOrder() is completed. Now in this time period, the active may have become false in other .then() requests. It may be confusing for new developers. Since you are using lot of .then() in your code, you have to understand that the .then() part is only executed after the function before .then() completes the execution. So the function taking less time will execute it's .then() part before the others. So in short, you CANNOT control the order in this scenario unless you know how much time every function will take, which is probably impossible to confirm. For overcoming this problem, you have to use async/await. Or, you need to change your logic so it is less dependent on that deep level promises.
I am not very sure about what you are trying to achieve here, but here is the idea about how you can solve the ordering problem. It is just a reference code, I have not tested it. Just an idea on how you can hold your threads to make sure your code runs in an order using async/await.
async function binanceTrailingSLOrder(symbol, orderId, quantity, oldPrice, percentage, active) {
const clean_trade = client.ws.trades([symbol], async trade => { //run websocket
var livePrice = parseFloat(binance_symbols[symbol]["close"]); //set new price to live price
if (active == true) {
try {
const order = await binanceCheckOrderStatus(symbol, orderId);
if (!order) {
throw new Error('order not found')
}
switch (order.status) {
case "PENDING":
if (livePrice >= (oldPrice * ((100 + percentage) / 100)) && active == true) {
active = false;
const r4 = await binanceCancelOrder(symbol, orderId);
if (r4) {
var newSL = livePrice * ((100 - percentage) / 100);
var r5 = binanceStopOrder(symbol, 'SELL', r4.origQty, newSL, newSL);
if (r5) {
orderId = r5.orderId; quantity = r5.origQty; oldPrice = r5.price;
active = true;
}
}
}
break;
default:
break;
}
}
catch(error) {
console.log('error found: ', error);
}
}
});
}
async function binanceCheckOrderStatus(symbol, orderId) {
if(!orderId){
console.log("order Id not found");
return false;
} else {
var client = loadBinanceKeys2();
return new Promise((resolve, reject) => {
client.getOrder({
symbol: symbol,
orderId: orderId,
recvWindow: 1000000
}).then((order) => {
resolve(order);
}).catch((err) => {
reject(err);
});
});
}
}

Related

Can't get rid of a global variable (Need to make it non-global)

I'm working on a web app that shows the total duration of a playlist. Here we're dealing with the YouTube API. And I want to know how should I get rid of the global variable newPageToken. Also I still need to use it in the third function on this snippet.
let newPageToken = null;
// Next page for more results (Max 50 per page)
function getNextTokenURL() {
console.log(newPageToken);
return newPageToken
? `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&pageToken=${newPageToken}&key=${API_KEY}`
: `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&key=${API_KEY}`;
}
async function getVideoIdsForPageToken() {
try {
const { data } = await axios.get(getNextTokenURL());
const nextPageToken = data.nextPageToken;
const videoIds = data.items.map((video) => {
return video.contentDetails.videoId;
});
return { videoIds, nextPageToken };
} catch (e) {
if (e.response) {
const { code, message } = e.response.data.error;
throw new Error(`StatusCode ${code}. Reason: ${message}`);
console.log("Errow while fetching videos list.");
} else {
throw new Error(e.message);
}
}
}
// Navigates between the videos per page and adds them (Maximum 50)
async function getPlaylistData() {
try {
const { videoIds, nextPageToken } = await getVideoIdsForPageToken();
let pageToken = nextPageToken;
newPageToken = pageToken;
const returnedVideoIds = [];
returnedVideoIds.push(getDetailsForVideoIds(videoIds));
const videoGroups = await Promise.all(returnedVideoIds);
for (const group of videoGroups) {
for (const video of group) {
finalTotalDuration += returnedToSeconds(video.contentDetails.duration);
}
}
// console.log(videoIds);
if (nextPageToken) {
await getPlaylistData();
}
} catch (e) {
throw new Error(e.message);
console.log("Error while navigating between video pages.");
}
}```
Assumptions
finalTotalDuration is also a global variable declared somewhere (Not a good idea)
You call getPlaylistData for multiple playlist for multiple users
Solution
You need to ensure the getPlaylistData is standalone and returns the finalTotalDuration as a return value (not set a global one)
To make it standalone it has to be iterative in nature. It should be a recursive function which does the following
async function getPlaylistTotalDuration(newPageToken) {
// Step 1: Create the required query URL based on the newPageToken parameter
// Step 2: Start a local duration counter
// Step 3: Get the video details based on the URL created in Step 1
// Step 4: Get the durations in seconds and add it to the local duration counter created in Step 2
// Step 5: Check if the return of Step 3 has a nextPageToken, if so do a recursive call to self with the new token
// Step 6: Return the final value, which will propogate back in a recursive function
}
You can simply call the function like
let finalTotalDuration = getPlaylistTotalDuration(null); // or getPlaylistTotalDuration();
for example the below getPlaylistTotalDuration is a replacement to your getPlaylistData method
async function getVideoIdsForPageToken(url) {
try {
const { data } = await axios.get(url);
const nextPageToken = data.nextPageToken;
const videoIds = data.items.map((video) => {
return video.contentDetails.videoId;
});
return { videoIds, nextPageToken };
} catch (e) {
if (e.response) {
const { code, message } = e.response.data.error;
throw new Error(`StatusCode ${code}. Reason: ${message}`);
console.log("Errow while fetching videos list.");
} else {
throw new Error(e.message);
}
}
}
async function getPlaylistTotalDuration(newPageToken) {
try {
// Step 1: Create the required query URL based on the newPageToken parameter
let url = newPageToken
? `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&pageToken=${newPageToken}&key=${API_KEY}`
: `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&key=${API_KEY}`;
// Step 2: Start a local duration counter
let totalDuration = 0;
// Step 3: Get the video details based on the URL created in Step 1
const { videoIds, nextPageToken } = await getVideoIdsForPageToken(url);
const returnedVideoIds = [];
returnedVideoIds.push(getDetailsForVideoIds(videoIds));
const videoGroups = await Promise.all(returnedVideoIds);
for (const group of videoGroups) {
for (const video of group) {
// Step 4: Get the durations in seconds and add it to the local duration counter created in Step 2
totalDuration += returnedToSeconds(video.contentDetails.duration);
}
}
// Step 5: Check if the return of Step 3 has a nextPageToken, if so do a recursive call to self with the new token
if (nextPageToken) {
totalDuration += await getPlaylistTotalDuration(nextPageToken);
}
// Step 6: Return the final value, which will propogate back in a recursive function
return totalDuration;
} catch (e) {
throw new Error(e.message);
console.log("Error while navigating between video pages.");
}
}
Note: I have not actually ran the above code, but hopefully you get an idea of what needs to be done.

botpress - increment vlaue

I am trying to get a custom action running to simply incrementing a value on passing a specific node on the flow.
My custom actions looks like this:
function action(bp: typeof sdk, event: sdk.IO.IncomingEvent, args: any, { user, temp, session } = event.state) {
/** Your code starts below */
let i = undefined
const p = new Promise((resolve, reject) => {
if (i === undefined) {
resolve((i = 0))
} else if (i >= 0) {
resolve(i + 1)
} else {
reject('i cannot be < 0')
}
})
const runCount = async () => {
try {
const counter = await p
i = counter
return (session.count = counter)
} catch (err) {
console.log(err)
}
}
return runCount()
/** Your code ends here */
}
When I runCount() variable i will be set to 0. But then, after in rerun runCount() it does not increment further.
What do I need to do to save the variable so it increments on every runCount() call.
Greetings
Lorenz
I just managed to solve the problem.
I had to declare i = session.count at the beginning.
Now it gets the value out of the session state and increments the state on every call.
Maybe someone gets some help out of this.
Lorenz

Javascript 'then' not working as expected in Ripple-lib calls

I'm trying to create a simple example of payments over the XRPL using Ripple-lib. The idea is to send several payments to different accounts stored in an array. I've made it kind of work in a different way as it is expected, but when using the 'then' method (as the docs recommend) does not work at all.
I'm a total newbie to Javascript so I don't have a good grasp on the language nor asyncronous coding and promises. When using the 'then' paradigm, the code stops working and no output can be seen in the console. This is the code I'm currently using. In the comments inside the 'SendXRP' function I explain the problem. How can this be re-arranged? Between the two ways, what is the proper one to code it?
'use strict';
const RippleAPI = require('ripple-lib').RippleAPI;
const sender = 'r*********************************';
const secret = 's****************************';
const destinations = ['r*********************************',
'r*********************************',
'r*********************************'];
const amount = 5;
// Instantiate Ripple API
const api = new RippleAPI({
server: "wss://s.altnet.rippletest.net:51233"
});
run();
async function sendXRP(amount, fee, destination, memo) {
// Update amount
amount = (amount - fee).toString();
// Build payment
const payment = {
source: {
address: sender,
maxAmount: {
value: amount,
currency: 'XRP'
}
},
destination: {
address: destination,
amount: {
value: amount,
currency: 'XRP'
}
},
memos: [
{
data: memo
}
]
};
// Build instuctions
const instructions = {
maxLedgerVersionOffset: 5
};
console.log('Sending ' + amount + ' to ' + destination);
// THIS KIND OF WORKS FOR NOW
// Prepare the payment
const preparedTX = await api.preparePayment(sender, payment, instructions);
// Sign the payment
const signedTX = api.sign(preparedTX.txJSON, secret);
// Submit the payment
const result = await api.submit(signedTX['signedTransaction']);
// Return TX hash on successful TX
if ('resultCode' in result && result['resultCode'] == 'tesSUCCESS') {
return signedTX.id;
} else {
return null;
}
// THIS IS MORE SIMILAR TO HOW IT IS DONE IN THE DOCS! NOT WORKING!
// ALSO, HOW DO I RETURN THE RESULT OF API.SIGN TO THE MAIN FUNCTION?
// Prepare the payment
// api.preparePayment(sender, payment, instructions).then(preparedTX => {
// // Sign the payment
// api.sign(preparedTX.txJSON, secret).then(signedTX => {
// // Submit the payment
// api.submit(signedTX['signedTransaction']);
// })
// }).catch(console.error);
}
function run() {
// Connect to Ripple server
api.connect().then(() => {
return api.getFee();
}).then(async fee => {
for (var i in destinations) {
var hash = await sendXRP(amount, Number(fee), destinations[i], 'memotext');
console.log(hash);
}
}).then(() => {
return api.disconnect();
}).catch(console.error);
}
Could it be that some of the transactions failed to send? If it failed, the result variable from sendXRP should have the txresult, but since you returned null if the result code is not tesSUCCESS, it doesn't return the result information.
const result = await api.submit(signedTX['signedTransaction']);
if ('resultCode' in result && result['resultCode'] == 'tesSUCCESS') {
return signedTX.id;
} else {
return null;
}
Before, when I tried submitting transactions consecutively, it would fail and return error code tefPAST_SEQ.
"The sequence number of the transaction is lower than the current sequence number of the account sending the transaction." from https://developers.ripple.com/tef-codes.html
I recommend removing the if('resultCode' in result...) block and check the transaction result. If the transactions failed with tefPAST_SEQ error, my solution to this is set the account sequence in instructions manually or add setTimeOut after each submit.

Monitoring child node values in Firebase and assigning numerical ranks

I have upvote and downvote function's which are performing transactions and correctly manipulating the vote count in my database of sports player names.
Votes are cast as 1's and -1's. Then the math is done to total the player's vote count and put in the database as votes
Each time a vote is cast, I would like a function or piece of code to look through all the names in players and assign a number to each name depicting their rank among everyone in the database (based on their votes from most to least) (I.e. James has 10 upvotes and 0 down votes (votes = 10), he's rank 1. John has 10 upvotes and 1 downvote (votes = 9) and is rank 2. If I upvote John, I should refresh the page and see they are tied at 1. This works to a degree with my current code below, but once I start adding more names via the input and do some upvoting, downvoting, and retracting my votes, the voteCount variable gets all whacky and the ranks go way off course. I'm sure there's an easier and or better way to do this.
orderedPlayersRank is an array that sorts the players by votes, with the best first and worst last. So my #1 ranked person should always be first in the orderedPlayersRank array.
global vars
let prevPlayerVotes = 0
let rankCount = 1
//RANKING CODE
//orderedPlayersRank sorts players from highest votes to lowest
orderedPlayersRank.map((player) => {
this.database.child(player.id).transaction(function(player){
if (player.votes >= prevPlayerVotes) {
prevPlayerVotes = player.votes
player.rank = rankCount
} else if (player.votes < prevPlayerVotes) {
rankCount++
player.rank = rankCount
prevPlayerVotes = player.votes
} else {
console.log("Rank calculation error.")
}
return player;
})
})
Here's my complete upvote function just for reference. I'm putting the above code in where I have the //ranking functionality comment toward the bottom. In that spot, the ranking code is run anytime a valid vote is cast. I would be putting the same code in the downvote function as well.
upvotePlayer(playerId) {
const players = this.state.players;
const orderedPlayersRank = _.orderBy(players, ['votes'], ['desc'])
if (this.state.user) {
let ref = firebase.database().ref('/players/' + playerId + '/voters');
ref.once('value', snap => {
var value = snap.val()
if (value !== null) {
ref.child(this.uid).once('value', snap => {
if (snap.val() === 0 || snap.val() == null) {
ref.child(this.uid).set(1);
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
}
return player;
})
} else if (snap.val() === -1) {
ref.child(this.uid).set(1);
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
player.votes++
}
return player;
})
} else if (snap.val() === 1) {
ref.child(this.uid).set(0);
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes--
}
return player;
})
} else {
console.log("Error in upvoting. snap.val(): " + snap.val())
}
})
} else {
ref.child(this.uid).set(1);
this.alertUpVote()
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
console.log("Player added")
}
return player;
})
}
});
//ranking functionality here
} else {
this.alertNotLoggedIn()
console.log("Must be logged in to vote.")
}
}
As I said, the upvote function is working fine. I'm just looking for some advice on the ranking feature I'm struggling with. I appreciate any help and can supply any other relevant code
So transactions can be run multiple times before completion if the data changes before the transaction is resolved. This can cause any variables outside the scope to become out of sync (i.e. rankCount and prevPlayerVotes). Another reason might be that you are looping over the orderedPlayersRank and returning a Promise for each call to transaction. This will cause prevPlayerRank and rankCount to be read/modified simultaneously instead of sequentially as I assume you are expecting.
One solution could just be to use orderByChild('votes') on the list and use the index paired with checking the previous value to determine rank at display time or set the rank when changes are made to votes (either by Firebase Function, or a watcher).
Ex. (Firebase Function)
export var rank = functions.database.ref('players/{playerId}/votes')
.onUpdate((change, context) => {
// list by 'votes' in ascending order
var orderedListRef = change.after.ref.root.child('players').orderByChild('votes')
var oldVotes = change.before.val()
var newVotes = change.after.val()
var notChanged = 0
var changeRank = 0
// went higher in the list so bump every player passed by 1
if (newVotes > oldVotes) {
// Range: [oldVotes, newVotes]
orderedListRef = orderedListRef.startAt(oldVotes).endAt(newVotes)
changeRank = 1
notChanged = newVotes
} else {// went lower in the list so bump every player passed by -1
// Range: [newVotes, oldVotes]
orderedListRef = orderedListRef.startAt(newVotes).endAt(oldVotes)
changeRank = -1
notChanged = oldVotes
}
return orderedListRef.once('value')
.then((ss) => {
var promises = []
var playersPassed = 0
// IMPORTANT: must use `forEach` to ensure proper order
ss.forEach((playerSS) => {
if (playerSS.key === context.params.playerId) {
return
}
playersPassed += 1
if (playerSS.child('votes').val() === notChanged) {
return
}
// use transaction to ensure proper number of bumps if multiple changes at once
promises.push(playerSS.child('rank').ref.transaction((rank) => {
return rank + changeRank
}))
})
// use transaction to adjust rank by players passed
promises.push(change.before.ref.parent.child('rank')
.transaction((rank) => {
return rank - playersPassed * changeRank
}))
return Promise.all(promises)
})
})
Initialization example
export var initRank = functions.database.ref('players/{playerId}/votes')
.onCreate((snapshot, context) => {
// list by 'votes' in ascending order
return Promise.all([
snapshot.ref.root
.child('players')
.orderByChild('votes')
.startAt(snapshot.val())
.once('value')
.then((ss) => {
return snapshot.ref.parent.child('rank').transaction((rank) => {
if (rank) {
return rank + ss.numChildren
}
return ss.numChildren
})
}),
snapshot.ref.root
.child('players')
.orderByChild('votes')
.endAt(snapshot.val()-1)
.once('value')
.then((ss) => {
var promises = []
ss.forEach((playerSS) => {
promises.push(playerSS.child('rank').ref.transaction((rank) => {
if (rank) {
return rank + 1
}
})
})
return Promise.all(promises)
})
])
})
With this approach, you will need to set the rank of newly created players to the highest rank. Hope this helps!

How can I throttle stack of api requests?

I have an array of ids, and I want to make an api request for each id, but I want to control how many requests are made per second, or better still, have only 5 open connections at any time, and when a connection is complete, fetch the next one.
Currently I have this, which just fires off all the requests at the same time:
_.each([1,2,3,4,5,6,7,8,9,10], function(issueId) {
github.fetchIssue(repo.namespace, repo.id, issueId, filters)
.then(function(response) {
console.log('Writing: ' + issueId);
writeIssueToDisk(fetchIssueCallback(response));
});
});
Personally, I'd use Bluebird's .map() with the concurrency option since I'm already using promises and Bluebird for anything async. But, if you want to see what a hand-coded counter scheme that restricts how many concurrent requests can run at once looks like, here's one:
function limitEach(collection, max, fn, done) {
var cntr = 0, index = 0, errFlag = false;
function runMore() {
while (!errFlag && cntr < max && index < collection.length) {
++cntr;
fn(collection[index++], function(err, data) {
--cntr;
if (errFlag) return;
if (err) {
errFlag = true;
done(err);
} else {
runMore();
}
});
}
if (!errFlag && cntr === 0 && index === collection.length) {
done();
}
}
runMore();
}
With Bluebird:
function fetch(id) {
console.log("Fetching " + id);
return Promise.delay(2000, id)
.then(function(id) {
console.log(" Fetched " + id);
});
}
var ids = [1,2,3,4,5,6,7,8,9,10];
Promise.map(ids, fetch, { concurrency: 3 });
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.min.js"></script>
<!-- results pane console output; see http://meta.stackexchange.com/a/242491 -->
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Divide your data into as many arrays as you want concurrent connections. Schedule with setTimeout, and have the completion callback handle the rest of the sub-array.
Wrap the setTimeout in a function of its own so that the variable values are frozen to their values at the time of delayed_fetch() invocation.
function delayed_fetch(delay, namespace, id, issueIds, filters) {
setTimeout(
function() {
var issueId=issueIds.shift();
github.fetchIssue(namespace, id, issueId, filters).then(function(response) {
console.log('Writing: ' + issueId);
writeIssueToDisk(fetchIssueCallback(response));
delayed_fetch(0, namespace, id, issueIds, filters);
});
}, delay);
}
var i=0;
_.each([ [1,2] , [3,4], [5,6], [7,8], [9,10] ], function(issueIds) {
var delay=++i*200; // millisecond
delayed_fetch(delay, repo.namespace, repo.id, issueIds, filters);
});
i'd recommend using throat just for this: https://github.com/ForbesLindesay/throat
Using Bluebird
function getUserFunc(user) {
//Get a collection of user
}
function getImageFunc(id) {
//get a collection of image profile based on id of the user
}
function search(response) {
return getUsersFunc(response).then(response => {
const promises = response.map(items => return items.id);
const images = id => {
return getImagesFunc(id).then(items => items.image);
};
return Promise.map(promises, images, { concurrency: 5 });
});
}
Previously i used ES6 function Promise.all(), but it doesn't work like what i'm expecting. Then go with third party library bluebird.js and Work like a charm.

Categories