<jupyter_start><jupyter_text>Turtle Rules Este notebook foi implementado seguindo o artigo disponível em: 


Código original, usado como base, disponível em:  * Trend Filter: MA50>MA100, long only; MA50<MA100, short only.
* Entry rule:
    1. Close > recent 50 days max price, open long
    1. Close < recent 50 days min price, open short
* Exit rule:
    1. Close <= recent 25 days min price, close long. If the close price drops below the minimum of recent 25 days price, close long positions.
    1. Close >= recent 25 days max price, close short. If the close price rises above the maximum of recent 25 days price, close short positions.
* Trailing Stop: 3 ATR100.
* Position sizing: 
    1. Allocate 0.2% cash to each asset, i.e., risk factor is 0.002.
    1. contract_number =$\dfrac{0.002 \times cash}{ATR_{100} \times price}$ Importando as bibliotecas<jupyter_code>import backtrader as bt
from datetime import datetime
import sys
sys.path.insert(1, 'D:\Sistemas\Backtrader\GetData\GetData')
sys.dont_write_bytecode = True
from Alpha_Vantage_Get_Data import Alpha_Vantage_Get_Data as avgd
from backtrader.analyzers import (SQN, AnnualReturn, TimeReturn, SharpeRatio,
                                  TradeAnalyzer)
from typing import Dict, List, Tuple, Union
import os
import multiprocessing
from tqdm.contrib.concurrent import process_map 
import pandas as pd
from dask import bag as db
from dask.diagnostics import ProgressBar
from matplotlib import pyplot as plt<jupyter_output><empty_output><jupyter_text>Estratégia A estrategia original está feita para apeas 1 ativo... generalizar<jupyter_code>class DonchianChannelsIndicator(bt.Indicator):
    '''Donchian channel.'''

    alias = ('DCH', 'DonchianChannel',)

    lines = ('dcm', 'dch', 'dcl',)  # dc middle, dc high, dc low

    params = (
        ('period', 20), # lookback period
    )

    plotinfo = dict(subplot=False)  # plot along with data
    plotlines = dict(
        dcm=dict(ls='--'),  # dashed line
        dch=dict(_samecolor=True),  # use same color as prev line (dcm)
        dcl=dict(_samecolor=True),  # use same color as prev line (dch)
    )

    def __init__(self):
        super().__init__()
        self.addminperiod(self.params.period + 1)
        self.lines.dch = bt.indicators.Highest(self.data.high(-1), period=self.params.period)
        self.lines.dcl = bt.indicators.Lowest(self.data.low(-1), period=self.params.period)
        self.lines.dcm = (self.lines.dch + self.lines.dcl) / 2.0  # avg of the above

class ClenowTrendFollowingStrategy(bt.Strategy):
    """The trend following strategy from the book "Following the trend" by <NAME>."""
    
    # ToDo: Tornar multi ativo

    alias = ('ClenowTrendFollowing',)

    params = (
        ('trend_filter_fast_period', 50),
        ('trend_filter_slow_period', 100),
        ('fast_donchian_channel_period', 20),
        ('slow_donchian_channel_period', 55),
        ('trailing_stop_atr_period', 20),
        ('trailing_stop_atr_count', 3),
        ('risk_factor', 0.01)
    )

    def __init__(self):
        self.inds = dict()
        for d in self.datas:
            self.inds[d] = {}
            self.inds[d]['trend_filter_fast'] = bt.indicators.EMA(d, period=self.params.trend_filter_fast_period)
            self.inds[d]['trend_filter_slow'] = bt.indicators.EMA(d, period=self.params.trend_filter_slow_period)
            self.inds[d]['dc_fast'] = DonchianChannelsIndicator(d, period=self.params.fast_donchian_channel_period)
            self.inds[d]['dc_slow'] = DonchianChannelsIndicator(d, period=self.params.slow_donchian_channel_period)
            self.inds[d]['atr'] = bt.indicators.ATR(d, period=self.params.trailing_stop_atr_period)
            self.inds[d]['order'] = None  # the pending order
            # For trailing stop loss
            self.inds[d]['sl_order'] = None # trailing stop order
            self.inds[d]['sl_price'] = None
            self.inds[d]['max_price'] = None # track the highest price after opening long positions
            self.inds[d]['min_price'] = None # track the lowest price after opening short positions

    def next(self):
        for i, d in enumerate(self.datas):
            # self.inds[d]['dc_slow'].dcl <= self.inds[d]['dc_fast'].dcl <= self.inds[d]['dc_fast'].dch <= self.inds[d]['dc_slow'].dch
            assert self.inds[d]['dc_slow'].dcl <= self.inds[d]['dc_fast'].dcl
            assert self.inds[d]['dc_fast'].dcl <= self.inds[d]['dc_fast'].dch
            assert self.inds[d]['dc_fast'].dch <= self.inds[d]['dc_slow'].dch

            if not self.getposition(d).size: # Entry rules
                assert self.getposition(d).size == 0
                
                # Position size rule
                max_loss = self.broker.get_cash() * self.p.risk_factor # cash you afford to loss
                position_size = max_loss / self.inds[d]['atr'][0]

                if d.close > self.inds[d]['dc_slow'].dch:
                    if self.inds[d]['trend_filter_fast'] > self.inds[d]['trend_filter_slow']: # trend filter
                        if self.inds[d]['order']:
                            self.broker.cancel(self.inds[d]['order'])
                        else:
                            # Entry rule 1
                            self.inds[d]['order'] = self.buy(d, price=d.close[0], size=position_size, exectype=bt.Order.Limit) 
                            self.inds[d]['max_price'] = d.close[0]
                elif d.close < self.inds[d]['dc_slow'].dcl:
                    if self.inds[d]['trend_filter_fast']  < self.inds[d]['trend_filter_slow']: # trend filter
                        if self.inds[d]['order']:
                            self.broker.cancel(self.inds[d]['order'])
                        else:
                            # Entry rule 2
                            self.inds[d]['order'] = self.sell(d, price=d.close[0], size=position_size, exectype=bt.Order.Limit) 
                            self.inds[d]['min_price'] = d.close[0]
            else:
                assert self.getposition(d).size
                # assert self.inds[d]['order'] is None

                # Exit rules
                if self.getposition(d).size > 0:
                    # Exit rule 1
                    if d.close < self.inds[d]['dc_fast'].dcl:
                        self.inds[d]['order'] = self.order_target_value(d, target=0.0, exectype=bt.Order.Limit, price=d.close[0])
                        return
                else:
                    # Exit rule 2
                    if d.close > self.inds[d]['dc_fast'].dch:
                        self.inds[d]['order'] = self.order_target_value(d, target=0.0, exectype=bt.Order.Limit, price=d.close[0])
                        return

                # Trailing stop loss
                trail_amount = self.inds[d]['atr'][0] * self.p.trailing_stop_atr_count
                if self.position.size > 0:
                    self.inds[d]['max_price'] = d.close[0] if self.inds[d]['max_price'] is None else max(self.inds[d]['max_price'], d.close[0])
                    if self.inds[d]['sl_price'] is None or self.inds[d]['sl_price'] < self.inds[d]['max_price'] - trail_amount:
                        self.inds[d]['sl_price'] = self.inds[d]['max_price'] - trail_amount # increase trailing price
                        if self.inds[d]['sl_order']:
                            self.broker.cancel(self.inds[d]['sl_order'])
                        else:
                            self.inds[d]['sl_order'] = self.order_target_value(d, target=0.0, exectype=bt.Order.Stop, price=self.inds[d]['sl_price'])
                elif self.position.size < 0:
                    self.inds[d]['min_price'] = d.close[0] if self.inds[d]['min_price'] is None else min(self.inds[d]['min_price'], d.close[0])
                    if self.inds[d]['sl_price'] is None or self.inds[d]['sl_price'] > self.inds[d]['min_price'] + trail_amount:
                        self.inds[d]['sl_price'] = self.inds[d]['min_price'] + trail_amount # decrease trailing price
                        if self.inds[d]['sl_order']:
                            self.broker.cancel(self.inds[d]['sl_order'])
                        else:
                            self.inds[d]['sl_order'] = self.order_target_value(d, target=0.0, exectype=bt.Order.Stop, price=self.inds[d]['sl_price'])



    def notify_order(self, order):
        if order.status in [order.Created, order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # print logs here

        # Write down: no pending order
        # if order.exectype == bt.Order.Stop:
        #     self.sl_order = None
        # else:
        #     self.order = None<jupyter_output><empty_output><jupyter_text>Executando a estratégia<jupyter_code>INITIAL_CASH = 100000.0 # dollars

class PandasData_custom(bt.feeds.PandasData):
    params = (('open', 0),
        ('high', 1),
        ('low', 2),
        ('close', 3),
        ('volume', 4),     
    )

#from utils import CryptoSpotCommissionInfo, CryptoContractCommissionInfo, CryptoPandasData

def strategy_demo():
    cerebro = bt.Cerebro(maxcpus=1)
    cerebro.addstrategy(ClenowTrendFollowingStrategy)

    #data_feed = CryptoPandasData(dataname=time_bars, timeframe=bt.TimeFrame.Minutes, compression=BAR_SIZE//(1000*60), name='Binance-Swap-BTC_USDT')
    #cerebro.adddata(data_feed)
    #Get Data
    symbol_list = ['IVVB11', 'BOVA11'
    ,'AZUL4'
    ,'B3SA3'
    ,'BBDC3'
    ,'BTOW3'
    ,'BRML3'
    ,'BRAP4'
    ,'ENGI11'
    ,'BBSE3'
    ,'BPAC11'
    ,'BBAS3'
    ,'BRKM5'
    ,'BRFS3'
    ,'CRFB3'
    ,'CCRO3'
    ,'CMIG4'
    ,'FLRY3'
    ,'EQTL3'
    ,'EZTC3'
    ,'HGTX3'
    ,'CIEL3'
    ,'COGN3'
    ,'CSAN3'
    ,'CPFE3'
    ,'ABEV3'
    ,'CVCB3'
    ,'CYRE3'
    ,'ELET3'
    ,'ELET6'
    ,'EMBR3'
    ,'ENBR3'
    ,'EGIE3'
    ,'ECOR3'
    ,'GGBR4'
    ,'GOLL4'
    ,'BBDC4'
    ,'GOAU4'
    ,'HAPV3'
    ,'HYPE3'
    ,'IGTA3'
    ,'NTCO3'
    ,'GNDI3'
    ,'ITSA4'
    ,'IRBR3'
    ,'ITUB4'
    ,'JBSS3'
    ,'KLBN11'
    ,'RENT3'
    ,'LAME4'
    ,'LREN3'
    ,'MGLU3'
    ,'MRFG3'
    ,'BEEF3'
    ,'MRVE3'
    ,'MULT3'
    ,'BRDT3'
    ,'PETR3'
    ,'PETR4'
    ,'PRIO3'
    ,'QUAL3'
    ,'RADL3'
    ,'RAIL3'
    ,'SBSP3'
    ,'SANB11'
    ,'CSNA3'
    ,'SULA11'
    ,'SUZB3'
    ,'TAEE11'
    ,'VIVT3'
    ,'TIMS3'
    ,'TOTS3'
    ,'UGPA3'
    ,'USIM5'
    ,'VALE3'
    ,'VVAR3'
    ,'WEGE3'
    ,'YDUQ3']

    av = avgd(debug = True, data_dir = 'D:/Sistemas/Backtrader/GetData/GetData/Data/')
    print(av.DATA_DIR)
    data_list = av.get_stock_data(
                    symbol_list)
    del av
    #print (data_list)
    for i in range(len(data_list)):    

        data = PandasData_custom(
                    dataname=data_list[i][0], # This is the Pandas DataFrame
                    name=data_list[i][1], # This is the symbol
                    timeframe=bt.TimeFrame.Days,
                    compression=1,
                    fromdate=datetime(2011,1,1),
                    todate=datetime(2021,1,1)
                    )

        #Add the data to Cerebro
        cerebro.adddata(data)

    cerebro.broker.setcash(INITIAL_CASH)
    # https://www.backtrader.com/blog/posts/2016-12-06-shorting-cash/shorting-cash/
    cerebro.broker.set_shortcash(False)
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    

    cerebro.addsizer(bt.sizers.PercentSizer, percents=100)

    cerebro.addanalyzer(bt.analyzers.SharpeRatio, timeframe=bt.TimeFrame.Days, compression=1, factor=365, annualize=True)
    cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="ta")
    cerebro.addanalyzer(bt.analyzers.DrawDown)
    cerebro.addanalyzer(bt.analyzers.Returns, timeframe=bt.TimeFrame.Days, compression=1, tann=365)
    cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.NoTimeFrame)
    cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.NoTimeFrame, data=data, _name='buyandhold')

    results = cerebro.run()
    assert len(results) == 1

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    print(f'Number of trades: {results[0].analyzers.ta.get_analysis().total.total}')
    #print('PnL: %.2f' % (results[0].analyzers.ta.get_analysis().pnl.net.total,))
    print('PnL: %.2f' % (cerebro.broker.getvalue()-INITIAL_CASH,))
    print('Sharpe Ratio: ', results[0].analyzers.sharperatio.get_analysis()['sharperatio'])
    print('CAGR: %.2f%%' % (results[0].analyzers.returns.get_analysis()['ravg'] * 100,))
    print('Total return: %.2f%%' % (list(results[0].analyzers.timereturn.get_analysis().values())[0] * 100,))
    print('Max Drawdown: %.2f%%' % results[0].analyzers.drawdown.get_analysis().max.drawdown)
    print('Buy and Hold: {0:.2f}%'.format(list(results[0].analyzers.buyandhold.get_analysis().values())[0] * 100))

    #plt.rcParams['figure.figsize'] = (16, 8)
    #cerebro.plot(iplot=False)
    cerebro.plot(style='candlestick')
    return results[0]
result = strategy_demo()<jupyter_output>D:/Sistemas/Backtrader/GetData/GetData/Data/
Get CSV Data of: IVVB11.SAO
Get CSV Data of: BOVA11.SAO
Get CSV Data of: AZUL4.SAO
Get CSV Data of: B3SA3.SAO
Get CSV Data of: BBDC3.SAO
Get CSV Data of: BTOW3.SAO
Get CSV Data of: BRML3.SAO
Get CSV Data of: BRAP4.SAO
Get CSV Data of: ENGI11.SAO
Get CSV Data of: BBSE3.SAO
Get CSV Data of: BPAC11.SAO
Get CSV Data of: BBAS3.SAO
Get CSV Data of: BRKM5.SAO
Get CSV Data of: BRFS3.SAO
Get CSV Data of: CRFB3.SAO
Get CSV Data of: CCRO3.SAO
Get CSV Data of: CMIG4.SAO
Get CSV Data of: FLRY3.SAO
Get CSV Data of: EQTL3.SAO
Get CSV Data of: EZTC3.SAO
Get CSV Data of: HGTX3.SAO
Get CSV Data of: CIEL3.SAO
Get CSV Data of: COGN3.SAO
Get CSV Data of: CSAN3.SAO
Get CSV Data of: CPFE3.SAO
Get CSV Data of: ABEV3.SAO
Get CSV Data of: CVCB3.SAO
Get CSV Data of: CYRE3.SAO
Get CSV Data of: ELET3.SAO
Get CSV Data of: ELET6.SAO
Get CSV Data of: EMBR3.SAO
Get CSV Data of: ENBR3.SAO
Get CSV Data of: EGIE3.SAO
Get CSV Data of: ECOR3.SAO
Get CSV Data of: GGBR4.SAO
Get CS[...]
{'fraction_non_alphanumeric': 0.1354559419075548, 'fraction_numerical': 0.021575198994553832, 'mean_word_length': 2.3124421831637374, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 22, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '303068', 'license': 'MIT', 'n_tokens_mistral': 5386, 'n_tokens_neox': 4935, 'n_words': 1003, 'path': 'samples/TURTLE_RULES/TURTLE_RULES.ipynb', 'repo_name': 'webclinic017/Backtrader-14', 'size': '14330'}