I’m trying to build an application which calculates the result of a forex trade.
I have managed to build something which calculates the next result with the previous result of the calculated trade, the problem I am facing is that the previous trade my not actually be closed before the next trade is calculated, resulting in the wrong balance being used ( a balance which is only available in the future once the trade is closed)
Example
const trades = [{
pair: "EURCAD",
entryDate: "01/01/24",
entryTime: "12:10",
exitDate: "01/01/24"
exitTime: "13:10",}
{
pair: "AUDUSD",
entryDate: "03/01/24",
entryTime: "17:40",
exitDate: "05/01/24"
exitTime: "22:40"},
{
pair: "EURUSD",
entryDate: "04/01/24",
entryTime: "16:15",
exitDate: "12/01/24"
exitTime: "13:10"},
]
Here is the function that I’ve created so far.
I have also created a working Codepen which can be found here
Code Pen
const run = async () => {
const markets = [
{ label: "GBP/CAD", ticker: "GBPCAD", pipDistance: 0.0001 },
{ label: "AUD/USD", ticker: "AUDUSD", pipDistance: 0.0001 },
{ label: "EUR/USD", ticker: "EURUSD", pipDistance: 0.0001 },
{ label: "EUR/CAD", ticker: "EURCAD", pipDistance: 0.0001 },
];
const allTradesArray = [
{
_id: "65b26a76fb5bea9618e111ca",
pair: "EURCAD",
direction: "Short",
created: {
$date: "2024-01-01T04:10:55.000Z",
},
entryDate: "01/01/24",
entryTime: "12:10",
exitDate: "01/01/24",
exitTime: "13:10",
openPrice: "1.47363",
closePrice: "1.48200",
stopLossPips: "86",
fee: "0",
},
{
_id: "65b26b87fb5bea9618e111de",
pair: "EURUSD",
direction: "Long",
created: {
$date: "2024-01-04T04:10:55.000Z",
},
entryDate: "04/01/24",
entryTime: "16:15",
exitDate: "12/01/24",
exitTime: "19:00",
openPrice: "1.47363",
closePrice: "1.48255",
stopLossPips: "29.5",
fee: "0",
},
{
_id: "65b26c49fb5bea9618e111fc",
pair: "AUDUSD",
direction: "Short",
created: {
$date: "2024-01-05T09:40:55.000Z",
},
entryDate: "03/01/24",
entryTime: "17:40",
exitDate: "05/01/24",
exitTime: "22:40",
openPrice: "1.46509",
closePrice: "1.46809",
stopLossPips: "10",
fee: "0",
},
{
_id: "65b27fc6fb5bea9618e1137e",
pair: "GBPCAD",
direction: "Short",
created: {
$date: "2024-01-09T04:10:55.000Z",
},
entryDate: "09/01/24",
entryTime: "12:10",
exitDate: "10/01/24",
exitTime: "15:01",
openPrice: "1.46509",
closePrice: "1.46809",
stopLossPips: "30",
fee: "0",
},
];
const calculateFields = async (allTrades) => {
let trades = allTrades;
let profitLossDollar;
let profitLossPercentage;
let rollingReturnPercentage;
let startingBalance = 50000;
let balance = startingBalance;
// account for slippage eg. if trade loses by 0.5 of a pip lets call it a breakeven
const slippageTrades = true;
let virtualFields = [];
let returnTotals = [];
let note;
let reason;
const checkTypeOfPair = (p) => {
const pairObject = markets.find((m) => m.ticker === p);
const schema = { pipDistance: pairObject?.pipDistance };
return schema;
};
const calculateDollarPipRisk = (balance, stopLossPips) => {
const dollarValueRiskPerTrade = (1 / 100) * balance;
const perPipValue = dollarValueRiskPerTrade / stopLossPips;
return {
riskValue: parseFloat(dollarValueRiskPerTrade.toFixed(2)),
valuePerPip: parseFloat(perPipValue.toFixed(2)),
};
};
const calculateOutcomeOfSlippage = (
closePrice,
openPrice,
direction,
pair
) => {
const typeofPair = checkTypeOfPair(pair);
if (closePrice === "") {
return "In Progress";
}
if (closePrice === openPrice) {
return "Breakeven";
}
if (direction === "Long") {
if (closePrice < openPrice) {
const amountOfPipsLostBy =
(openPrice - closePrice) / typeofPair.pipDistance;
if (parseFloat(amountOfPipsLostBy.toFixed(1)) <= 0.5) {
return "Breakeven";
} else {
return "Loss";
}
} else if (closePrice > openPrice) {
const amountOfPipsWonBy =
(closePrice - openPrice) / typeofPair.pipDistance;
if (parseFloat(amountOfPipsWonBy.toFixed(1)) <= 0.5) {
return "Breakeven";
} else {
return "Win";
}
}
} else if (direction === "Short") {
if (closePrice > openPrice) {
const amountOfPipsLostBy =
(closePrice - openPrice) / typeofPair.pipDistance;
if (parseFloat(amountOfPipsLostBy.toFixed(1)) <= 0.5) {
return "Breakeven";
} else {
return "Loss";
}
} else if (closePrice < openPrice) {
const amountOfPipsWonBy =
(openPrice - closePrice) / typeofPair.pipDistance;
if (parseFloat(amountOfPipsWonBy.toFixed(1)) <= 0.5) {
return "Breakeven";
} else {
return "Win";
}
}
}
};
const calculateOutcome = (
closePrice,
openPrice,
direction,
pair,
slippage
) => {
if (closePrice === "") {
return "In Progress";
}
if (closePrice === openPrice) {
return "Breakeven";
}
if (direction === "Long") {
if (closePrice < openPrice) {
return "Loss";
} else if (closePrice > openPrice) {
return "Win";
}
} else if (direction === "Short") {
if (closePrice > openPrice) {
return "Loss";
} else if (closePrice < openPrice) {
return "Win";
}
}
};
const workOutWinLoseInPips = (
outcome,
direction,
closePrice,
openPrice,
pair
) => {
let pipsWonBy;
let pipsLostBy;
const typeofPair = checkTypeOfPair(pair);
if (outcome === "Breakeven" || outcome === "In Progress") {
pipsWonBy = 0;
pipsLostBy = 0;
}
// handle longs
if (direction === "Long" && outcome === "Win") {
const amountOfPipsWonBy =
(closePrice - openPrice) / typeofPair.pipDistance;
pipsWonBy = parseFloat(amountOfPipsWonBy.toFixed(1));
pipsLostBy = 0;
} else if (direction === "Long" && outcome === "Loss") {
const amountOfPipsLostBy =
(openPrice - closePrice) / typeofPair.pipDistance;
pipsLostBy = parseFloat(amountOfPipsLostBy.toFixed(1));
pipsWonBy = 0;
}
// handle Shorts
if (direction === "Short" && outcome === "Win") {
const amountOfPipsWonBy =
(openPrice - closePrice) / typeofPair.pipDistance;
pipsWonBy = parseFloat(amountOfPipsWonBy.toFixed(1));
pipsLostBy = 0;
} else if (direction === "Short" && outcome === "Loss") {
const amountOfPipsLostBy =
(closePrice - openPrice) / typeofPair.pipDistance;
pipsLostBy = parseFloat(amountOfPipsLostBy.toFixed(1));
pipsWonBy = 0;
}
return { pipsLostBy: pipsLostBy, pipsWonBy: pipsWonBy };
};
for (let index = 0; index < trades.length; index++) {
const trade = trades[index];
const outcome = calculateOutcome(
trade.closePrice,
trade.openPrice,
trade.direction,
trade.pair
);
const outcomeOfSlippage = calculateOutcomeOfSlippage(
trade.closePrice,
trade.openPrice,
trade.direction,
trade.pair
);
const { riskValue, valuePerPip } = calculateDollarPipRisk(
balance,
trade.stopLossPips
);
const { pipsWonBy, pipsLostBy } = workOutWinLoseInPips(
!slippageTrades ? outcomeOfSlippage : outcome,
trade.direction,
trade.closePrice,
trade.openPrice,
trade.pair,
slippageTrades
);
const fee = parseFloat(trade.fee);
const fee_cleaned = Math.abs(fee);
const feeAsDecimal = parseFloat(
((fee_cleaned / balance) * 100).toFixed(2)
);
if (outcome === "Win") {
// calculate percentagte return or loss for this trade
profitLossPercentage = ((valuePerPip * pipsWonBy) / balance) * 100;
profitLossPercentage = parseFloat(profitLossPercentage.toFixed(2));
// calculate dollar return or loss for this trade
profitLossDollar = valuePerPip * pipsWonBy;
profitLossDollar = parseFloat(profitLossDollar.toFixed(2));
// calculate balance after broker fees
if (fee < 0) {
balance = balance + pipsWonBy * valuePerPip - fee_cleaned;
profitLossDollar = profitLossDollar - fee_cleaned;
profitLossPercentage = profitLossPercentage - feeAsDecimal;
} else if (fee > 0) {
balance = balance + pipsWonBy * valuePerPip + fee_cleaned;
profitLossDollar = profitLossDollar + fee_cleaned;
profitLossPercentage = profitLossPercentage + feeAsDecimal;
} else if (fee === 0) {
balance = balance + pipsWonBy * valuePerPip;
}
} else if (outcome === "Loss") {
// calculate percentagte return or loss for this trade
profitLossPercentage = ((valuePerPip * pipsLostBy) / balance) * 100;
//convert to negative number
profitLossPercentage = -Math.abs(
parseFloat(profitLossPercentage.toFixed(2))
);
// calculate dollar return or loss for this trade
profitLossDollar = valuePerPip * pipsLostBy;
//convert to negative number
profitLossDollar = -Math.abs(parseFloat(profitLossDollar.toFixed(2)));
if (fee < 0) {
balance = balance - pipsLostBy * valuePerPip - fee_cleaned;
profitLossDollar = profitLossDollar - fee_cleaned;
profitLossPercentage = profitLossPercentage - feeAsDecimal;
} else if (fee > 0) {
balance = balance - pipsLostBy * valuePerPip + fee_cleaned;
profitLossDollar = profitLossDollar + fee_cleaned;
profitLossPercentage = profitLossPercentage + feeAsDecimal;
} else if (fee === 0) {
balance = balance - pipsLostBy * valuePerPip;
}
} else if (outcome === "Breakeven" || outcome === "In Progress") {
// calculate percentagte return or loss for this trade
profitLossPercentage = 0;
// calculate dollar return or loss for this trade
profitLossDollar = 0;
if (fee < 0) {
balance = balance - fee_cleaned;
profitLossDollar = profitLossDollar - fee_cleaned;
profitLossPercentage = profitLossPercentage - feeAsDecimal;
} else if (fee > 0) {
balance = balance + fee_cleaned;
profitLossDollar = profitLossDollar + fee_cleaned;
} else if (fee === 0) {
// balance stays the same
}
}
returnTotals.push(profitLossPercentage);
let returnTotals_sum = returnTotals.reduce(function (a, b) {
return a + b;
});
// rolling percentage
const priceDifference = balance - startingBalance;
const percent = (priceDifference / startingBalance) * 100;
rollingReturnPercentage = parseFloat(percent.toFixed(2));
const newTrade_virtual = {
_id: trade._id,
riskValue: parseFloat(riskValue.toFixed(2)),
valuePerPip: parseFloat(valuePerPip.toFixed(2)),
profitLossDollar: parseFloat(profitLossDollar.toFixed(2)),
profitLossPercentage: parseFloat(profitLossPercentage.toFixed(2)),
outcome: slippageTrades ? outcomeOfSlippage : outcome,
rollingBalance: balance.toFixed(2),
rollingReturnPercentage: rollingReturnPercentage,
openPrice: trade.openPrice,
closePrice: trade.closePrice,
pair: trade.pair,
direction: trade.direction,
stopLossPips: trade.stopLossPips,
entryDate: trade.entryDate,
entryTime: trade.entryTime,
exitDate: trade.exitDate,
exitTime: trade.exitTime,
fee: trade.fee,
returnTotals: returnTotals_sum.toFixed(2),
};
virtualFields.push(newTrade_virtual);
// console.log("percentage return", returnTotals_sum.toFixed(2));
// console.log("dollar return", priceDifference.toFixed(2));
// console.log("balance", balance.toFixed(2));
} // end of Alltrades for loop
console.log("virtualFields", virtualFields);
};
await calculateFields(allTradesArray);
};
run()