Backtesting em Small Caps

Francke Peixoto
7 min readJan 31, 2022

--

🥁 Começando pelo começo….
“Investidores que ainda não utilizam o backtesting vão se surpreender com as inúmeras vantagens que ele resguarda” — Mark Jurik (Computerized Trading)

Small Caps

✏️ “O conceito de small cap não é absoluto e definitivo. Normalmente, recebem essa alcunha as ações de empresas que têm uma capitalização de mercado menor do que a dos nomes mais tradicionais da bolsa de valores; Além de terem uma capitalização menor, as small caps também costumam ter menos liquidez — ou seja, são menos negociadas no mercado…” InfoMoney

Muitas vezes uma descrição (ou definição) como a das Small Caps, podem soar um pouco ‘enganosa’. Em geral, as empresas que são considerada Small Caps, tem um potencial de crescimento muito promissor; Se souber fazer um bom filtro, a probabilidade de encontrar bons ativos é grande. 🌱

📍 Com dúvidas sobre Small Caps? A B3 disponibilizou um curso gratuito em: TradeMap e B3 lançam curso gratuito Investindo em Small Caps).

Backtesting

✏️ “Um backtest analisará o desempenho de uma estratégia em relação a muitos fatores diferentes. Um backtest bem-sucedido mostrará aos traders uma estratégia que provou mostrar resultados positivos historicamente. Embora o mercado nunca se mova da mesma forma, o backtesting baseia-se na suposição de que as ações se movem em padrões semelhantes aos historicamente.” Corporate Finance Institute

⏱️ Vamos começar?

Para obter nossa lista de ações, usaremos os dados do site ADVFN para desenvolver o backtest.

ADVFN Brasil: Portal de investimentos em ações da bolsa de valores do Brasil, com cotações da Bovespa e BM&F. https://br.advfn.com/small-caps

📅 Período: 17/Novembro/2019 à 28/Janeiro/2021
💵 Capital Inícial: R$ 100.000,00

🪣 ADVFN SmallCaps Class
Retorna um dataframe com as maiores altas da categoria Small Caps no site da ADVFN.

class ADVFNSmallCaps:
'''ADVFNSmallCaps v1 - by Francke Peixoto'''
'''Criado pela BM&FBOVESPA, o SMLL (Índice BM&FBOVESPA Small Cap) tem por objetivo medir o comportamento das empresas negociadas na bolsa de valores de modo segmentado,aferindo o retorno de uma carteira composta por empresas de menor capitalização listadas no Mercado Bovespa.'''
def __init__(self):
self.urlBase = 'https://br.advfn.com/small-caps'
self.smallCaps = []
self.__data = None
self.__scraping()
def __scraping(self):
if self.__data == None:
pass
page = requests.get(self.urlBase)
tree = html.fromstring(page.content)
content = tree.xpath('//div[@id="content"]')[0]
tables = content.xpath('.//table[@class="table_element_class"]')
self.smallCaps =[]
for table in tables:
names = table.xpath('.//tr/td/a/@title')
links = table.xpath('.//tr/td/a/@href')
if len(names)>0:
for index, link in enumerate(links):
match = re.search(r'[A-Z]{3,5}\d{1,3}',link)
if match:
symbol = match.group(0)
company = names[index]
company = company[company.find(' '):].strip()
self.smallCaps.append({'symbol': symbol,'company':company})
self.__data = pd.DataFrame(self.smallCaps)
self.__data = self.__data.drop_duplicates()
def get(self):
#Retorna um dataframe com os ultimos smallcaps listados no site da ADVFN.
return self.__data
smallcaps = ADVFNSmallCaps().get()
smallcaps_top = smallcaps.head(3).copy()
smallcaps_top.head()

🧮 Obtendo Cotações e salvando em um diretório…

👀 Top 3 — Small Caps (ADVFN)

🥇 1 ➡️ MRVE3 — Mrv Engenharia Participacoes Sa

🥈 2 ➡️ MYPK3 — Iochpe Maxion Sa

🥉 3 ➡️ ODPV3 — Odontoprev Sa

Quantitative Finance
A finança quantitativa possui em sua estrutura os seguintes componentes:
Backtesting

  • Post-Trade Analytics (PTA)
  • Sinal de pesquisa
  • Lucos e Perdas (PnL)

Pnl: A variância PnL é usada para medir a volatilidade no desempenho/retorno da estratégia adotada.

print('➜ Volatilidade do mercado, variação PnL')
gerenciaRisco = pd.DataFrame()
gerenciaRisco['Symbol'] = symbols
gerenciaRisco['Índice Sharpe'] = .0
gerenciaRisco['Índice Sharpe Anualizado'] = .0
for symbol in symbols:
data = cotacoes[symbol].copy()
dates = data.index
pnls = np.random.randint(-990, 1000, size=len(dates))
pnls = pnls.cumsum()
positions = np.random.randint(-1, 2, size=len(dates))
positions = positions.cumsum()
strategy_performance = pd.DataFrame(index=dates, data={'pnl': pnls, 'position': positions})
plt.legend(loc="upper left")
strategy_performance['pnl'].plot(figsize=(20,5),grid=True,label=f'PnL {symbol}')
daily_pnl_series = strategy_performance['pnl'].shift(-1) - strategy_performance['pnl']
daily_pnl_series.fillna(0, inplace=True)
avg_daily_pnl = daily_pnl_series.mean()
std_daily_pnl = daily_pnl_series.std()
sharpe_ratio = avg_daily_pnl/std_daily_pnl
annualized_sharpe_ratio = sharpe_ratio * np.sqrt(252)
annualized_sharpe_ratio
sharpe_ratio,annualized_sharpe_ratio
gerenciaRisco.loc[gerenciaRisco.Symbol==symbol,'Índice Sharpe'] = sharpe_ratio
gerenciaRisco.loc[gerenciaRisco.Symbol==symbol,'Índice Sharpe Anualizado'] = annualized_sharpe_ratio

Indice Sharpe / trade-level

O Índice Sharpe no nível de negociação compara os retornos da estratégia (PnL médios) em relação aos desvios padrões da volatilidade da estratégia (Desvio padrão dos PnL).

Taxa do Indice Sharpe

É assumido que a taxa é livre de risco se for 0, pois não rolamos as posições, portanto não há cobrança de juros; Essa suposição é realista para negociação intradiária ou diária.

Gerando Estimativas | Estimates
Usei a função criada por @XIANGYU WANG: estimate_multiple_k

  • HA: Heteroscedasticity Assumption
  • VR: Variance Ratio

Backtrader

Backtrader é uma biblioteca escrita em python usada no desenvolvimento de estratégias e Backtesting e negociações no mercados financeiros. Sua curva de aprendizado é bem fácil, ela possui uma documentação bastante organizada e de fácil entendimento. Backtrader — Quickstart

🧠 O Backtrader é constituido por uma arquitetura conhecida como Cerebro, onde é representada os principais componentes do fluxo de trabalho. Uma instância do objeto Cerebro orquestra o processo geral de coleta de dados.

Data feeds, lines e indicators

Os feeds de dados são a matéria-prima de uma estratégia, ele possui informações importantes, como dados de mercado OHLCV, que pode ser personalizado facilmente.

O backtrader, pode ingerir dados de diversas fontes. (csv,pandas,yahoo e etc..)

Strategy, sinais para negocioação

O objeto Strategy possui a lógica de negociação que coloca ordens com base nas informações de feed de dados que a instância Cerebro apresenta em cada barra durante a execução.

*Commissions, schema de comissões.

O objetivo é avaliar os pontos de dados atuais e passados em cada barra de negociação, ela precisa decidir quais pedidos colocar. Nesse ponto, é criado as ordens que o Cerebro passa para uma instância do Broker em execução e fornece uma notificação do resultado em cada barra.

Você pode usar os métodos de estratégia buy() e sell() para colocar ordens de mercado, fechamento e limite, bem como ordens de para e limite de parada.

  • Market order: è preenchido na próxima barra aberta.
  • Close order: è preenchido na próxima barra fechada.
  • Limit order: é executada apenas se um limite de preço for atingido.
  • Stop order: é uma ordem de mercado se o preço atingir um determinado limite.
  • Stop limit order: é uma ordem limitada assim que o stop é acionado (gatilho).

SmallCapStrateg — Contrução da estratégia

class SmallCapStrategy(bt.Strategy):
#criei uma estratégia básica com base na documentação do próprio Backtrader...
params = (('macd_fast' ,12),('macd_slow' ,26), ('macd_signal',9),('ema_period' ,20),('sma_period' ,10),('period' ,5), ('trailpercent',0.6))
def log(self, txt, dt=None):
dt = dt or self.data.datetime.date(0)
print('%s, %s' % (dt, txt))

def __init__(self):
self.close = self.data.close
self.open = self.data.open # Keep a reference to the "close" line in the data[0] dataseries
self.order = None # Property to keep track of pending orders. There are no orders when the strategy is initialized.
self.buyprice = None
self.buycomm = None
self.sma_data = bt.indicators.SimpleMovingAverage(self.data, period=self.params.sma_period)
self.ema_data = bt.indicators.ExponentialMovingAverage(self.data, period=self.params.ema_period)
self.atr = bt.indicators.ATR(self.data, plot = False, period=self.params.period)

self.macd = bt.indicators.MACD(self.data, period_me1=self.params.macd_fast, period_me2=self.params.macd_slow, period_signal=self.params.macd_signal)
self.macd_cross = bt.indicators.CrossOver( self.macd.macd, self.macd.signal)
# Add ExpMA, WtgMA, StocSlow, MACD, ATR, RSI indicators for plotting.
bt.indicators.ExponentialMovingAverage(self.data, period=self.params.ema_period)
bt.indicators.SimpleMovingAverage(self.data, period=self.params.sma_period)
bt.indicators.WeightedMovingAverage(self.data, period=self.params.sma_period,subplot = True)
bt.indicators.StochasticSlow(self.data)
bt.indicators.MACDHisto(self.data)
rsi = bt.indicators.RSI(self.data)
bt.indicators.SmoothedMovingAverage(rsi, period=self.params.period)
bt.indicators.ATR(self.data, period=14)
bt.indicators.AverageDirectionalMovementIndex(self.data, period=self.params.period)

def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('{0},{1}'.format(dt.isoformat(),txt))

def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.alive():
self.order = None
if order.status in [order.Completed]:
if order.isbuy():
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Pedido cancelado...')

self.order = None
def start(self):
self.order = None
def notify_trade(self,trade):
if not trade.isclosed:
return

def next(self):
if self.order:
return
if self.macd_cross[0]>0.0 and self.close[0] < self.close[-1]:
self.order = self.buy()
elif self.order is None:
self.order = self.sell(excetype=bt.Order.StopTrail, trailpercent=self.params.trailpercent)
tcheck = self.data.close * (1.0 -self.params.trailpercent )

Execução da estratégia:

🥇 1 ➡️ MRVE3 — Mrv Engenharia Participacoes Sa

🥈 2 ➡️ MYPK3 — Iochpe Maxion Sa

🥉 3 ➡️ ODPV3 — Odontoprev Sa

Small Caps — Resultados

Fonte completo em: https://www.kaggle.com/franckepeixoto/backtesting-small-caps-b3

--

--

Francke Peixoto
Francke Peixoto

Written by Francke Peixoto

Software Engineer | Data Engineer | Data & Analytics Enthusiastic | Machine Learning | Azure | Fullstack Developer | Systems Analist | .Net — Acta, non verba

No responses yet