首页 > 其他分享 >动量策略

动量策略

时间:2025-01-19 23:22:22浏览次数:1  
标签:策略 self def cerebro bt 动量 stocks momentum


# -*- coding: utf-8 -*-
"""
Momentum strategies are almost the opposite of mean-reversion strategies. 
A typical momentum strategy will buy stocks that have been showing 
an upward trend in hopes that the trend will continue. 
The momentum strategy defined in Clenow’s books 
trades based upon the following rules:

Trade once a week. In his book, Clenow trades every Wednesday, 
but as he notes, which day is completely arbitrary.

Rank stocks in the S&P 500 based on momentum. 
Momentum is calculated by multiplying 
the annualized exponential regression slope 
of the past 90 days by the R^2R 
2 coefficient of the regression calculation.

Position size is calculated using the 20-day Average True Range of each stock, 
multiplied by 10 basis points of the portfolio value.

Only open new positions if the S&P 500 is above its 200-day moving average.

Every week, look to sell stocks that are not in the top 20% momentum ranking, 
or have fallen below their 100 day moving average. 
Buy stocks in the top 20% momentum rankings with remaining cash.

Every other week, rebalance existing positions 
with updated Average True Range values.


quant/survivorship-free at master · teddykoker/quant · GitHub  
https://github.com/teddykoker/quant/tree/master/survivorship-free

"""

from datetime import datetime 
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
#%matplotlib inline
plt.rcParams["figure.figsize"] = (10, 6) # (w, h)
plt.ioff()

import backtrader as bt

from scipy.stats import linregress


#%%

def momentum(closes):
    returns = np.log(closes)
    x = np.arange(len(returns))
    slope, _, rvalue, _, _ = linregress(x, returns)
    return ((1 + slope) ** 252) * (rvalue ** 2)  # annualize slope and multiply by R^2

#file = 'https://github.com/teddykoker/quant/tree/master/survivorship-free/data/tickers.csv'
#tickers = pd.read_csv('survivorship-free/tickers.csv', header=None)[1].tolist()
#tickers = pd.read_csv(file, header=None)[1].tolist()
#stocks = (
#    (pd.concat(
#        [pd.read_csv(f"survivorship-free/{ticker}.csv", index_col='date', parse_dates=True)[
#            'close'
#        ].rename(ticker)
#        for ticker in tickers],
#        axis=1,
#        sort=True)
#    )
#)
#stocks = stocks.loc[:,~stocks.columns.duplicated()]
#



class Momentum(bt.Indicator):
    lines = ('trend',)
    params = (('period', 90),)
    
    def __init__(self):
        self.addminperiod(self.params.period)
    
    def next(self):
        returns = np.log(self.data.get(size=self.p.period))
        x = np.arange(len(returns))
        slope, _, rvalue, _, _ = linregress(x, returns)
        annualized = (1 + slope) ** 252
        self.lines.trend[0] = annualized * (rvalue ** 2)
        
        
class Strategy(bt.Strategy):
    def __init__(self):
        self.i = 0
        self.inds = {}
        self.spy = self.datas[0]
        self.stocks = self.datas[1:]
        
        self.spy_sma200 = bt.indicators.SimpleMovingAverage(self.spy.close,
                                                            period=200)
        for d in self.stocks:
            self.inds[d] = {}
            self.inds[d]["momentum"] = Momentum(d.close, 
                                                period=90)
            self.inds[d]["sma100"] = bt.indicators.SimpleMovingAverage(d.close, 
                                                                       period=100)
            self.inds[d]["atr20"] = bt.indicators.ATR(d, 
                                                      period=20)

    def prenext(self):
        # call next() even when data is not available for all tickers
        self.next()
    
    def next(self):
        if self.i % 5 == 0:
            self.rebalance_portfolio()
        if self.i % 10 == 0:
            self.rebalance_positions()
        self.i += 1
    
    def rebalance_portfolio(self):
        # only look at data that we can have indicators for 
        self.rankings = list(filter(lambda d: len(d) > 100, self.stocks))
        self.rankings.sort(key=lambda d: self.inds[d]["momentum"][0])
        num_stocks = len(self.rankings)
        
        # sell stocks based on criteria
        for i, d in enumerate(self.rankings):
            if self.getposition(self.data).size:
                if i > num_stocks * 0.2 or d < self.inds[d]["sma100"]:
                    self.close(d)
        
        if self.spy < self.spy_sma200:
            return
        
        # buy stocks with remaining cash
        for i, d in enumerate(self.rankings[:int(num_stocks * 0.2)]):
            cash = self.broker.get_cash()
            value = self.broker.get_value()
            if cash <= 0:
                break
            if not self.getposition(self.data).size:
                size = value * 0.001 / self.inds[d]["atr20"]
                self.buy(d, size=size)
                
        
    def rebalance_positions(self):
        num_stocks = len(self.rankings)
        
        if self.spy < self.spy_sma200:
            return

        # rebalance all stocks
        for i, d in enumerate(self.rankings[:int(num_stocks * 0.2)]):
            cash = self.broker.get_cash()
            value = self.broker.get_value()
            if cash <= 0:
                break
            size = value * 0.001 / self.inds[d]["atr20"]
            self.order_target_size(d, size)

cerebro = bt.Cerebro(stdstats=False)
cerebro.broker.set_coc(True)

spy = bt.feeds.YahooFinanceData(dataname='SPY',
                                 fromdate=datetime(2012,2,28),
                                 todate=datetime(2018,2,28),
                                 plot=False)
cerebro.adddata(spy)  # add S&P 500 Index

for ticker in tickers:
    df = pd.read_csv(f"survivorship-free/{ticker}.csv",
                     parse_dates=True,
                     index_col=0)
    if len(df) > 100: # data must be long enough to compute 100 day SMA
        cerebro.adddata(bt.feeds.PandasData(dataname=df, plot=False))

cerebro.addobserver(bt.observers.Value)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.0)
cerebro.addanalyzer(bt.analyzers.Returns)
cerebro.addanalyzer(bt.analyzers.DrawDown)
cerebro.addstrategy(Strategy)
results = cerebro.run()
cerebro.plot(iplot=False)[0][0]
print(f"Sharpe: {results[0].analyzers.sharperatio.get_analysis()['sharperatio']:.3f}")
print(f"Norm. Annual Return: {results[0].analyzers.returns.get_analysis()['rnorm100']:.2f}%")
print(f"Max Drawdown: {results[0].analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")



标签:策略,self,def,cerebro,bt,动量,stocks,momentum
From: https://www.cnblogs.com/duan-qs/p/18680495

相关文章