Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some questions about take profit (tp) & stop loss(sl) #82

Closed
AlgoQ opened this issue May 29, 2020 · 13 comments
Closed

Some questions about take profit (tp) & stop loss(sl) #82

AlgoQ opened this issue May 29, 2020 · 13 comments
Labels
invalid This is not a (valid) bug report

Comments

@AlgoQ
Copy link

AlgoQ commented May 29, 2020

When I'm using the buy function with a stop loss / take profit argument I would expect that the order can only be closed on that specific stop loss / take profit price. But when I look at the chart (stop loss=0.66%, take profit=1%), I see that the average negative trade result is around -0.80% with as max -1.14752%. How is this possible?

Also why isn't it possible to optimize the take profit / stop loss rate by using the "optimize" function? Can this be added in a later release? Or can I help contribute?

Backtesting version: 0.1.7

@kernc
Copy link
Owner

kernc commented May 29, 2020

If the next bar's open price is lower than set stop-loss, for example, the trade is exited immediately at that lower (open) price. Similarly for TP. If this, by chance, doesn't explain the discrepancy you're observing, then it's likely a bug.

@kernc
Copy link
Owner

kernc commented May 29, 2020

Also why isn't it possible to optimize the take profit / stop loss rate by using the "optimize" function? Can this be added in a later release? Or can I help contribute?

Backtest.optimize() can optimize any abstract parameter. E.g., a code such as this would optimize for 10 different TP levels:

class Strategy(Strategy):
    tp_pct = 1.01

    def next(self):
        self.buy(tp=self.tp_pct * self.data.Close)

bt = Backtest(...)
bt.optimize(tp_pct=np.linspace(1.01, 1.10, 10))

@AlgoQ
Copy link
Author

AlgoQ commented May 30, 2020

If the next bar's open price is lower than set stop-loss, for example, the trade is exited immediately at that lower (open) price. Similarly for TP. If this, by chance, doesn't explain the discrepancy you're observing, then it's likely a bug.

I will do some testings to see if this is the case.
By the way, thanks for the (fast) reply!

@sdmovie
Copy link

sdmovie commented May 31, 2020

easily optimize any params - awesome,that's why I choose backtesting.py, smart designed, small yet powerful, and relative easier to change/customize (compared to backtrader, I totally lost myself when tring to customize it)

@AlgoQ
Copy link
Author

AlgoQ commented May 31, 2020

Backtest.optimize() can optimize any abstract parameter. E.g., a code such as this would optimize for 10 different TP levels:

@kernc is it possible to create a constraint with these abstract parameters?
I've tried this but I get a ValueError at the line of the constraint.

bt.optimize(
    profitPer=np.linspace(0.75, 1.25, 50),
    stoplossPer=np.linspace(0.50, 1, 50),
    maximize='Equity Final [$]',
    constraint=lambda p: p.profitPer > p.stoplossPer
)

Error:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

@AlgoQ
Copy link
Author

AlgoQ commented May 31, 2020

If the next bar's open price is lower than set stop-loss

Also isn't it possible to gather the next open price to calculate the stop loss and take profit value more precisely?

@kernc
Copy link
Owner

kernc commented May 31, 2020

I've tried this but I get a ValueError at the line of the constraint.

Can you post the full traceback? I see no evident reason it should fail there.

isn't it possible to gather the next open price to calculate the stop loss and take profit value more precisely?

For debugging, maybe via Strategy._broker._data:

def next(self):
data = self._data
i = self._i = len(data) - 1

@AlgoQ
Copy link
Author

AlgoQ commented May 31, 2020

Can you post the full traceback? I see no evident reason it should fail there.

Yeah, no problem:

ValueError                                Traceback (most recent call last)
<timed eval> in <module>

~\anaconda3\lib\site-packages\backtesting\backtesting.py in optimize(self, maximize, constraint, return_heatmap, **kwargs)
    803                                         map(AttrDict,
    804                                             product(*(zip(repeat(k), _tuple(v))
--> 805                                                       for k, v in kwargs.items()))))))
    806         if not param_combos:
    807             raise ValueError('No admissible parameter combinations to test')

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

You mean for debugging the tool itself?

Not exactly, like when you're live trading and place a limit order you can calculate your stop loss and take profit price exactly. Now we are calculating the stop loss and the take profit value based on the current close price, but as you said the trade is based of the next bar's open price. Isn't there an ethical way to calculate the stop loss and the take profit based on the next bar's open price, in order that the loses/wins are closer to when live trading with limit orders?

@kernc
Copy link
Owner

kernc commented May 31, 2020

Isn't there an ethical way to calculate the stop loss and the take profit based on the next bar's open price, in order that the loses/wins are closer to when live trading with limit orders?

Not really. As the strategy next iteration is assumed to trigger between bars (after each full bar), you don't yet know the next bar's open. If you want more fine-tuned trading, you need to begin with more fine-grained data (hourly, minute, 5-second bars etc.). But unexpected gaps will remain.

@sdmovie
Copy link

sdmovie commented Jun 1, 2020

certain backtest platform uses minute bar as price reference even you are backtesting hourly. That's more accurate but far slower than expected. It's a tradeoff upon one's choice.

@AlgoQ
Copy link
Author

AlgoQ commented Jun 2, 2020

@kernc I think I found a bug related to stop loss / take profit. I've been using ATR to calculate my take profit / stop loss price. But when I use a very small stop loss and a very high take profit which has never been reached for all the placed orders, but I still get a positive win rate (on other strategies I even got higher win rates). I had expected that the win rate would be 0%. Am I missing something?

Code:

# IMPORTS
import pandas as pd
import numpy as np
from backtesting import Strategy, Backtest
from backtesting.lib import crossover
from backtesting.test import GOOG
from finta import TA

class SmaCrossAtr(Strategy):
    timePeriod1= 9
    timePeriod2= 14

    atrPeriod = 14
    
    atrSlDivisor    = 2
    atrTpMultiplier = 500

    def init(self):
        # Make dataframe compatible with finta
        df = pd.DataFrame({
                    'open': self.data.Open,
                    'high': self.data.High,
                    'low': self.data.Low,
                    'close': self.data.Close,
                    'volume': self.data.Volume},
                    index=self.data.index)

        self.sma1 = self.I(TA.SMA, df, self.timePeriod1)
        self.sma2 = self.I(TA.SMA, df, self.timePeriod2)
        self.atr = self.I(TA.ATR, df, self.atrPeriod)
    def next(self):
        price = self.data.Close[-1]
        
        if crossover(self.sma1, self.sma2):
            self.buy(sl=price - (self.atr[-1] / self.atrSlDivisor), tp=price + (self.atr[-1] * self.atrTpMultiplier))
        elif crossover(self.sma2, self.sma1):
            self.sell(sl=price + (self.atr[-1] / self.atrSlDivisor), tp=price - (self.atr[-1] * self.atrTpMultiplier))

# Run backtest
bt = Backtest(GOOG, SmaCrossAtr, commission=0)
bt.run()

Results:

Start                     2004-08-19 00:00:00
End                       2013-03-01 00:00:00
Duration                   3116 days 00:00:00
Exposure [%]                          24.4544
Equity Final [$]                      44052.1
Equity Peak [$]                       44088.2
Return [%]                            340.521
Buy & Hold Return [%]                 703.458
Max. Drawdown [%]                    -34.7554
Avg. Drawdown [%]                    -4.39525
Max. Drawdown Duration     1050 days 00:00:00
Avg. Drawdown Duration       82 days 00:00:00
# Trades                                  141
Win Rate [%]                          14.8936
Best Trade [%]                        24.6793
Worst Trade [%]                      -11.1672
Avg. Trade [%]                      -0.260255
Max. Trade Duration          83 days 00:00:00
Avg. Trade Duration           6 days 00:00:00
Expectancy [%]                        2.02165
SQN                                 -0.934781
Sharpe Ratio                       -0.0677723
Sortino Ratio                        -0.20609
Calmar Ratio                      -0.00748819
_strategy                         SmaCrossAtr
dtype: object

@AlgoQ
Copy link
Author

AlgoQ commented Jun 8, 2020

@kernc Any updates on this bug?

@kernc
Copy link
Owner

kernc commented Jul 15, 2020

[@JanssensKobe] I think I found a bug related to stop loss / take profit. [...] I still get a positive win rate.

        if crossover(self.sma1, self.sma2):
            self.buy(sl=price - (self.atr[-1] / self.atrSlDivisor), tp=price + (self.atr[-1] * self.atrTpMultiplier))
        elif crossover(self.sma2, self.sma1):
            self.sell(sl=price + (self.atr[-1] / self.atrSlDivisor), tp=price - (self.atr[-1] * self.atrTpMultiplier))

I suspect the moving averages have been crossing over while some of the currently held trades had a positive PnL. That makes them "win" trades even if they didn't hit the TP level.


Now with PR #47 merged and somewhat overhauled v0.2.0 released, let's call this bug obsolete.

@kernc kernc closed this as completed Jul 15, 2020
@kernc kernc added the invalid This is not a (valid) bug report label Jul 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This is not a (valid) bug report
Projects
None yet
Development

No branches or pull requests

3 participants