Repository: freqtrade/freqtrade

Issue:
stoploss_from_open has some ambiguous with leverage
<!-- 
Have you searched for similar issues before posting it?

If you have discovered a bug in the bot, please [search the issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue). 
If it hasn't been reported, please create a new issue.

Please do not use bug reports to request new features.
-->

## Describe your environment

  * Operating system: ____ Linux
  * Python Version: _____ (`python -V`) 3.10
  * CCXT version: _____ (`pip freeze | grep ccxt`) 1.95
  * Freqtrade Version: ____ (`freqtrade -V` or `docker compose run --rm freqtrade -V` for Freqtrade running in docker)
  
Note: All issues other than enhancement requests will be closed without further comment if the above template is deleted or not filled out.

## Describe the problem:

In the doc explanation,  profit and stoploss is calculated with leverage.

And this function `stoploss_from_open` (in freqtrade/strategy/strategy_helper.py)  may wrong when enter short with leverage.  Because it's profit_rate may large than 1 , and  but open_relative_stop still less than 1, and stoploss be always negative, and function return 0.0. But in fact, it has positive profit.

*Explain the problem you have encountered*

### Steps to reproduce:

  1. _____
  2. _____
  3. _____
  
### Observed Results:

  * What happened?  
       stoploss_from_open may have  ambiguous  when used leverage
  * What did you expect to happen?
       In function dive leverage,  or argument explicitly explanates  without leverage
### Relevant code exceptions or logs
``` Python
              def stoploss_from_open(
                  open_relative_stop: float,
                  current_profit: float,
                  is_short: bool = False
                  ) -> float:
                  """
                  Given the current profit, and a desired stop loss value relative to the open price,
                  return a stop loss value that is relative to the current price, and which can be
                  returned from `custom_stoploss`.
              
                  The requested stop can be positive for a stop above the open price, or negative for
                  a stop below the open price. The return value is always >= 0.
              
                  Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price
              
                  :param open_relative_stop: Desired stop loss percentage relative to open price
                  :param current_profit: The current profit percentage
                  :param is_short: When true, perform the calculation for short instead of long
                  :return: Stop loss value relative to current price
                  """
              
                  # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value
                  if (current_profit == -1 and not is_short) or (is_short and current_profit == 1):
                      return 1
              
                  if is_short is True:
                      stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit))
                  else:
                      stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit))
              
                  # negative stoploss values indicate the requested stop price is higher/lower
                  # (long/short) than the current price
                  return max(stoploss, 0.0)
```
Note: Please copy/paste text of the messages, no screenshots of logs please.

  ```
  // paste your log here
  ```



Discussion:
Please get used to fill out the issue template. Repeat offenders to this will have their issues closed without further comment.
We're asking some very basic questions - by deleting the issue template, you're explicitly choosing not to answer our questions - which makes me doubt why you think we should chose to answer your questions.


Please also don't don't post screenshots of code (it's text for a reason) without any further comment.
This issue could not be less clear, and unless you edit it to provide appropriate information (why / what you think is wrong) - i'll have to close it.
Sorry, this issue have been updated
The intended use of every stoploss_* helper method (`stoploss_from_absolute`, `stoploss_from_open`) is within the `custom_stoploss()` callback.

`custom_stoploss()` is working exactly the same way than stoploss / trailing stoploss would.
in a leveraged scenario, this means that a -10% stoploss at 10x leverage will result in the stoploss to be positioned 1% below the current price (-10% / 10x) - at 5x leverage at -2% (-10 / 5x).

I therefore don't really see why leverage would be relevant - if you return a leverage-adjusted stoploss, then it'd be a wrong stoploss.

Maybe you can make some actual example for a case where you suspect there can be a problem? 
It's just a little ambiguous , and may cause confuse to new trader.

` custom_stoploss()`'s argument  `current_profit` is leveraged,  The `def stoploss_from_open()`  use the argument `open_relative_stop` without leveraged  and `current_profit` need divide by leveraged.  The return of  `stoploss_from_open()` also need multiple leveraged.

If  both argument with leveraged, `stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit))` this code may be negative in short order.
Example:
   1. open short in 100$ with 10x, need stoploss with 10%, so open_relative_stop = -1%
   2. the price down to 50$, current_profit=500% 
   3. stoploss = -1 + (1 - (- 10%)) / (1- 500%) < 0  (both argument with leveraged)


In now situation, may need do as below , so it' s may be clearer if the `stoploss_from_open` 's argument `current_profit`  name to `profit_relative` or  do the transform inner function.

``` Python   
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
                        current_profit: float, **kwargs) -> float:
        if trade.is_short:
            sl = stoploss_from_open(-0.01, current_profit / 10, is_short=trade.is_short)
        else:
            sl = stoploss_from_open(-0.015, current_profit / 10, is_short=trade.is_short)
        return sl  * 10
```

Resolving patch:
diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py
--- a/freqtrade/strategy/strategy_helper.py
+++ b/freqtrade/strategy/strategy_helper.py
@@ -86,37 +86,41 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
 def stoploss_from_open(
     open_relative_stop: float,
     current_profit: float,
-    is_short: bool = False
+    is_short: bool = False,
+    leverage: float = 1.0
 ) -> float:
     """
-
-    Given the current profit, and a desired stop loss value relative to the open price,
+    Given the current profit, and a desired stop loss value relative to the trade entry price,
     return a stop loss value that is relative to the current price, and which can be
     returned from `custom_stoploss`.
 
     The requested stop can be positive for a stop above the open price, or negative for
     a stop below the open price. The return value is always >= 0.
+    `open_relative_stop` will be considered as adjusted for leverage if leverage is provided..
 
     Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price
 
-    :param open_relative_stop: Desired stop loss percentage relative to open price
+    :param open_relative_stop: Desired stop loss percentage, relative to the open price,
+                               adjusted for leverage
     :param current_profit: The current profit percentage
     :param is_short: When true, perform the calculation for short instead of long
+    :param leverage: Leverage to use for the calculation
     :return: Stop loss value relative to current price
     """
 
     # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value
-    if (current_profit == -1 and not is_short) or (is_short and current_profit == 1):
+    _current_profit = current_profit / leverage
+    if (_current_profit == -1 and not is_short) or (is_short and _current_profit == 1):
         return 1
 
     if is_short is True:
-        stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit))
+        stoploss = -1 + ((1 - open_relative_stop / leverage) / (1 - _current_profit))
     else:
-        stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit))
+        stoploss = 1 - ((1 + open_relative_stop / leverage) / (1 + _current_profit))
 
     # negative stoploss values indicate the requested stop price is higher/lower
     # (long/short) than the current price
-    return max(stoploss, 0.0)
+    return max(stoploss * leverage, 0.0)
 
 
 def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float:


Test patch:
diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py
--- a/tests/strategy/test_strategy_helpers.py
+++ b/tests/strategy/test_strategy_helpers.py
@@ -177,26 +177,30 @@ def test_stoploss_from_open(side, profitrange):
     ("long", 0.1, 0.2, 1, 0.08333333),
     ("long", 0.1, 0.5, 1, 0.266666666),
     ("long", 0.1, 5, 1, 0.816666666),  # 500% profit, set stoploss to 10% above open price
+    ("long", 0, 5, 10,  3.3333333),  # 500% profit, set stoploss break even
+    ("long", 0.1, 5, 10,  3.26666666),  # 500% profit, set stoploss to 10% above open price
+    ("long", -0.1, 5, 10,  3.3999999),  # 500% profit, set stoploss to 10% belowopen price
 
     ("short", 0, 0.1, 1, 0.1111111),
     ("short", -0.1, 0.1, 1, 0.2222222),
     ("short", 0.1, 0.2, 1, 0.125),
     ("short", 0.1, 1, 1, 1),
+    ("short", -0.01, 5, 10, 10.01999999),  # 500% profit at 10x
 ])
 def test_stoploss_from_open_leverage(side, rel_stop, curr_profit, leverage, expected):
 
-    stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short')
+    stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short', leverage)
     assert pytest.approx(stoploss) == expected
     open_rate = 100
     if stoploss != 1:
         if side == 'long':
-            current_rate = open_rate * (1 + curr_profit)
-            stop = current_rate * (1 - stoploss)
-            assert pytest.approx(stop) == open_rate * (1 + rel_stop)
+            current_rate = open_rate * (1 + curr_profit / leverage)
+            stop = current_rate * (1 - stoploss / leverage)
+            assert pytest.approx(stop) == open_rate * (1 + rel_stop / leverage)
         else:
-            current_rate = open_rate * (1 - curr_profit)
-            stop = current_rate * (1 + stoploss)
-            assert pytest.approx(stop) == open_rate * (1 - rel_stop)
+            current_rate = open_rate * (1 - curr_profit / leverage)
+            stop = current_rate * (1 + stoploss / leverage)
+            assert pytest.approx(stop) == open_rate * (1 - rel_stop / leverage)
 
 
 def test_stoploss_from_absolute():