I have to project the price for a publicly traded contract when the price should be approximately matching the targeted profit or loss. I have written the following code, runs under 0.02ms.. could be a faster and could be a bit more elegant as the code will get slower progressively depending on the iterations needed. I have documented the code, hope that it makes sense.


    class Contract
    {
        public string Symbol { get; set; }
        public string Currency { get; set; }
        public decimal CommisionPerTrade { get; set; }
        public decimal MinTick { get; set; }
        public string PriceFormat { get; set; }
    
        private (int pos, decimal avgCost) position;
    
        public void AssignPossition(int shares, decimal averagePrice)
        {
            position = (shares, averagePrice);
        }
    
        public (int pos, decimal avgCost) Position => position;
    
        /// <summary>
        /// Get an exchange acceptable price for a contract for a given Profit & Loss value.
        /// The price depends on the rounding of a given contract, possible minimum - ticks are 100, 10, 1.0, 0.1, 0.01, 0.001, 0.0005
        /// </summary>
        /// <param name="pnl">The P&L that is aimed for.</param>
        /// <param name="mayBeHigher">if set to <c>true</c> then loss may be higher as well as profit to take the next acceptable 
        /// contract price else accept less rather them more.</param>
        /// <returns>a acceptable price at a given Min-Tick range that would get a approximate P&L</returns>
        /// <remarks>Please note that loss is a negative number, loss may be higher is actually a lower number as a loss of -100 is 
        /// higher than a loss of -50</remarks> 
        public bool TryGetPriceAtPnl(decimal pnl, bool mayBeHigher, out double price)
        {
    
            if ((Math.Abs(pnl) <= CommisionPerTrade && mayBeHigher == false)
    
                || (Position.avgCost == 0 || Position.pos == 0))
            {
                price = 0;
                return false;
            }
    
    
            //the current price paid for the contract
            decimal priceAtPnl = Position.avgCost;
    
            //start with deducting the commission that will need to be paid
            decimal sum = -CommisionPerTrade;
    
            //get the Price for minimal price change (min-tick) in the current position on a contract
            //positions when short contain negative values so best to take abs values
            decimal pnlPerTick = (Math.Abs(Position.pos) * (Position.avgCost + MinTick))
                                - (Math.Abs(Position.pos) * Position.avgCost);
    
            if (position.pos > 0)// long trade
            {
    
                if (pnl > 0) //profit target
                {
                    //positive P&L 
                    while ((!mayBeHigher && sum + pnlPerTick <= pnl) || (mayBeHigher && sum <= pnl))
                    {
                        sum += pnlPerTick;
                        priceAtPnl += MinTick;//next allowed price by adding the minimum price change
                    }
                }
                else //loss target
                {
                    //when a negative P&L "may be higher" then this actually is, and must be, a lower value
                    while ((!mayBeHigher && sum - pnlPerTick >= pnl) || (mayBeHigher && sum >= pnl))
                    {
                        sum -= pnlPerTick;
                        priceAtPnl -= MinTick;//previous allowed price by subtracting the minimum price change
                    }
                }
            }
            else // short trade
            {
                if (pnl > 0) //profit target
                {
                    //positive P&L 
                    while ((!mayBeHigher && sum + pnlPerTick >= pnl) || (mayBeHigher && sum >= pnl))
                    {
                        sum += pnlPerTick;
                        priceAtPnl -= MinTick;//next allowed price by adding the minimum price change
                    }
                }
                else //loss target
                {
                    //when a negative P&L "may be higher" then this actually is, and must be, a lower value
                    while ((!mayBeHigher && sum - pnlPerTick >= pnl) || (mayBeHigher && sum >= pnl))
                    {
                        sum -= pnlPerTick;
                        priceAtPnl += MinTick;//previous allowed price by subtracting the minimum price change
                    }
                }
    
    
            }
    
            //can't have floating point imprecision but need to return a double so cast the decimal to a double
            price = Convert.ToDouble(priceAtPnl);
            return true;
        }
    
    }

   

I have several unit tests and all look to be projecting the correct price for both long and short trades as well as profit targets and stop-loss prices.

Here are 2 unit tests

    public void NativeTrainerTestPriceAtPnlGoodCaseShortLoss()
    {
    
        var contract = new Contract() { Symbol = "ABC", Currency = "ABC", CommisionPerTrade = 238.50M + 238.48M, MinTick = 0.001M, PriceFormat = "N2" };
        contract.AssignPossition(shares: -100_000, averagePrice: 118.891M);
    
        /* P&L table for short trades
            * ______________________________________
            * PRICE       P&L       Min-Ticks
            * 118.901    -1476.98    10           P
            * 118.900    -1376.98    9            R
            * 118.899    -1276.98    8            I
            * 118.898    -1176.98    7            C
            * 118.897    -1076.98    6            E
            * 118.896     -976.98    5            
            * 118.895     -876.98    4            U
            * 118.894     -776.98    3            P
            * 118.893     -676.98    2
            * 118.892     -576.98    1
            * 118.891     -476.98    0  ---      BASE PRICE
            * 118.890     -376.98    1
            * 118.889     -276.98    2           P
            * 118.888     -276.98    3           R
            * 118.887     -176.98    4           I
            * 118.886      -76.98    5           C
            * 118.885       23.02    6           E
            * 118.884      123.02    7
            * 118.883      223.02    8           D
            * 118.882      323.02    9           O
            * 118.881      423.02    10          W
            * 118.880      523.02    11          N
            */
    
    
        //warm up .net to make sure we are jitted
        contract.TryGetPriceAtPnl(700, false, out double _);
    
        var sw = System.Diagnostics.Stopwatch.StartNew();
        Assert.IsTrue(contract.TryGetPriceAtPnl(pnl: -700.00M, mayBeHigher: false, out double price), "Calculate price for P&L 700 must be possible, however returned false");
        sw.Stop();
    
    
    
        Assert.IsTrue(price > 0, "Price must always be more then 0");
        Assert.AreEqual(118.893D, price);
        if (sw.ElapsedMilliseconds > 0.02)
            Assert.Inconclusive($"Non Functional requirement, price calculations must be under 2 ms, this server did it in {sw.Elapsed}");
    
    
    }
    [TestMethod]
    [TestCategory("Trade Stop-Loss")]        
    public void NativeTrainerTestPriceAtPnlGoodCaseLongLoss()
    {
    
        /* P&L table for long trades
            * ______________________________________
            * PRICE       P&L       Min-Ticks
            * 118.936      523.12   10          
            * 118.935      423.12    9           
            * 118.934      323.12    8           
            * 118.933      223.12    7           
            * 118.932      123.12    6           
            * 118.931       23.12    5           
            * 118.930     - 76.88    4           
            * 118.929     -176.88    3           
            * 118.928     -276.88    2
            * 118.927     -376.88    1
            * 118.926     -476.88    0  ---      
            * 118.925     -576.88    1
            * 118.924     -676.88    2           
            * 118.923     -767.88    3           
            * 118.922     -867.88    4           
            * 118.921     -967.88    5           
    
            */
    
    
        var contract = new Contract() { Symbol = "ABC", Currency = "ABC", CommisionPerTrade = 238.43M + 238.45M, MinTick = 0.001M, PriceFormat = "N2" };
        contract.AssignPossition(shares: 100_000, averagePrice: 118.926M);
    
        //warm up .net to make sure we are jitted
        contract.TryGetPriceAtPnl(700, false, out double _);
    
        var sw = System.Diagnostics.Stopwatch.StartNew();            
        contract.TryGetPriceAtPnl(pnl: -700M, mayBeHigher: false, out double price);
        sw.Stop();
        Assert.AreEqual(118.924D, price);
    
        contract.TryGetPriceAtPnl(pnl: -700M, mayBeHigher: true, out price);
        Assert.AreEqual(118.923D, price);
    
        if (sw.ElapsedMilliseconds > 0.02)
            Assert.Inconclusive($"Non Functional requirement, price calculations must be under 2 ms, this server did it in {sw.Elapsed}");
    
    
    }

Ideally I would like to project the price without the loops. Important is that one must project valid prices as else the exchange will reject the order.