3 of 8 altered title based on commend

Use math to project price rather than a loop

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.

/// <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 524.16 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.