Repository: paperswithbacktest/awesome-systematic-trading
Branch: main
Commit: 1ce1eb2b7752
Files: 66
Total size: 519.8 KB
Directory structure:
gitextract_ocdq6ygj/
├── .gitignore
├── .vscode/
│ ├── launch.json
│ └── settings.json
├── README.md
├── README_zh.md
└── static/
└── strategies/
├── 12-month-cycle-in-cross-section-of-stocks-returns.py
├── 52-weeks-high-effect-in-stocks.py
├── accrual-anomaly.py
├── asset-class-momentum-rotational-system.py
├── asset-class-trend-following.py
├── asset-growth-effect.py
├── betting-against-beta-factor-in-country-equity-indexes.py
├── betting-against-beta-factor-in-stocks.py
├── combining-fundamental-fscore-and-equity-short-term-reversals.py
├── combining-smart-factors-momentum-and-market-portfolio.py
├── consistent-momentum-strategy.py
├── crude-oil-predicts-equity-returns.py
├── currency-momentum-factor.py
├── currency-value-factor-ppp-strategy.py
├── dispersion-trading.py
├── dollar-carry-trade.py
├── earnings-announcement-premium.py
├── earnings-announcements-combined-with-stock-repurchases.py
├── earnings-quality-factor.py
├── esg-factor-momentum-strategy.py
├── fed-model.py
├── fx-carry-trade.py
├── how-to-use-lexical-density-of-company-filings.py
├── intraday-seasonality-in-bitcoin.py
├── january-barometer.py
├── low-volatility-factor-effect-in-stocks.py
├── market-sentiment-and-an-overnight-anomaly.py
├── momentum-and-reversal-combined-with-volatility-effect-in-stocks.py
├── momentum-effect-in-commodities.py
├── momentum-factor-and-style-rotation-effect.py
├── momentum-factor-combined-with-asset-growth-effect.py
├── momentum-factor-effect-in-stocks.py
├── momentum-in-mutual-fund-returns.py
├── option-expiration-week-effect.py
├── paired-switching.py
├── pairs-trading-with-country-etfs.py
├── pairs-trading-with-stocks
├── payday-anomaly.py
├── rd-expenditures-and-stock-returns.py
├── rebalancing-premium-in-cryptocurrencies.py
├── residual-momentum-factor.py
├── return-asymmetry-effect-in-commodity-futures.py
├── reversal-during-earnings-announcements.py
├── roa-effect-within-stocks.py
├── sector-momentum-rotational-system.py
├── short-interest-effect-long-short-version.py
├── short-term-reversal-in-stocks.py
├── short-term-reversal-with-futures.py
├── skewness-effect-in-commodities.py
├── small-capitalization-stocks-premium-anomaly.py
├── soccer-clubs-stocks-arbitrage.py
├── synthetic-lending-rates-predict-subsequent-market-return.py
├── term-structure-effect-in-commodities.py
├── time-series-momentum-effect.py
├── trading-wti-brent-spread.py
├── trend-following-effect-in-stocks.py
├── turn-of-the-month-in-equity-indexes.py
├── value-and-momentum-factors-across-asset-classes.py
├── value-book-to-market-factor.py
├── value-factor-effect-within-countries.py
└── volatility-risk-premium-effect.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
"name": "Current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"args": [],
"console": "integratedTerminal",
"python": "${env:HOME}/miniconda3/bin/python",
"justMyCode": false
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"editor.formatOnSave": true,
"python.defaultInterpreterPath": "${env:HOME}/miniconda3/bin/python",
}
================================================
FILE: README.md
================================================
Awesome Systematic Trading
[希望阅读中文版?点我](./README_zh.md)
We are collecting a list of resources papers, softwares, books, articles for finding, developing, and running systematic trading (quantitative trading) strategies.
### What will you find here?
- [97 libraries and packages](#libraries-and-packages) for research and live trading
- [40+ strategies](#strategies) described by institutionals and academics
- [55 books](#books) for beginners and professionals
- [23 videos](#videos) and interviews
- And also some [blogs](#blogs) and [courses](#courses)
📈 Interested in trading strategies implemented in Python?
Visit our comprehensive collection at paperswithbacktest.com for exclusive content!
Click here to see the full table of content
- [Libraries and packages](#libraries-and-packages)
- [Backtesting and Live Trading](#backtesting-and-live-trading)
- [General - Event Driven Frameworks](#general---event-driven-frameworks)
- [General - Vector Based Frameworks](#general---vector-based-frameworks)
- [Cryptocurrencies](#cryptocurrencies)
- [Trading bots](#trading-bots)
- [Analytics](#analytics)
- [Indicators](#indicators)
- [Metrics computation](#metrics-computation)
- [Optimization](#optimization)
- [Pricing](#pricing)
- [Risk](#risk)
- [Broker APIs](#broker-apis)
- [Data Sources](#data-sources)
- [General](#general)
- [Cryptocurrencies](#cryptocurrencies-1)
- [Data Science](#data-science)
- [Databases](#databases)
- [Graph Computation](#graph-computation)
- [Machine Learning](#machine-learning)
- [TimeSeries Analysis](#timeseries-analysis)
- [Visualization](#visualization)
- [Strategies](#strategies)
- [Bonds, commodities, currencies, equities](#bonds-commodities-currencies-equities)
- [Bonds, commodities, equities, REITs](#bonds-commodities-equities-reits)
- [Bonds, equities](#bonds-equities)
- [Bonds, equities, REITs](#bonds-equities-reits)
- [Commodities](#commodities)
- [Cryptos](#cryptos)
- [Currencies](#currencies)
- [Equities](#equities)
- [Books](#books)
- [Beginner](#beginner)
- [Biography](#biography)
- [Coding](#coding)
- [Crypto](#crypto)
- [General](#general-1)
- [High Frequency Trading](#high-frequency-trading)
- [Machine Learning](#machine-learning-1)
- [Videos](#videos)
- [Blogs](#blogs)
- [Courses](#courses)
> ### How can I help?
> You can help by submitting an issue with suggestions and by sharing on Twitter:
>
> [](https://twitter.com/intent/tweet?text=A%20free%20and%20comprehensive%20list%20of%20papers%2C%20libraries%2C%20books%2C%20blogs%2C%20tutorials%20for%20quantitative%20traders.&url=https://github.com/paperswithbacktest/awesome-systematic-trading)
# Libraries and packages
*List of **97 libraries and packages** implementing trading bots, backtesters, indicators, pricers, etc. Each library is categorized by its programming language and ordered by descending populatrity (number of stars).*
## Backtesting and Live Trading
### General - Event Driven Frameworks
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [vnpy](https://github.com/vnpy/vnpy) | Python-based open source quantitative trading system development framework, officially released in January 2015, has grown step by step into a full-featured quantitative trading platform |  |  |
| [zipline](https://github.com/quantopian/zipline) | Zipline is a Pythonic algorithmic trading library. It is an event-driven system for backtesting. |  |  |
| [backtrader](https://github.com/mementum/backtrader) | Event driven Python Backtesting library for trading strategies |  |  |
| [QUANTAXIS](https://github.com/QUANTAXIS/QUANTAXIS) | QUANTAXIS 支持任务调度 分布式部署的 股票/期货/期权/港股/虚拟货币 数据/回测/模拟/交易/可视化/多账户 纯本地量化解决方案 |  |  |
| [QuantConnect](https://github.com/QuantConnect/Lean) | Lean Algorithmic Trading Engine by QuantConnect (Python, C#) |  |  |
| [Rqalpha](https://github.com/ricequant/rqalpha) | A extendable, replaceable Python algorithmic backtest && trading framework supporting multiple securities |  |  |
| [finmarketpy](https://github.com/cuemacro/finmarketpy) | Python library for backtesting trading strategies & analyzing financial markets (formerly pythalesians) |  |  |
| [backtesting.py](https://github.com/kernc/backtesting.py) | Backtesting.py is a Python framework for inferring viability of trading strategies on historical (past) data. Improved upon the vision of Backtrader, and by all means surpassingly comparable to other accessible alternatives, Backtesting.py is lightweight, fast, user-friendly, intuitive, interactive, intelligent and, hopefully, future-proof. |  |  |
| [zvt](https://github.com/zvtvz/zvt) | Modular quant framework |  |  |
| [WonderTrader](https://github.com/wondertrader/wondertrader) | WonderTrader——量化研发交易一站式框架 |  |  |
| [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) | A high-performance algorithmic trading platform and event-driven backtester |  |  |
| [PandoraTrader](https://github.com/pegasusTrader/PandoraTrader) | High-frequency quantitative trading platform based on c++ development, supporting multiple trading APIs and cross-platform |  |  |
| [HFTBacktest](https://github.com/nkaz001/hftbacktest) | Highly precise backtest on HFT data in Python+Numba |  |  |
| [aat](https://github.com/AsyncAlgoTrading/aat) | An asynchronous, event-driven framework for writing algorithmic trading strategies in python with optional acceleration in C++. It is designed to be modular and extensible, with support for a wide variety of instruments and strategies, live trading across (and between) multiple exchanges. |  |  |
| [sdoosa-algo-trade-python](https://github.com/sreenivasdoosa/sdoosa-algo-trade-python) | This project is mainly for newbies into algo trading who are interested in learning to code their own trading algo using python interpreter. |  |  |
| [lumibot](https://github.com/Lumiwealth/lumibot) | A very simple yet useful backtesting and sample based live trading framework (a bit slow to run...) |  |  |
| [quanttrader](https://github.com/letianzj/quanttrader) | Backtest and live trading in Python. Event based. Similar to backtesting.py. |  |  |
| [gobacktest](https://github.com/gobacktest/gobacktest) | A Go implementation of event-driven backtesting framework |  |  |
| [FlashFunk](https://github.com/HFQR/FlashFunk) | High Performance Runtime in Rust |  |  |
### General - Vector Based Frameworks
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [vectorbt](https://github.com/polakowo/vectorbt) | vectorbt takes a novel approach to backtesting: it operates entirely on pandas and NumPy objects, and is accelerated by Numba to analyze any data at speed and scale. This allows for testing of many thousands of strategies in seconds. |  |  |
| [pysystemtrade](https://github.com/robcarver17/pysystemtrade) | Systematic Trading in python from book Systematic Trading by Rob Carver |  |  |
| [bt](https://github.com/pmorissette/bt) | Flexible backtesting for Python based on Algo and Strategy Tree |  |  |
### Cryptocurrencies
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Freqtrade](https://github.com/freqtrade/freqtrade) | Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning. |  |  |
| [Jesse](https://github.com/jesse-ai/jesse) | Jesse is an advanced crypto trading framework which aims to simplify researching and defining trading strategies. |  |  |
| [OctoBot](https://github.com/Drakkar-Software/OctoBot) | Cryptocurrency trading bot for TA, arbitrage and social trading with an advanced web interface |  |  |
| [Kelp](https://github.com/stellar/kelp) | Kelp is a free and open-source trading bot for the Stellar DEX and 100+ centralized exchanges |  |  |
| [openlimits](https://github.com/nash-io/openlimits) | A Rust high performance cryptocurrency trading API with support for multiple exchanges and language wrappers. |  |  |
| [bTrader](https://github.com/gabriel-milan/btrader) | Triangle arbitrage trading bot for Binance |  |  |
| [crypto-crawler-rs](https://github.com/crypto-crawler/crypto-crawler-rs) | Crawl orderbook and trade messages from crypto exchanges |  |  |
| [Hummingbot](https://github.com/CoinAlpha/hummingbot) | A client for crypto market making |  |  |
| [cryptotrader-core](https://github.com/monomadic/cryptotrader-core) | Simple to use Crypto Exchange REST API client in rust. |  |  |
## Trading bots
*Trading bots and alpha models. Some of them are old and not maintained.*
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Blackbird](https://github.com/butor/blackbird) | Blackbird Bitcoin Arbitrage: a long/short market-neutral strategy |  |  |
| [bitcoin-arbitrage](https://github.com/maxme/bitcoin-arbitrage) | Bitcoin arbitrage - opportunity detector |  |  |
| [ThetaGang](https://github.com/brndnmtthws/thetagang) | ThetaGang is an IBKR bot for collecting money |  |  |
| [czsc](https://github.com/waditu/czsc) | 缠中说禅技术分析工具;缠论;股票;期货;Quant;量化交易 |  |  |
| [R2 Bitcoin Arbitrager](https://github.com/bitrinjani/r2) | R2 Bitcoin Arbitrager is an automatic arbitrage trading system powered by Node.js + TypeScript |  |  |
| [analyzingalpha](https://github.com/leosmigel/analyzingalpha) | Implementation of simple strategies |  |  |
| [PyTrendFollow](https://github.com/chrism2671/PyTrendFollow) | PyTrendFollow - systematic futures trading using trend following |  |  |
## Analytics
### Indicators
*Libraries of indicators to predict future price movements.*
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [ta-lib](https://github.com/mrjbq7/ta-lib) | Perform technical analysis of financial market data |  |  |
| [go-tart](https://github.com/iamjinlei/go-tart) | A Go implementation of the [ta-lib]((https://github.com/mrjbq7/ta-lib) with streaming update support |  |  |
| [pandas-ta](https://github.com/twopirllc/pandas-ta) | Pandas Technical Analysis (Pandas TA) is an easy to use library that leverages the Pandas package with more than 130 Indicators and Utility functions and more than 60 TA Lib Candlestick Patterns |  |  |
| [finta](https://github.com/peerchemist/finta) | Common financial technical indicators implemented in Pandas |  |  |
| [ta-rust](https://github.com/greyblake/ta-rs) | Technical analysis library for Rust language |  |  |
### Metrics computation
*Librairies of financial metrics.*
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [quantstats](https://github.com/ranaroussi/quantstats) | Portfolio analytics for quants, written in Python |  |  |
| [ffn](https://github.com/pmorissette/ffn) | A financial function library for Python |  |  |
### Optimization
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [PyPortfolioOpt](https://github.com/robertmartin8/PyPortfolioOpt) | Financial portfolio optimizations in python, including classical efficient frontier, Black-Litterman, Hierarchical Risk Parity |  |  |
| [Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib) | Portfolio Optimization and Quantitative Strategic Asset Allocation in Python |  |  |
| [empyrial](https://github.com/ssantoshp/Empyrial) | Empyrial is a Python-based open-source quantitative investment library dedicated to financial institutions and retail investors, officially released in March 2021 |  |  |
| [Deepdow](https://github.com/jankrepl/deepdow) | Python package connecting portfolio optimization and deep learning. Its goal is to facilitate research of networks that perform weight allocation in one forward pass. |  |  |
| [spectre](https://github.com/Heerozh/spectre) | Portfolio Optimization and Quantitative Strategic Asset Allocation in Python |  |  |
### Pricing
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [tf-quant-finance](https://github.com/google/tf-quant-finance) | High-performance TensorFlow library for quantitative finance from Google |  |  |
| [FinancePy](https://github.com/domokane/FinancePy) | A Python Finance Library that focuses on the pricing and risk-management of Financial Derivatives, including fixed-income, equity, FX and credit derivatives |  |  |
| [PyQL](https://github.com/enthought/pyql) | Python wrapper of the famous pricing library QuantLib |  |  |
### Risk
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [pyfolio](https://github.com/quantopian/pyfolio) | Portfolio and risk analytics in Python |  |  |
## Broker APIs
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [ccxt](https://github.com/ccxt/ccxt) | A JavaScript / Python / PHP cryptocurrency trading API with support for more than 100 bitcoin/altcoin exchanges |  |  |
| [Ib_insync](https://github.com/erdewit/ib_insync) | Python sync/async framework for Interactive Brokers. |  |  |
| [Coinnect](https://github.com/hugues31/coinnect) | Coinnect is a Rust library aiming to provide a complete access to main crypto currencies exchanges via REST API. |  |  |
| [PENDAX](https://github.com/CompendiumFi/PENDAX-SDK) | Javascript SDK for Trading, Data, and Websockets for FTX, FTXUS, OKX, Bybit, & More. |  |  |
## Data Sources
### General
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [OpenBB Terminal](https://github.com/OpenBB-finance/OpenBBTerminal) | Investment Research for Everyone, Anywhere. |  |  |
| [TuShare](https://github.com/waditu/tushare) | TuShare is a utility for crawling historical data of China stocks |  |  |
| [yfinance](https://github.com/ranaroussi/yfinance) | yfinance offers a threaded and Pythonic way to download market data from Yahoo!Ⓡ finance. |  |  |
| [AkShare](https://github.com/akfamily/akshare) | AKShare is an elegant and simple financial data interface library for Python, built for human beings! |  |  |
| [pandas-datareader](https://github.com/pydata/pandas-datareader) | Up to date remote data access for pandas, works for multiple versions of pandas. |  |  |
| [Quandl](https://github.com/quandl/quandl-python) | Get millions of financial and economic dataset from hundreds of publishers via a single free API. |  |  |
| [findatapy](https://github.com/cuemacro/findatapy) | findatapy creates an easy to use Python API to download market data from many sources including Quandl, Bloomberg, Yahoo, Google etc. using a unified high level interface. |  |  |
| [Investpy](https://github.com/alvarobartt/investpy) | Financial Data Extraction from Investing.com with Python |  |  |
| [Fundamental Analysis Data](https://github.com/JerBouma/FundamentalAnalysis) | Fully-fledged Fundamental Analysis package capable of collecting 20 years of Company Profiles, Financial Statements, Ratios and Stock Data of 20.000+ companies. |  |  |
| [Wallstreet](https://github.com/mcdallas/wallstreet) | Wallstreet: Real time Stock and Option tools |  |  |
### Cryptocurrencies
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Cryptofeed](https://github.com/bmoscon/cryptofeed) | Cryptocurrency Exchange Websocket Data Feed Handler with Asyncio |  |  |
| [Gekko-Datasets](https://github.com/xFFFFF/Gekko-Datasets) | Gekko trading bot dataset dumps. Download and use history files in SQLite format. |  |  |
| [CryptoInscriber](https://github.com/Optixal/CryptoInscriber) | A live crypto currency historical trade data blotter. Download live historical trade data from any crypto exchange. |  |  |
| [Crypto Lake](https://github.com/crypto-lake/lake-api) | High frequency order book & trade data for crypto |  |  |
## Data Science
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [TensorFlow](https://github.com/tensorflow/tensorflow) | Fundamental algorithms for scientific computing in Python |  |  |
| [Pytorch](https://github.com/pytorch/pytorch) | Tensors and Dynamic neural networks in Python with strong GPU acceleration |  |  |
| [Keras](https://github.com/keras-team/keras) | The most user friendly Deep Learning for humans in Python |  |  |
| [Scikit-learn](https://github.com/scikit-learn/scikit-learn) | Machine learning in Python |  |  |
| [Pandas](https://github.com/pandas-dev/pandas) | Flexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more |  |  |
| [Numpy](https://github.com/numpy/numpy) | The fundamental package for scientific computing with Python |  |  |
| [Scipy](https://github.com/scipy/scipy) | Fundamental algorithms for scientific computing in Python |  |  |
| [PyMC](https://github.com/pymc-devs/pymc) | Probabilistic Programming in Python: Bayesian Modeling and Probabilistic Machine Learning with Aesara |  |  |
| [Cvxpy](https://github.com/cvxpy/cvxpy) | A Python-embedded modeling language for convex optimization problems. |  |  |
## Databases
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Marketstore](https://github.com/alpacahq/marketstore) | DataFrame Server for Financial Timeseries Data |  |  |
| [Tectonicdb](https://github.com/0b01/tectonicdb) | Tectonicdb is a fast, highly compressed standalone database and streaming protocol for order book ticks. |  |  |
| [ArcticDB (Man Group)](https://github.com/man-group/arcticdb) | High performance datastore for time series and tick data |  |  |
## Graph Computation
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Ray](https://github.com/ray-project/ray) | An open source framework that provides a simple, universal API for building distributed applications. |  |  |
| [Dask](https://github.com/dask/dask) | Parallel computing with task scheduling in Python with a Pandas like API |  |  |
| [Incremental (JaneStreet)](https://github.com/janestreet/incremental) | Incremental is a library that gives you a way of building complex computations that can update efficiently in response to their inputs changing, inspired by the work of Umut Acar et. al. on self-adjusting computations. Incremental can be useful in a number of applications |  |  |
| [Man MDF](https://github.com/man-group/mdf) | Data-flow programming toolkit for Python |  |  |
| [GraphKit](https://github.com/yahoo/graphkit) | A lightweight Python module for creating and running ordered graphs of computations. |  |  |
| [Tributary](https://github.com/timkpaine/tributary) | Streaming reactive and dataflow graphs in Python |  |  |
## Machine Learning
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [QLib (Microsoft)](https://github.com/microsoft/qlib) | Qlib is an AI-oriented quantitative investment platform, which aims to realize the potential, empower the research, and create the value of AI technologies in quantitative investment. With Qlib, you can easily try your ideas to create better Quant investment strategies. An increasing number of SOTA Quant research works/papers are released in Qlib. |  |  |
| [FinRL](https://github.com/AI4Finance-Foundation/FinRL) | FinRL is the first open-source framework to demonstrate the great potential of applying deep reinforcement learning in quantitative finance. |  |  |
| [MlFinLab (Hudson & Thames)](https://github.com/hudson-and-thames/mlfinlab) | MlFinLab helps portfolio managers and traders who want to leverage the power of machine learning by providing reproducible, interpretable, and easy to use tools. |  |  |
| [TradingGym](https://github.com/Yvictor/TradingGym) | Trading and Backtesting environment for training reinforcement learning agent or simple rule base algo. |  |  |
| [Stock Trading Bot using Deep Q-Learning](https://github.com/pskrunner14/trading-bot) | Stock Trading Bot using Deep Q-Learning |  |  |
## TimeSeries Analysis
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [Facebook Prophet](https://github.com/facebook/prophet) | Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth. |  |  |
| [statsmodels](https://github.com/statsmodels/statsmodels) | Python module that allows users to explore data, estimate statistical models, and perform statistical tests. |  |  |
| [tsfresh](https://github.com/blue-yonder/tsfresh) | Automatic extraction of relevant features from time series. |  |  |
| [pmdarima](https://github.com/alkaline-ml/pmdarima) | A statistical library designed to fill the void in Python's time series analysis capabilities, including the equivalent of R's auto.arima function. |  |  |
## Visualization
| Repository | Description | Stars | Made with |
|------------|-------------|-------|-----------|
| [D-Tale (Man Group)](https://github.com/man-group/dtale) | D-Tale is the combination of a Flask back-end and a React front-end to bring you an easy way to view & analyze Pandas data structures. |  |  |
| [mplfinance](https://github.com/matplotlib/mplfinance) | Financial Markets Data Visualization using Matplotlib |  |  |
| [btplotting](https://github.com/happydasch/btplotting) | btplotting provides plotting for backtests, optimization results and live data from backtrader. |  |  |
# Strategies
*List of **40+ academic papers** describing original systematic trading strategies. Each strategy is categorized by its asset class and ordered by descending Sharpe ratio.*
👉 Strategies are now hosted [here](https://paperswithbacktest.com).
Previous list of strategies:
## Bonds, commodities, currencies, equities
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|-------------|--------------|------------|-------------|----------------|--------|
| Time Series Momentum Effect | `0.576` | `20.5%` | `Monthly` | [QuantConnect](./static/strategies/time-series-momentum-effect.py) | [Paper](https://pages.stern.nyu.edu/~lpederse/papers/TimeSeriesMomentum.pdf) |
| Short Term Reversal with Futures | `-0.05` | `12.3%` | `Weekly` | [QuantConnect](./static/strategies/asset-class-momentum-rotational-system.py) | [Paper](https://ideas.repec.org/a/eee/jbfina/v28y2004i6p1337-1361.html) |
## Bonds, commodities, equities, REITs
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Asset Class Trend-Following | `0.502` | `10.4%` | `Monthly` | [QuantConnect](./static/strategies/asset-class-trend-following.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=962461) |
| Momentum Asset Allocation Strategy | `0.321` | `11%` | `Monthly` | [QuantConnect](./static/strategies/asset-class-trend-following.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1585517) |
## Bonds, equities
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Paired Switching | `0.691` | `9.5%` | `Quarterly` | [QuantConnect](./static/strategies/paired-switching.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1917044) |
| FED Model | `0.369` | `14.3%` | `Monthly` | [QuantConnect](./static/strategies/fed-model.py) | [Paper](https://www.researchgate.net/publication/228267011_The_FED_Model_and_Expected_Asset_Returns) |
## Bonds, equities, REITs
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Value and Momentum Factors across Asset Classes | `0.155` | `9.8%` | `Monthly` | [QuantConnect](./static/strategies/value-and-momentum-factors-across-asset-classes.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1079975) |
## Commodities
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Skewness Effect in Commodities | `0.482` | `17.7%` | `Monthly` | [QuantConnect](./static/strategies/skewness-effect-in-commodities.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2671165) |
| Return Asymmetry Effect in Commodity Futures | `0.239` | `13.4%` | `Monthly` | [QuantConnect](./static/strategies/return-asymmetry-effect-in-commodity-futures.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3918896) |
| Momentum Effect in Commodities | `0.14` | `20.3%` | `Monthly` | [QuantConnect](./static/strategies/momentum-effect-in-commodities.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=702281) |
| Term Structure Effect in Commodities | `0.128` | `23.1%` | `Monthly` | [QuantConnect](./static/strategies/term-structure-effect-in-commodities.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1127213) |
| Trading WTI/BRENT Spread | `-0.199` | `11.6%` | `Daily` | [QuantConnect](./static/strategies/trading-wti-brent-spread.py) | [Paper](https://link.springer.com/article/10.1057/jdhf.2009.24) |
## Cryptos
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Overnight Seasonality in Bitcoin | `0.892` | `20.8%` | `Intraday` | [QuantConnect](./static/strategies/intraday-seasonality-in-bitcoin.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4081000) |
| Rebalancing Premium in Cryptocurrencies | `0.698` | `27.5%` | `Daily` | [QuantConnect](./static/strategies/rebalancing-premium-in-cryptocurrencies.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3982120) |
## Currencies
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| FX Carry Trade | `0.254` | `7.8%` | `Monthly` | [QuantConnect](./static/strategies/fx-carry-trade.py) | [Paper](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
| Dollar Carry Trade | `0.113` | `5.8%` | `Monthly` | [QuantConnect](./static/strategies/dollar-carry-trade.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1541230) |
| Currency Momentum Factor | `-0.01` | `6.7%` | `Monthly` | [QuantConnect](./static/strategies/currency-momentum-factor.py) | [Paper](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
| Currency Value Factor – PPP Strategy | `-0.103` | `5%` | `Quarterly` | [QuantConnect](./static/strategies/currency-value-factor-ppp-strategy.py) | [Paper](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
## Equities
| Title | Sharpe Ratio | Volatility | Rebalancing | Implementation | Source |
|--------------|--------------|------------|-------------|----------------|--------|
| Asset Growth Effect | `0.835` | `10.2%` | `Yearly` | [QuantConnect](./static/strategies/asset-growth-effect.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1335524) |
| Short Term Reversal Effect in Stocks | `0.816` | `21.4%` | `Weekly` | [QuantConnect](./static/strategies/short-term-reversal-in-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1605049) |
| Reversal During Earnings-Announcements | `0.785` | `25.7%` | `Daily` | [QuantConnect](./static/strategies/reversal-during-earnings-announcements.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2275982) |
| Size Factor – Small Capitalization Stocks Premium | `0.747` | `11.1%` | `Yearly` | [QuantConnect](./static/strategies/small-capitalization-stocks-premium-anomaly.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3177539) |
| Low Volatility Factor Effect in Stocks | `0.717` | `11.5%` | `Monthly` | [QuantConnect](./static/strategies/low-volatility-factor-effect-in-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=980865) |
| How to Use Lexical Density of Company Filings | `0.688` | `10.4%` | `Monthly` | [QuantConnect](./static/strategies/how-to-use-lexical-density-of-company-filings.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3921091) |
| Volatility Risk Premium Effect | `0.637` | `13.2%` | `Monthly` | [QuantConnect](./static/strategies/volatility-risk-premium-effect.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=189840) |
| Pairs Trading with Stocks | `0.634` | `8.5%` | `Daily` | [QuantConnect](./static/strategies/pairs-trading-with-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=141615) |
| Crude Oil Predicts Equity Returns | `0.599` | `11.5%` | `Monthly` | [QuantConnect](./static/strategies/crude-oil-predicts-equity-returns.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=460500) |
| Betting Against Beta Factor in Stocks | `0.594` | `18.9%` | `Monthly` | [QuantConnect](./static/strategies/betting-against-beta-factor-in-stocks.py) | [Paper](https://pages.stern.nyu.edu/~lpederse/papers/BettingAgainstBeta.pdf) |
| Trend-following Effect in Stocks | `0.569` | `15.2%` | `Daily` | [QuantConnect](./static/strategies/trend-following-effect-in-stocks.py) | [Paper](https://www.cis.upenn.edu/~mkearns/finread/trend.pdf) |
| ESG Factor Momentum Strategy | `0.559` | `21.8%` | `Monthly` | [QuantConnect](./static/strategies/esg-factor-momentum-strategy.py) | [Paper](https://www.semanticscholar.org/paper/Can-ESG-Add-Alpha-An-Analysis-of-ESG-Tilt-and-Nagy-Kassam/64f77da4f8ce5906a73ffe4e9eec7c49c0960acc) |
| Value (Book-to-Market) Factor | `0.526` | `11.9%` | `Monthly` | [QuantConnect](./static/strategies/value-book-to-market-factor.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2595747) |
| Soccer Clubs’ Stocks Arbitrage | `0.515` | `14.2%` | `Daily` | [QuantConnect](./static/strategies/soccer-clubs-stocks-arbitrage.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1343685) |
| Synthetic Lending Rates Predict Subsequent Market Return | `0.494` | `13.7%` | `Daily` | [QuantConnect](./static/strategies/synthetic-lending-rates-predict-subsequent-market-return.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3976307) |
| Option-Expiration Week Effect | `0.452` | `5%` | `Weekly` | [QuantConnect](./static/strategies/option-expiration-week-effect.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1571786) |
| Dispersion Trading | `0.432` | `8.1%` | `Monthly` | [QuantConnect](./static/strategies/dispersion-trading.py) | [Paper](https://www.academia.edu/16327015/EQUILIBRIUM_INDEX_AND_SINGLE_STOCK_VOLATILITY_RISK_PREMIA) |
| Momentum in Mutual Fund Returns | `0.414` | `13.6%` | `Quarterly` | [QuantConnect](./static/strategies/momentum-in-mutual-fund-returns.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1462408) |
| Sector Momentum – Rotational System | `0.401` | `14.1%` | `Monthly` | [QuantConnect](./static/strategies/sector-momentum-rotational-system.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1585517) |
| Combining Smart Factors Momentum and Market Portfolio | `0.388` | `8.2%` | `Monthly` | [QuantConnect](./static/strategies/combining-smart-factors-momentum-and-market-portfolio.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3745517) |
| Momentum and Reversal Combined with Volatility Effect in Stocks | `0.375` | `17%` | `Monthly` | [QuantConnect](./static/strategies/momentum-and-reversal-combined-with-volatility-effect-in-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1679464) |
| Market Sentiment and an Overnight Anomaly | `0.369` | `3.6%` | `Daily` | [QuantConnect](./static/strategies/market-sentiment-and-an-overnight-anomaly.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3829582) |
| January Barometer | `0.365` | `7.4%` | `Monthly` | [QuantConnect](./static/strategies/january-barometer.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1436516) |
| R&D Expenditures and Stock Returns | `0.354` | `8.1%` | `Yearly` | [QuantConnect](./static/strategies/rd-expenditures-and-stock-returns.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=227564) |
| Value Factor – CAPE Effect within Countries | `0.351` | `20.2%` | `Yearly` | [QuantConnect](./static/strategies/value-factor-effect-within-countries.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2129474) |
| 12 Month Cycle in Cross-Section of Stocks Returns | `0.34` | `43.7%` | `Monthly` | [QuantConnect](./static/strategies/12-month-cycle-in-cross-section-of-stocks-returns.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=687022) |
| Turn of the Month in Equity Indexes | `0.305` | `7.2%` | `Daily` | [QuantConnect](./static/strategies/turn-of-the-month-in-equity-indexes.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=917884) |
| Payday Anomaly | `0.269` | `3.8%` | `Daily` | [QuantConnect](./static/strategies/payday-anomaly.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3257064) |
| Pairs Trading with Country ETFs | `0.257` | `5.7%` | `Daily` | [QuantConnect](./static/strategies/pairs-trading-with-country-etfs.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1958546) |
| Residual Momentum Factor | `0.24` | `9.7%` | `Monthly` | [QuantConnect](./static/strategies/residual-momentum-factor.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2319861) |
| Earnings Announcement Premium | `0.192` | `3.7%` | `Monthly` | [QuantConnect](./static/strategies/earnings-announcement-premium.py) | [Paper](https://www.nber.org/system/files/working_papers/w13090/w13090.pdf) |
| ROA Effect within Stocks | `0.155` | `8.7%` | `Monthly` | [QuantConnect](./static/strategies/roa-effect-within-stocks.py) | [Paper](https://static1.squarespace.com/static/5e6033a4ea02d801f37e15bb/t/5f61583e88f43b7d5b7196b5/1600215105801/Chen_Zhang_JF.pdf) |
| 52-Weeks High Effect in Stocks | `0.153` | `19%` | `Monthly` | [QuantConnect](./static/strategies/52-weeks-high-effect-in-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1787378) |
| Combining Fundamental FSCORE and Equity Short-Term Reversals | `0.153` | `17.6%` | `Monthly` | [QuantConnect](./static/strategies/combining-fundamental-fscore-and-equity-short-term-reversals.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3097420) |
| Betting Against Beta Factor in International Equities | `0.142` | `9.1%` | `Monthly` | [QuantConnect](./static/strategies/betting-against-beta-factor-in-country-equity-indexes.py) | [Paper](https://pages.stern.nyu.edu/~lpederse/papers/BettingAgainstBeta.pdf) |
| Consistent Momentum Strategy | `0.128` | `28.8%` | `6 Months` | [QuantConnect](./static/strategies/consistent-momentum-strategy.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2652592) |
| Short Interest Effect – Long-Short Version | `0.079` | `6.6%` | `Monthly` | [QuantConnect](./static/strategies/short-interest-effect-long-short-version.py) | [Paper](https://www.semanticscholar.org/paper/Why-Do-Short-Interest-Levels-Predict-Stock-Returns-Boehmer-Erturk/06418ef437dc7156229532a97d0f8392373eb297?p2df) |
| Momentum Factor Combined with Asset Growth Effect | `0.058` | `25.1%` | `Monthly` | [QuantConnect](./static/strategies/momentum-factor-combined-with-asset-growth-effect.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1684767) |
| Momentum Factor Effect in Stocks | `-0.008` | `21.8%` | `Monthly` | [QuantConnect](./static/strategies/momentum-factor-effect-in-stocks.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2435323) |
| Momentum Factor and Style Rotation Effect | `-0.056` | `10%` | `Monthly` | [QuantConnect](./static/strategies/momentum-factor-and-style-rotation-effect.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1276815) |
| Earnings Announcements Combined with Stock Repurchases | `-0.16` | `0.1%` | `Daily` | [QuantConnect](./static/strategies/earnings-announcements-combined-with-stock-repurchases.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2589966) |
| Earnings Quality Factor | `-0.18` | `28.7%` | `Yearly` | [QuantConnect](./static/strategies/earnings-quality-factor.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2179247) |
| Accrual Anomaly | `-0.272` | `13.7%` | `Yearly` | [QuantConnect](./static/strategies/accrual-anomaly.py) | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=546108) |
| ESG, Price Momentum and Stochastic Optimization | `N/A` | `N/A` | `Monthly` | | [Paper](https://quantpedia.com/strategies/esg-price-momentum-and-stochastic-optimization/) |
| The Positive Similarity of Company Filings and Stock Returns | `N/A` | `N/A` | `Monthly` | | [Paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3690461) |
# Books
A comprehensive list of **55 books** for quantitative traders.
## Beginner
| Title | Reviews | Rating |
|----------|---------|--------|
| [A Beginner’s Guide to the Stock Market: Everything You Need to Start Making Money Today - Matthew R. Kratter](https://amzn.to/3QN2VdU) |  |  |
| [How to Day Trade for a Living: A Beginner’s Guide to Trading Tools and Tactics, Money Management, Discipline and Trading Psychology - Andrew Aziz](https://amzn.to/3bmehFv) |  |  |
| [The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns - John C. Bogle](https://amzn.to/3A4mgkR) |  |  |
| [Investing QuickStart Guide: The Simplified Beginner’s Guide to Successfully Navigating the Stock Market, Growing Your Wealth & Creating a Secure Financial Future - Ted D. Snow](https://amzn.to/3A5aRkX) |  |  |
| [Day Trading QuickStart Guide: The Simplified Beginner’s Guide to Winning Trade Plans, Conquering the Markets, and Becoming a Successful Day Trader - Troy Noonan](https://amzn.to/3HPZijw) |  |  |
| [Introduction To Algo Trading: How Retail Traders Can Successfully Compete With Professional Traders - Kevin J Davey](https://amzn.to/39Tf7JC) |  |  |
| [Algorithmic Trading and DMA: An introduction to direct access trading strategies - Barry Johnson](https://amzn.to/3xYb0UN) |  |  |
## Biography
| Title | Reviews | Rating |
|----------|---------|--------|
| [My Life as a Quant: Reflections on Physics and Finance - Emanuel Derman](https://amzn.to/3A8KudR) |  |  |
| [How I Became a Quant: Insights from 25 of Wall Street’s Elite: - Barry Schachter](https://amzn.to/3Alf8kz) |  |  |
## Coding
| Title | Reviews | Rating |
|----------|---------|--------|
| [Python for Finance: Mastering Data-Driven Finance - Yves Hilpisch](https://amzn.to/3NhkTlP) |  |  |
| [Trading Evolved: Anyone can Build Killer Trading Strategies in Python - Andreas F. Clenow](https://amzn.to/3A0jcGB) |  |  |
| [Python for Algorithmic Trading: From Idea to Cloud Deployment - Yves Hilpisch](https://amzn.to/3bpkd0C) |  |  |
| [Algorithmic Trading with Python: Quantitative Methods and Strategy Development - Chris Conlan](https://amzn.to/3u3cxYo) |  |  |
| [Learn Algorithmic Trading: Build and deploy algorithmic trading systems and strategies using Python and advanced data analysis - Sebastien Donadio](https://amzn.to/3NqNghA) |  |  |
## Crypto
| Title | Reviews | Rating |
|----------|---------|--------|
| [The Bitcoin Standard: The Decentralized Alternative to Central Banking - Saifedean Ammous](https://amzn.to/3QMJgec) |  |  |
| [Bitcoin Billionaires: A True Story of Genius, Betrayal, and Redemption - Ben Mezrich](https://amzn.to/39SkdWt) |  |  |
| [Mastering Bitcoin: Programming the Open Blockchain - Andreas M. Antonopoulos](https://amzn.to/3NniZ3p) |  |  |
| [Why Buy Bitcoin: Investing Today in the Money of Tomorrow - Andy Edstrom](https://amzn.to/3OMcKqZ) |  |  |
## General
| Title | Reviews | Rating |
|----------|---------|--------|
| [The Intelligent Investor: The Definitive Book on Value Investing - Benjamin Graham, Jason Zweig](https://www.amazon.fr/gp/product/0060555661/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0060555661&linkId=aba73910e4e3873b6cc8364487662bd6) |  |  |
| [How I Invest My Money: Finance experts reveal how they save, spend, and invest - Joshua Brown, Brian Portnoy](https://amzn.to/3A4rsoU) |  |  |
| [Naked Forex: High-Probability Techniques for Trading Without Indicators - Alex Nekritin](https://amzn.to/3NkrAUj) |  |  |
| [The Four Pillars of Investing: Lessons for Building a Winning Portfolio - William J. Bernstein](https://www.amazon.fr/gp/product/B0041842TW/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=B0041842TW&linkId=d9bc2fec4f3faa41ca4f24aed3c72122) |  |  |
| [Option Volatility and Pricing: Advanced Trading Strategies and Techniques, 2nd Edition - Sheldon Natenberg](https://amzn.to/3btOxXL) |  |  |
| [The Art and Science of Technical Analysis: Market Structure, Price Action, and Trading Strategies - Adam Grimes](https://www.amazon.fr/gp/product/1118115120/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118115120&linkId=d5dc1f0e6727b2663d2186a110a31ad0) |  |  |
| [The New Trading for a Living: Psychology, Discipline, Trading Tools and Systems, Risk Control, Trade Management (Wiley Trading) - Alexander Elder](https://www.amazon.fr/gp/product/1118467450/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118467450&linkId=67ee502653bc52a5240ced9fc88eb76d) |  |  |
| [Building Winning Algorithmic Trading Systems: A Trader’s Journey From Data Mining to Monte Carlo Simulation to Live Trading (Wiley Trading) - Kevin J Davey](https://amzn.to/39QnsxA) |  |  |
| [Systematic Trading: A unique new method for designing trading and investing systems - Robert Carver](https://www.amazon.fr/gp/product/0857194453/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0857194453&linkId=32d8bffc32c01041cde066bacab76c04) |  |  |
| [Quantitative Momentum: A Practitioner’s Guide to Building a Momentum-Based Stock Selection System (Wiley Finance) - Wesley R. Gray, Jack R. Vogel](https://www.amazon.fr/gp/product/111923719X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=111923719X&linkId=b825cb65462a4a9254af3b7dc5328131) |  |  |
| [Algorithmic Trading: Winning Strategies and Their Rationale - Ernest P. Chan](https://amzn.to/3xWi8kd) |  |  |
| [Leveraged Trading: A professional approach to trading FX, stocks on margin, CFDs, spread bets and futures for all traders - Robert Carver](https://amzn.to/3Nhl6p7) |  |  |
| [Trading Systems: A New Approach to System Development and Portfolio Optimisation - Emilio Tomasini, Urban Jaekle](https://www.amazon.fr/gp/product/1905641796/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1905641796&linkId=61e6634242c497498338f73641ce0a80) |  |  |
| [Trading and Exchanges: Market Microstructure for Practitioners - Larry Harris](https://www.amazon.fr/gp/product/0195144708/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0195144708&linkId=e47e596fc0696cbd624726cce05b4500) |  |  |
| [Trading Systems 2nd edition: A new approach to system development and portfolio optimisation - Emilio Tomasini, Urban Jaekle](https://www.amazon.fr/gp/product/085719755X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=085719755X&linkId=97aa558484a8dc2bf57a5296e7f38cad) |  |  |
| [Machine Trading: Deploying Computer Algorithms to Conquer the Markets - Ernest P. Chan](https://amzn.to/3OIBe4o) |  |  |
| [Quantitative Equity Portfolio Management: An Active Approach to Portfolio Construction and Management (McGraw-Hill Library of Investment and Finance) - Ludwig B Chincarini, Daehwan Kim](https://amzn.to/3yl9u0c) |  |  |
| [Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk - Richard Grinold, Ronald Kahn](https://amzn.to/3xMKaic) |  |  |
| [Quantitative Technical Analysis: An integrated approach to trading system development and trading management - Dr Howard B Bandy](https://www.amazon.fr/gp/product/0979183855/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0979183855&linkId=8ef7bda69477bdccf90f5ac02ee495b0) |  |  |
| [Advances in Active Portfolio Management: New Developments in Quantitative Investing - Richard Grinold, Ronald Kahn](https://amzn.to/3xUTK2z) |  |  |
| [Professional Automated Trading: Theory and Practice - Eugene A. Durenard](https://amzn.to/3yhfOpw) |  |  |
| [Algorithmic Trading and Quantitative Strategies (Chapman and Hall/CRC Financial Mathematics Series) - Raja Velu, Maxence Hardy, Daniel Nehren](https://amzn.to/3xUTQXZ) |  |  |
| [Quantitative Trading: Algorithms, Analytics, Data, Models, Optimization - Xin Guo, Tze Leung Lai, Howard Shek, Samuel Po-Shing Wong](https://www.amazon.fr/gp/product/0367871815/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0367871815&linkId=3f2ba1cbc0e1fe02e255da740423b2fb) |  |  |
## High Frequency Trading
| Title | Reviews | Rating |
|----------|---------|--------|
| [Inside the Black Box: A Simple Guide to Quantitative and High Frequency Trading - Rishi K. Narang](https://www.amazon.fr/gp/product/1118362411/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118362411&linkId=35e02d4e636350366531a5033597a541) |  |  |
| [Algorithmic and High-Frequency Trading (Mathematics, Finance and Risk) - Álvaro Cartea, Sebastian Jaimungal, José Penalva](https://www.amazon.fr/gp/product/1107091144/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1107091144&linkId=64e3ceb66482d8db6827830964b85613) |  |  |
| [The Problem of HFT – Collected Writings on High Frequency Trading & Stock Market Structure Reform - Haim Bodek](https://www.amazon.fr/gp/product/1481978357/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1481978357&linkId=2f3acf998de645990b681e2ac9f0217c) |  |  |
| [An Introduction to High-Frequency Finance - Ramazan Gençay, Michel Dacorogna, Ulrich A. Muller, Olivier Pictet, Richard Olsen](https://www.amazon.fr/gp/product/0122796713/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0122796713&linkId=7e6c098026204f399e45d7fbb803dcca) |  |  |
| [Market Microstructure in Practice - Charles-Albert Lehalle, Sophie Laruelle](https://www.amazon.fr/Market-Microstructure-Practice-Sophie-Laruelle/dp/9813231122) |  |  |
| [The Financial Mathematics of Market Liquidity - Olivier Gueant](https://www.amazon.com/Financial-Mathematics-Market-Liquidity-Execution/dp/1498725473) |  |  |
| [High-Frequency Trading - Maureen O’Hara, David Easley, Marcos M López de Prado](https://www.amazon.fr/gp/product/178272009X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=178272009X&linkId=082f861ff6bbe4cca4ef7ccbe620a2c4) |  |  |
## Machine Learning
| Title | Reviews | Rating |
|----------|---------|--------|
| [Dark Pools: The rise of A.I. trading machines and the looming threat to Wall Street - Scott Patterson](https://www.amazon.fr/gp/product/0307887189/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0307887189&linkId=2572cae24ed7de0b279580312daf0f03) |  |  |
| [Advances in Financial Machine Learning - Marcos Lopez de Prado](https://www.amazon.fr/gp/product/1119482089/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1119482089&linkId=7eff4d3f3d9f2d00d05032f726386e53) |  |  |
| [Machine Learning for Algorithmic Trading: Predictive models to extract signals from market and alternative data for systematic trading strategies with Python, 2nd Edition - Stefan Jansen](https://www.amazon.fr/gp/product/1839217715/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1839217715&linkId=80e3e93e1b6027596858ed0f1fbf10c2) |  |  |
| [Machine Learning for Asset Managers (Elements in Quantitative Finance) - Marcos M López de Prado](https://www.amazon.fr/gp/product/1108792898/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1108792898&linkId=8eb7e3c369d38b36df8dfecf05a622db) |  |  |
| [Machine Learning in Finance: From Theory to Practice - Matthew F. Dixon, Igor Halperin, Paul Bilokon](https://www.amazon.fr/gp/product/3030410676/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=3030410676&linkId=5f5f1df6be62ae96ef7a0c536c3ecdb4) |  |  |
| [Artificial Intelligence in Finance: A Python-Based Guide - Yves Hilpisch](https://www.amazon.fr/gp/product/1492055433/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1492055433&linkId=7c20249be4d35badb127d6a5423fc495) |  |  |
| [Algorithmic Trading Methods: Applications Using Advanced Statistics, Optimization, and Machine Learning Techniques - Robert Kissell](https://www.amazon.fr/gp/product/0128156309/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0128156309&linkId=0a197c0b547a0ee63ccd19389bb42edd) |  |  |
# Videos
| Title | Likes |
|--------------------------------------------------------------------|-------|
| [Krish Naik - Machine learning tutorials and their Application in Stock Prediction](https://www.youtube.com/watch?v=H6du_pfuznE) |  |
| [QuantInsti Youtube - webinars about Machine Learning for trading](https://www.youtube.com/user/quantinsti/search?query=machine+learning) |  |
| [Siraj Raval - Videos about stock market prediction using Deep Learning](https://www.youtube.com/channel/UCWN3xxRkmTPmbKwht9FuE5A/search?query=trading) |  |
| [Quantopian - Webinars about Machine Learning for trading](https://www.youtube.com/channel/UC606MUq45P3zFLa4VGKbxsg/search?query=machine+learning) |  |
| [Sentdex - Machine Learning for Forex and Stock analysis and algorithmic trading](https://www.youtube.com/watch?v=v_L9jR8P-54&list=PLQVvvaa0QuDe6ZBtkCNWNUbdaBo2vA4RO) |  |
| [QuantNews - Machine Learning for Algorithmic Trading 3 part series](https://www.youtube.com/playlist?list=PLHJACfjILJ-91qkw5YC83S6COKGscctzz) |  |
| [Sentdex - Python programming for Finance (a few videos including Machine Learning)](https://www.youtube.com/watch?v=Z-5wNWgRJpk&index=9&list=PLQVvvaa0QuDcOdF96TBtRtuQksErCEBYZ) |  |
| [Chat with Traders EP042 - Machine learning for algorithmic trading with Bert Mouler](https://www.youtube.com/watch?v=i8FNO8r7PaE) |  |
| [Tucker Balch - Applying Deep Reinforcement Learning to Trading](https://www.youtube.com/watch?v=Pka0DC_P17k) |  |
| [Ernie Chan - Machine Learning for Quantitative Trading Webinar](https://www.youtube.com/watch?v=72aEDjwGMr8&t=1023s) |  |
| [Chat with Traders EP147 - Detective work leading to viable trading strategies with Tom Starke](https://www.youtube.com/watch?v=JjXw9Mda7eY) |  |
| [Chat with Traders EP142 - Algo trader using automation to bypass human flaws with Bert Mouler](https://www.youtube.com/watch?v=ofL66mh6Tw0) |  |
| [Master Thesis presentation, Uni of Essex - Analyzing the Limit Order Book, A Deep Learning Approach](https://www.youtube.com/watch?v=qxSh2VFmRGw) |  |
| [Howard Bandy - Machine Learning Trading System Development Webinar](https://www.youtube.com/watch?v=v729evhMpYk&t=1s) |  |
| [Chat With Traders EP131 - Trading strategies, powered by machine learning with Morgan Slade](https://www.youtube.com/watch?v=EbWbeYu8zwg) |  |
| [Chat with Traders Quantopian 5 - Good Uses of Machine Learning in Finance with Max Margenot](https://www.youtube.com/watch?v=Zj5sXWv9SDM) |  |
| [Hitoshi Harada, CTO at Alpaca - Deep Learning in Finance Talk](https://www.youtube.com/watch?v=FoQKCeDuPiY) |  |
| [Better System Trader EP028 - David Aronson shares research into indicators that identify Bull and Bear markets.](https://www.youtube.com/watch?v=Q4rV0Y9NokI) |  |
| [Prediction Machines - Deep Learning with Python in Finance Talk](https://www.youtube.com/watch?v=xvm-M-R2fZY) |  |
| [Better System Trader EP064 - Cryptocurrencies and Machine Learning with Bert Mouler](https://www.youtube.com/watch?v=YgRTd4nLJoU) |  |
| [Better System Trader EP023 - Portfolio manager Michael Himmel talks AI and machine learning in trading](https://www.youtube.com/watch?v=9tZjeyhfG0g) |  |
| [Better System Trader EP082 - Machine Learning With Kris Longmore](https://www.youtube.com/watch?v=0syNgsd635M) |  |
# Blogs
| Title |
|--------------------------------------------------------------------|
| [AAA Quants, Tom Starke Blog](http://aaaquants.com/category/blog/) |
| [AI & Systematic Trading](https://blog.paperswithbacktest.com/) |
| [Blackarbs blog](http://www.blackarbs.com/blog/) |
| [Hardikp, Hardik Patel blog](https://www.hardikp.com/) |
| [Max Dama on Automated Trading](https://bit.ly/3wVZbh9) |
| [Medallion.Club on Systematic Trading (FR)](https://medallion.club/trading-algorithmique-quantitatif-systematique/) |
| [Proof Engineering: The Algorithmic Trading Platform](https://bit.ly/3lX7zYN) |
| [Quantsportal, Jacques Joubert's Blog](http://www.quantsportal.com/blog-page/) |
| [Quantstart - Machine Learning for Trading articles](https://www.quantstart.com/articles) |
| [RobotWealth, Kris Longmore Blog](https://robotwealth.com/blog/) |
# Courses
| Title |
|--------------------------------------------------------------------|
| [AI in Finance](https://cfte.education/) |
| [AI & Systematic Trading](https://paperswithbacktest.com/course) |
| [Algorithmic Trading for Cryptocurrencies in Python](https://github.com/tudorelu/tudorials/tree/master/trading) |
| [Coursera, NYU - Guided Tour of Machine Learning in Finance](https://www.coursera.org/learn/guided-tour-machine-learning-finance) |
| [Coursera, NYU - Fundamentals of Machine Learning in Finance](https://www.coursera.org/learn/fundamentals-machine-learning-in-finance) |
| [Coursera, NYU - Reinforcement Learning in Finance](https://www.coursera.org/learn/reinforcement-learning-in-finance) |
| [Coursera, NYU - Overview of Advanced Methods for Reinforcement Learning in Finance](https://www.coursera.org/learn/advanced-methods-reinforcement-learning-finance) |
| [Hudson and Thames Quantitative Research](https://github.com/hudson-and-thames) |
| [NYU: Overview of Advanced Methods of Reinforcement Learning in Finance](https://www.coursera.org/learn/advanced-methods-reinforcement-learning-finance/home/welcome) |
| [Udacity: Artificial Intelligence for Trading](https://www.udacity.com/course/ai-for-trading--nd880) |
| [Udacity, Georgia Tech - Machine Learning for Trading](https://www.udacity.com/course/machine-learning-for-trading--ud501) |
================================================
FILE: README_zh.md
================================================
令人敬畏的系统化交易
我们正在收集一份关于寻找、开发和运行系统性交易(量化交易)策略的资源论文、软件、书籍、文章清单。
### 你在这里会发现什么?
- [97个](#库和包)用于研究和实际交易的[库和包](#库和包)
- 机构和学术界描述的[40+项战略](#战略)
- [55本](#书籍)适合初学者和专业人士的[书籍](#书籍)
- [23个视频](#视频)和采访
- 还有一些[博客](#博客)和[课程](#课程)
点击这里查看完整的内容表
- [库和包](#库和包)
- [回溯测试和真实交易](#回溯测试和真实交易)
- [一般 - 事件驱动框架](#一般---事件驱动框架)
- [一般 - 基于矢量的框架](#一般---基于矢量的框架)
- [加密货币](#加密货币)
- [交易机器人](#交易机器人)
- [分析](#分析)
- [指标](#指标)
- [度量衡计算](#度量衡计算)
- [优化](#优化)
- [定价](#定价)
- [风险](#风险)
- [经纪人API](#经纪人api)
- [数据来源](#数据来源)
- [一般](#一般)
- [加密货币](#加密货币-1)
- [数据科学](#数据科学)
- [数据库](#数据库)
- [图形计算](#图形计算)
- [机器学习](#机器学习)
- [时间序列分析](#时间序列分析)
- [视觉化](#视觉化)
- [战略](#战略)
- [债券、商品、货币、股票](#债券商品货币股票)
- [债券、商品、股票、REITs](#债券商品股票reits)
- [债券、股票](#债券股票)
- [债券、股票、REITs](#债券股票reits)
- [商品](#商品)
- [加密货币](#加密货币-2)
- [货币](#货币)
- [股票](#股票)
- [书籍](#书籍)
- [初学者](#初学者)
- [传记](#传记)
- [编码](#编码)
- [隐蔽性](#隐蔽性)
- [一般](#一般-1)
- [高频交易](#高频交易)
- [机器学习](#机器学习-1)
- [视频](#视频)
- [博客](#博客)
- [课程](#课程)
> ### 我怎样才能提供帮助?
> 你可以通过提交带有建议的问题和在Twitter上分享来帮助。
>
> [](https://twitter.com/intent/tweet?text=A%20free%20and%20comprehensive%20list%20of%20papers%2C%20libraries%2C%20books%2C%20blogs%2C%20tutorials%20for%20quantitative%20traders.&url=https://github.com/paperswithbacktest/awesome-systematic-trading)
# 库和包
*97个实现交易机器人、回溯测试器、指标、定价器等的库和包列表。每个库都按其编程语言分类,并按人口降序排列(星星的数量)。*
## 回溯测试和真实交易
### 一般 - 事件驱动框架
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [vnpy](https://github.com/vnpy/vnpy) | 基于Python的开源量化交易系统开发框架,于2015年1月正式发布,已经一步步成长为一个全功能的量化交易平台。 |  |  |
| [zipline](https://github.com/quantopian/zipline) | Zipline是一个Pythonic算法交易库。它是一个事件驱动的系统,用于回溯测试。 |  |  |
| [backtrader](https://github.com/mementum/backtrader) | 事件驱动的Python交易策略回测库 |  |  |
| [QUANTAXIS](https://github.com/QUANTAXIS/QUANTAXIS) | QUANTAXIS 支持任务调度 分布式部署的 股票/期货/期权/港股/虚拟货币 数据/回测/模拟/交易/可视化/多账户 纯本地量化解决方案 |  |  |
| [QuantConnect](https://github.com/QuantConnect/Lean) | QuantConnect的精益算法交易引擎(Python,C#)。 |  |  |
| [Rqalpha](https://github.com/ricequant/rqalpha) | 一个可扩展、可替换的Python算法回测和交易框架,支持多种证券 |  |  |
| [finmarketpy](https://github.com/cuemacro/finmarketpy) | 用于回测交易策略和分析金融市场的Python库(前身为pythalesians)。 |  |  |
| [backtesting.py](https://github.com/kernc/backtesting.py) | Backtesting.py是一个Python框架,用于根据历史(过去)数据推断交易策略的可行性。Backtesting.py在Backtrader的基础上进行了改进,并以各种方式超越了其他可获得的替代方案,Backtesting.py是轻量级的、快速的、用户友好的、直观的、互动的、智能的,并希望是面向未来的。 |  |  |
| [zvt](https://github.com/zvtvz/zvt) | 模块化的量化框架 |  |  |
| [WonderTrader](https://github.com/wondertrader/wondertrader) | WonderTrader——量化研发交易一站式框架 |  |  |
| [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) | 一个高性能的算法交易平台和事件驱动的回测器 |  |  |
| [PandoraTrader](https://github.com/pegasusTrader/PandoraTrader) | 基于c++开发,支持多种交易API,跨平台的高频量化交易平台 |  |  |
[HFTBacktest](https://github.com/nkaz001/hftbacktest) | Python+Numba 对高频交易数据进行高精度回测 |  |  |
| [aat](https://github.com/AsyncAlgoTrading/aat) | 一个异步的、事件驱动的框架,用于用python编写算法交易策略,并可选择用C++进行加速。它的设计是模块化和可扩展的,支持各种工具和策略,在多个交易所之间进行实时交易。 |  |  |
| [sdoosa-algo-trade-python](https://github.com/sreenivasdoosa/sdoosa-algo-trade-python) | 这个项目主要是为那些有兴趣学习使用python解释器编写自己的交易算法的algo交易新手准备的。 |  |  |
| [lumibot](https://github.com/Lumiwealth/lumibot) | 一个非常简单而有用的回溯测试和基于样本的实时交易框架(运行速度有点慢......)。 |  |  |
| [quanttrader](https://github.com/letianzj/quanttrader) | 在Python中进行回测和实时交易。基于事件。类似于backtesting.py。 |  |  |
| [gobacktest](https://github.com/gobacktest/gobacktest) | 事件驱动的回溯测试框架的Go实现 |  |  |
| [FlashFunk](https://github.com/HFQR/FlashFunk) | Rust中的高性能运行时 |  |  |
### 一般 - 基于矢量的框架
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [vectorbt](https://github.com/polakowo/vectorbt) | vectorbt采取了一种新颖的回测方法:它完全在pandas和NumPy对象上运行,并由Numba加速,以速度和规模分析任何数据。这允许在几秒钟内对成千上万的策略进行测试。 |  |  |
| [pysystemtrade](https://github.com/robcarver17/pysystemtrade) | 罗布-卡弗的《系统交易》一书中的python系统交易 |  |  |
| [bt](https://github.com/pmorissette/bt) | 基于Algo和策略树的Python的灵活回测 |  |  |
### 加密货币
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Freqtrade](https://github.com/freqtrade/freqtrade) | Freqtrade是一个用Python编写的免费和开源的加密货币交易机器人。它被设计为支持所有主要交易所,并通过Telegram进行控制。它包含回测、绘图和资金管理工具,以及通过机器学习进行策略优化。 |  |  |
| [Jesse](https://github.com/jesse-ai/jesse) | Jesse是一个先进的加密货币交易框架,旨在简化研究和定义交易策略。 |  |  |
| [OctoBot](https://github.com/Drakkar-Software/OctoBot) | 用于TA、套利和社会交易的加密货币交易机器人,具有先进的网络界面 |  |  |
| [Kelp](https://github.com/stellar/kelp) | Kelp是一个免费和开源的交易机器人,适用于Stellar DEX和100多个集中式交易所 |  |  |
| [openlimits](https://github.com/nash-io/openlimits) | 一个Rust高性能的加密货币交易API,支持多个交易所和语言封装器。 |  |  |
| [bTrader](https://github.com/gabriel-milan/btrader) | Binance的三角套利交易机器人 |  |  |
| [crypto-crawler-rs](https://github.com/crypto-crawler/crypto-crawler-rs) | 抓取加密货币交易所的订单簿和交易信息 |  |  |
| [Hummingbot](https://github.com/CoinAlpha/hummingbot) | 一个用于加密货币做市的客户 |  |  |
| [cryptotrader-core](https://github.com/monomadic/cryptotrader-core) | 简单的使用Rust中的加密货币交易所REST API客户端。 |  |  |
## 交易机器人
*交易机器人和阿尔法模型。其中一些是旧的,没有维护。*
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Blackbird](https://github.com/butor/blackbird) | 黑鸟比特币套利:市场中立的多/空策略 |  |  |
| [bitcoin-arbitrage](https://github.com/maxme/bitcoin-arbitrage) | 比特币套利 - 机会检测器 |  |  |
| [ThetaGang](https://github.com/brndnmtthws/thetagang) | ThetaGang是一个用于收集资金的IBKR机器人 |  |  |
| [czsc](https://github.com/waditu/czsc) | 缠中说禅技术分析工具;缠论;股票;期货;Quant;量化交易 |  |  |
| [R2 Bitcoin Arbitrager](https://github.com/bitrinjani/r2) | R2 Bitcoin Arbitrager是一个由Node.js + TypeScript驱动的自动套利交易系统。 |  |  |
| [analyzingalpha](https://github.com/leosmigel/analyzingalpha) | 实施简单的战略 |  |  |
| [PyTrendFollow](https://github.com/chrism2671/PyTrendFollow) | PyTrendFollow - 使用趋势跟踪的系统性期货交易 |  |  |
## 分析
### 指标
*预测未来价格走势的指标库。*
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [ta-lib](https://github.com/mrjbq7/ta-lib) | 对金融市场数据进行技术分析 |  |  |
| [go-tart](https://github.com/iamjinlei/go-tart) | 用Go实现的[ta-lib]((https://github.com/mrjbq7/ta-lib),支持增量更新 |  |  |
| [pandas-ta](https://github.com/twopirllc/pandas-ta) | 潘达斯技术分析(Pandas TA)是一个易于使用的库,它利用潘达斯软件包的130多个指标和实用功能以及60多个TA Lib蜡烛图。 |  |  |
| [finta](https://github.com/peerchemist/finta) | 在Pandas中实施的共同财务技术指标 |  |  |
| [ta-rust](https://github.com/greyblake/ta-rs) | Rust语言的技术分析库 |  |  |
### 度量衡计算
*财务衡量标准。*
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [quantstats](https://github.com/ranaroussi/quantstats) | 用Python编写的面向量化投资人的投资组合分析方法 |  |  |
| [ffn](https://github.com/pmorissette/ffn) | 一个用于Python的金融函数库 |  |  |
### 优化
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [PyPortfolioOpt](https://github.com/robertmartin8/PyPortfolioOpt) | 在python中进行金融投资组合优化,包括经典的有效边界、Black-Litterman、分级风险平价等。 |  |  |
| [Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib) | Python中的投资组合优化和定量战略资产配置 |  |  |
| [empyrial](https://github.com/ssantoshp/Empyrial) | Empyrial是一个基于Python的开源量化投资库,专门为金融机构和零售投资者服务,于2021年3月正式发布。 |  |  |
| [Deepdow](https://github.com/jankrepl/deepdow) | 连接组合优化和深度学习的Python包。它的目标是促进研究在一次前进过程中进行权重分配的网络。 |  |  |
| [spectre](https://github.com/Heerozh/spectre) | Python中的投资组合优化和定量战略资产配置 |  |  |
### 定价
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [tf-quant-finance](https://github.com/google/tf-quant-finance) | 谷歌为量化金融提供的高性能TensorFlow库 |  |  |
| [FinancePy](https://github.com/domokane/FinancePy) | 一个Python金融库,专注于金融衍生品的定价和风险管理,包括固定收益、股票、外汇和信用衍生品。 |  |  |
| [PyQL](https://github.com/enthought/pyql) | 著名定价库QuantLib的Python封装器 |  |  |
### 风险
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [pyfolio](https://github.com/quantopian/pyfolio) | Python中的投资组合和风险分析 |  |  |
## 经纪人API
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [ccxt](https://github.com/ccxt/ccxt) | 一个JavaScript / Python / PHP加密货币交易API,支持100多个比特币/altcoin交易所 |  |  |
| [Ib_insync](https://github.com/erdewit/ib_insync) | 用于交互式经纪人的Python同步/async框架。 |  |  |
| [Coinnect](https://github.com/hugues31/coinnect) | Coinnect是一个Rust库,旨在通过REST API提供对主要加密货币交易所的完整访问。 |  |  |
## 数据来源
### 一般
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [OpenBB Terminal](https://github.com/OpenBB-finance/OpenBBTerminal) | 为每个人、在任何地方进行投资研究。 |  |  |
| [TuShare](https://github.com/waditu/tushare) | TuShare是一个用于抓取中国股票历史数据的工具。 |  |  |
| [yfinance](https://github.com/ranaroussi/yfinance) | yfinance提供了一个线程和Pythonic方式,从雅虎金融下载市场数据。 |  |  |
| [AkShare](https://github.com/akfamily/akshare) | AKShare是一个优雅而简单的Python金融数据接口库,它是为人类而建的!它是为人类服务的。 |  |  |
| [pandas-datareader](https://github.com/pydata/pandas-datareader) | 为pandas提供最新的远程数据访问,适用于多个版本的pandas。 |  |  |
| [Quandl](https://github.com/quandl/quandl-python) | 通过一个免费的API,从数百个出版商那里获得数以百万计的金融和经济数据集。 |  |  |
| [findatapy](https://github.com/cuemacro/findatapy) | findatapy创建了一个易于使用的Python API,使用统一的高级接口从许多来源下载市场数据,包括Quandl、彭博、雅虎、谷歌等。 |  |  |
| [Investpy](https://github.com/alvarobartt/investpy) | 用Python从Investing.com提取金融数据 |  |  |
| [Fundamental Analysis Data](https://github.com/JerBouma/FundamentalAnalysis) | 完整的基本面分析软件包能够收集20年的公司简介、财务报表、比率和20,000多家公司的股票数据。 |  |  |
| [Wallstreet](https://github.com/mcdallas/wallstreet) | 华尔街。实时股票和期权工具 |  |  |
### 加密货币
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Cryptofeed](https://github.com/bmoscon/cryptofeed) | 使用Asyncio的加密货币交易所Websocket数据源处理程序 |  |  |
| [Gekko-Datasets](https://github.com/xFFFFF/Gekko-Datasets) | Gekko交易机器人数据集转储。下载和使用SQLite格式的历史文件。 |  |  |
| [CryptoInscriber](https://github.com/Optixal/CryptoInscriber) | 一个实时的加密货币历史交易数据图谱。从任何加密货币交易所下载实时历史交易数据。 |  |  |
[Crypto Lake](https://github.com/crypto-lake/lake-api) | 加密货币的高频订单簿和交易数据
|  |  |
## 数据科学
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [TensorFlow](https://github.com/tensorflow/tensorflow) | Python中科学计算的基本算法 |  |  |
| [Pytorch](https://github.com/pytorch/pytorch) | Python中的张量和动态神经网络具有强大的GPU加速功能 |  |  |
| [Keras](https://github.com/keras-team/keras) | 最具用户友好性的Python中的人类深度学习 |  |  |
| [Scikit-learn](https://github.com/scikit-learn/scikit-learn) | Python中的机器学习 |  |  |
| [Pandas](https://github.com/pandas-dev/pandas) | 灵活而强大的Python数据分析/操作库,提供类似于R data.frame对象的标记数据结构、统计函数以及更多。 |  |  |
| [Numpy](https://github.com/numpy/numpy) | 用Python进行科学计算的基本包 |  |  |
| [Scipy](https://github.com/scipy/scipy) | Python中科学计算的基本算法 |  |  |
| [PyMC](https://github.com/pymc-devs/pymc) | Python中的概率编程。用Aesara进行贝叶斯建模和概率机器学习 |  |  |
| [Cvxpy](https://github.com/cvxpy/cvxpy) | 一种用于凸优化问题的Python嵌入式建模语言。 |  |  |
## 数据库
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Marketstore](https://github.com/alpacahq/marketstore) | 金融时序数据的DataFrame服务器 |  |  |
| [Tectonicdb](https://github.com/0b01/tectonicdb) | Tectonicdb是一个快速、高度压缩的独立数据库和流媒体协议,用于订单簿上的点子。 |  |  |
| [ArcticDB (Man Group)](https://github.com/man-group/arcticdb) | 用于时间序列和tick数据的高性能数据存储 |  |  |
## 图形计算
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Ray](https://github.com/ray-project/ray) | 一个开源框架,为构建分布式应用提供了一个简单、通用的API。 |  |  |
| [Dask](https://github.com/dask/dask) | 在Python中使用类似Pandas的API进行任务调度的并行计算 |  |  |
| [Incremental (JaneStreet)](https://github.com/janestreet/incremental) | Incremental是一个库,它为你提供了一种建立复杂计算的方法,可以根据输入的变化进行有效的更新,其灵感来自Umut Acar等人关于自我调整计算的工作。Incremental在许多应用中都很有用 |  |  |
| [Man MDF](https://github.com/man-group/mdf) | 用于Python的数据流编程工具包 |  |  |
| [GraphKit](https://github.com/yahoo/graphkit) | 一个轻量级的Python模块,用于创建和运行计算的有序图。 |  |  |
| [Tributary](https://github.com/timkpaine/tributary) | 在Python中流化反应式和数据流图 |  |  |
## 机器学习
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [QLib (Microsoft)](https://github.com/microsoft/qlib) | Qlib是一个以人工智能为导向的量化投资平台,旨在实现人工智能技术在量化投资中的潜力,授权研究,并创造价值。通过Qlib,你可以轻松尝试你的想法,创造更好的量化投资策略。越来越多的SOTA量化研究作品/论文在Qlib中发布。 |  |  |
| [FinRL](https://github.com/AI4Finance-Foundation/FinRL) | FinRL是第一个开源框架,展示了在量化金融中应用深度强化学习的巨大潜力。 |  |  |
| [MlFinLab (Hudson & Thames)](https://github.com/hudson-and-thames/mlfinlab) | MlFinLab通过提供可重复的、可解释的和易于使用的工具,帮助那些希望利用机器学习的力量的投资组合经理和交易者。 |  |  |
| [TradingGym](https://github.com/Yvictor/TradingGym) | 交易和回测环境,用于训练强化学习代理或简单的规则基础算法。 |  |  |
| [Stock Trading Bot using Deep Q-Learning](https://github.com/pskrunner14/trading-bot) | 使用深度Q-学习的股票交易机器人 |  |  |
## 时间序列分析
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [Facebook Prophet](https://github.com/facebook/prophet) | 对具有线性或非线性增长的多季节性的时间序列数据产生高质量的预测的工具。 |  |  |
| [statsmodels](https://github.com/statsmodels/statsmodels) | Python模块,允许用户探索数据,估计统计模型,并进行统计测试。 |  |  |
| [tsfresh](https://github.com/blue-yonder/tsfresh) | 从时间序列中自动提取相关特征。 |  |  |
| [pmdarima](https://github.com/alkaline-ml/pmdarima) | 一个统计库,旨在填补Python时间序列分析能力的空白,包括相当于R的auto.arima函数。 |  |  |
## 视觉化
| 存储库 | 描述 | 明星 | 使用方法 |
|------------|-------------|-------|-----------|
| [D-Tale (Man Group)](https://github.com/man-group/dtale) | D-Tale是Flask后端和React前端的结合,为你带来查看和分析Pandas数据结构的简单方法。 |  |  |
| [mplfinance](https://github.com/matplotlib/mplfinance) | 使用Matplotlib实现金融市场数据可视化 |  |  |
| [btplotting](https://github.com/happydasch/btplotting) | btplotting为回测、优化结果和backtrader的实时数据提供绘图。 |  |  |
# 战略
*40+篇描述原始系统交易策略的学术论文列表。每种策略按其资产类别分类,并按夏普比率降序排列。*
👉策略现在托管在 [这里](https://paperswithbacktest.com).
上一个策略列表:
## 债券、商品、货币、股票
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|-------------|--------------|------------|-------------|----------------|--------|
| 时间序列动量效应 | `0.576` | `20.5%` | `月度` | [QuantConnect](./static/strategies/time-series-momentum-effect.py) | [纸张](https://pages.stern.nyu.edu/~lpederse/papers/TimeSeriesMomentum.pdf) |
| 利用期货进行短期反转 | `-0.05` | `12.3%` | `每周` | [QuantConnect](./static/strategies/asset-class-momentum-rotational-system.py) | [纸张](https://ideas.repec.org/a/eee/jbfina/v28y2004i6p1337-1361.html) |
## 债券、商品、股票、REITs
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 资产类别的趋势跟踪 | `0.502` | `10.4%` | `月度` | [QuantConnect](./static/strategies/asset-class-trend-following.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=962461) |
| 动量资产配置策略 | `0.321` | `11%` | `月度` | [QuantConnect](./static/strategies/asset-class-trend-following.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1585517) |
## 债券、股票
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 成对切换 | `0.691` | `9.5%` | `季度` | [QuantConnect](./static/strategies/paired-switching.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1917044) |
| FED模式 | `0.369` | `14.3%` | `月度` | [QuantConnect](./static/strategies/fed-model.py) | [纸张](https://www.researchgate.net/publication/228267011_The_FED_Model_and_Expected_Asset_Returns) |
## 债券、股票、REITs
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 各类资产的价值和动量因素 | `0.155` | `9.8%` | `月度` | [QuantConnect](./static/strategies/value-and-momentum-factors-across-asset-classes.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1079975) |
## 商品
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 商品中的偏度效应 | `0.482` | `17.7%` | `月度` | [QuantConnect](./static/strategies/skewness-effect-in-commodities.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2671165) |
| 商品期货的收益不对称效应 | `0.239` | `13.4%` | `月度` | [QuantConnect](./static/strategies/return-asymmetry-effect-in-commodity-futures.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3918896) |
| 商品的动量效应 | `0.14` | `20.3%` | `月度` | [QuantConnect](./static/strategies/momentum-effect-in-commodities.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=702281) |
| 商品的期限结构效应 | `0.128` | `23.1%` | `月度` | [QuantConnect](./static/strategies/term-structure-effect-in-commodities.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1127213) |
| 交易WTI/BRENT价差 | `-0.199` | `11.6%` | `每日` | [QuantConnect](./static/strategies/trading-wti-brent-spread.py) | [纸张](https://link.springer.com/article/10.1057/jdhf.2009.24) |
## 加密货币
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 比特币的隔夜季节性 | `0.892` | `20.8%` | `日内交易` | [QuantConnect](./static/strategies/intraday-seasonality-in-bitcoin.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4081000) |
| 加密货币的再平衡溢价 | `0.698` | `27.5%` | `每日` | [QuantConnect](./static/strategies/rebalancing-premium-in-cryptocurrencies.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3982120) |
## 货币
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 外汇套利交易 | `0.254` | `7.8%` | `月度` | [QuantConnect](./static/strategies/fx-carry-trade.py) | [纸张](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
| 美元套利交易 | `0.113` | `5.8%` | `月度` | [QuantConnect](./static/strategies/dollar-carry-trade.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1541230) |
| 货币动量因素 | `-0.01` | `6.7%` | `月度` | [QuantConnect](./static/strategies/currency-momentum-factor.py) | [纸张](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
| 货币价值因素--PPP战略 | `-0.103` | `5%` | `季度` | [QuantConnect](./static/strategies/currency-value-factor-ppp-strategy.py) | [纸张](http://globalmarkets.db.com/new/docs/dbCurrencyReturns_March2009.pdf) |
## 股票
| 标题 | 夏普比率 | 挥发性 | 重新平衡 | 实施 | 来源 |
|--------------|--------------|------------|-------------|----------------|--------|
| 资产增长效应 | `0.835` | `10.2%` | `每年一次` | [QuantConnect](./static/strategies/asset-growth-effect.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1335524) |
| 股票的短期反转效应 | `0.816` | `21.4%` | `每周` | [QuantConnect](./static/strategies/short-term-reversal-in-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1605049) |
| 盈利期间的逆转-公告 | `0.785` | `25.7%` | `每日` | [QuantConnect](./static/strategies/reversal-during-earnings-announcements.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2275982) |
| 规模因素--小市值股票溢价 | `0.747` | `11.1%` | `每年一次` | [QuantConnect](./static/strategies/small-capitalization-stocks-premium-anomaly.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3177539) |
| 股票中的低波动因素效应 | `0.717` | `11.5%` | `月度` | [QuantConnect](./static/strategies/low-volatility-factor-effect-in-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=980865) |
| 如何使用公司文件的词汇密度 | `0.688` | `10.4%` | `月度` | [QuantConnect](./static/strategies/how-to-use-lexical-density-of-company-filings.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3921091) |
| 波动性风险溢价效应 | `0.637` | `13.2%` | `月度` | [QuantConnect](./static/strategies/volatility-risk-premium-effect.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=189840) |
| 与股票的配对交易 | `0.634` | `8.5%` | `每日` | [QuantConnect](./static/strategies/pairs-trading-with-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=141615) |
| 原油预示着股票收益 | `0.599` | `11.5%` | `月度` | [QuantConnect](./static/strategies/crude-oil-predicts-equity-returns.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=460500) |
| 对赌股票中的贝塔系数 | `0.594` | `18.9%` | `月度` | [QuantConnect](./static/strategies/betting-against-beta-factor-in-stocks.py) | [纸张](https://pages.stern.nyu.edu/~lpederse/papers/BettingAgainstBeta.pdf) |
| 股票中的趋势跟踪效应 | `0.569` | `15.2%` | `每日` | [QuantConnect](./static/strategies/trend-following-effect-in-stocks.py) | [纸张](https://www.cis.upenn.edu/~mkearns/finread/trend.pdf) |
| ESG因子动量策略 | `0.559` | `21.8%` | `月度` | [QuantConnect](./static/strategies/esg-factor-momentum-strategy.py) | [纸张](https://www.semanticscholar.org/paper/Can-ESG-Add-Alpha-An-Analysis-of-ESG-Tilt-and-Nagy-Kassam/64f77da4f8ce5906a73ffe4e9eec7c49c0960acc) |
| 价值(账面价值)因素 | `0.526` | `11.9%` | `月度` | [QuantConnect](./static/strategies/value-book-to-market-factor.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2595747) |
| 足球俱乐部的股票套利 | `0.515` | `14.2%` | `每日` | [QuantConnect](./static/strategies/soccer-clubs-stocks-arbitrage.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1343685) |
| 合成贷款利率预示着随后的市场回报 | `0.494` | `13.7%` | `每日` | [QuantConnect](./static/strategies/synthetic-lending-rates-predict-subsequent-market-return.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3976307) |
| 期权到期周效应 | `0.452` | `5%` | `每周` | [QuantConnect](./static/strategies/option-expiration-week-effect.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1571786) |
| 分散交易 | `0.432` | `8.1%` | `月度` | [QuantConnect](./static/strategies/dispersion-trading.py) | [纸张](https://www.academia.edu/16327015/EQUILIBRIUM_INDEX_AND_SINGLE_STOCK_VOLATILITY_RISK_PREMIA) |
| 共同基金回报的势头 | `0.414` | `13.6%` | `季度` | [QuantConnect](./static/strategies/momentum-in-mutual-fund-returns.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1462408) |
| 扇形动量--旋转系统 | `0.401` | `14.1%` | `月度` | [QuantConnect](./static/strategies/sector-momentum-rotational-system.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1585517) |
| 结合智能因素的势头和市场组合 | `0.388` | `8.2%` | `月度` | [QuantConnect](./static/strategies/combining-smart-factors-momentum-and-market-portfolio.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3745517) |
| 股票的动量和反转与波动效应的结合 | `0.375` | `17%` | `月度` | [QuantConnect](./static/strategies/momentum-and-reversal-combined-with-volatility-effect-in-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1679464) |
| 市场情绪和一夜之间的反常现象 | `0.369` | `3.6%` | `每日` | [QuantConnect](./static/strategies/market-sentiment-and-an-overnight-anomaly.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3829582) |
| 一月的晴雨表 | `0.365` | `7.4%` | `月度` | [QuantConnect](./static/strategies/january-barometer.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1436516) |
| 研发支出和股票收益 | `0.354` | `8.1%` | `每年一次` | [QuantConnect](./static/strategies/rd-expenditures-and-stock-returns.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=227564) |
| 价值因素 - 国家内部的CAPE效应 | `0.351` | `20.2%` | `每年一次` | [QuantConnect](./static/strategies/value-factor-effect-within-countries.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2129474) |
| 股票收益横截面的12个月周期 | `0.34` | `43.7%` | `月度` | [QuantConnect](./static/strategies/12-month-cycle-in-cross-section-of-stocks-returns.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=687022) |
| 股票指数的月度转折 | `0.305` | `7.2%` | `每日` | [QuantConnect](./static/strategies/turn-of-the-month-in-equity-indexes.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=917884) |
| 发薪日反常现象 | `0.269` | `3.8%` | `每日` | [QuantConnect](./static/strategies/payday-anomaly.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3257064) |
| 利用国家ETF进行对价交易 | `0.257` | `5.7%` | `每日` | [QuantConnect](./static/strategies/pairs-trading-with-country-etfs.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1958546) |
| 剩余动量系数 | `0.24` | `9.7%` | `月度` | [QuantConnect](./static/strategies/residual-momentum-factor.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2319861) |
| 盈利公告溢价 | `0.192` | `3.7%` | `月度` | [QuantConnect](./static/strategies/earnings-announcement-premium.py) | [纸张](https://www.nber.org/system/files/working_papers/w13090/w13090.pdf) |
| 股票内部的ROA效应 | `0.155` | `8.7%` | `月度` | [QuantConnect](./static/strategies/roa-effect-within-stocks.py) | [纸张](https://static1.squarespace.com/static/5e6033a4ea02d801f37e15bb/t/5f61583e88f43b7d5b7196b5/1600215105801/Chen_Zhang_JF.pdf) |
| 股票的52周高点效应 | `0.153` | `19%` | `月度` | [QuantConnect](./static/strategies/52-weeks-high-effect-in-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1787378) |
| 结合基本面FSCORE和股票短期逆转的情况 | `0.153` | `17.6%` | `月度` | [QuantConnect](./static/strategies/combining-fundamental-fscore-and-equity-short-term-reversals.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3097420) |
| 对抗国际股票中的贝塔系数的赌注 | `0.142` | `9.1%` | `月度` | [QuantConnect](./static/strategies/betting-against-beta-factor-in-country-equity-indexes.py) | [纸张](https://pages.stern.nyu.edu/~lpederse/papers/BettingAgainstBeta.pdf) |
| 一贯的动力策略 | `0.128` | `28.8%` | `6个月` | [QuantConnect](./static/strategies/consistent-momentum-strategy.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2652592) |
| 空头利息效应--多空版本 | `0.079` | `6.6%` | `月度` | [QuantConnect](./static/strategies/short-interest-effect-long-short-version.py) | [纸张](https://www.semanticscholar.org/paper/Why-Do-Short-Interest-Levels-Predict-Stock-Returns-Boehmer-Erturk/06418ef437dc7156229532a97d0f8392373eb297?p2df) |
| 动量因素与资产增长效应相结合 | `0.058` | `25.1%` | `月度` | [QuantConnect](./static/strategies/momentum-factor-combined-with-asset-growth-effect.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1684767) |
| 股票中的动量因素效应 | `-0.008` | `21.8%` | `月度` | [QuantConnect](./static/strategies/momentum-factor-effect-in-stocks.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2435323) |
| 动量因素和风格轮换效应 | `-0.056` | `10%` | `月度` | [QuantConnect](./static/strategies/momentum-factor-and-style-rotation-effect.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1276815) |
| 盈利公告与股票回购的结合 | `-0.16` | `0.1%` | `每日` | [QuantConnect](./static/strategies/earnings-announcements-combined-with-stock-repurchases.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2589966) |
| 盈利质量因素 | `-0.18` | `28.7%` | `每年一次` | [QuantConnect](./static/strategies/earnings-quality-factor.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2179247) |
| 应计项目的异常情况 | `-0.272` | `13.7%` | `每年一次` | [QuantConnect](./static/strategies/accrual-anomaly.py) | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=546108) |
| ESG、价格动量和随机优化 | `N/A` | `N/A` | `月度` | | [纸张](https://quantpedia.com/strategies/esg-price-momentum-and-stochastic-optimization/) |
| 公司申报和股票回报的正相似性 | `N/A` | `N/A` | `月度` | | [纸张](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3690461) |
# 书籍
为量化交易者提供的55本书的综合清单。
## 初学者
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [A Beginner’s Guide to the Stock Market: Everything You Need to Start Making Money Today - Matthew R. Kratter](https://amzn.to/3QN2VdU) |  |  |
| [How to Day Trade for a Living: A Beginner’s Guide to Trading Tools and Tactics, Money Management, Discipline and Trading Psychology - Andrew Aziz](https://amzn.to/3bmehFv) |  |  |
| [The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns - John C. Bogle](https://amzn.to/3A4mgkR) |  |  |
| [Investing QuickStart Guide: The Simplified Beginner’s Guide to Successfully Navigating the Stock Market, Growing Your Wealth & Creating a Secure Financial Future - Ted D. Snow](https://amzn.to/3A5aRkX) |  |  |
| [Day Trading QuickStart Guide: The Simplified Beginner’s Guide to Winning Trade Plans, Conquering the Markets, and Becoming a Successful Day Trader - Troy Noonan](https://amzn.to/3HPZijw) |  |  |
| [Introduction To Algo Trading: How Retail Traders Can Successfully Compete With Professional Traders - Kevin J Davey](https://amzn.to/39Tf7JC) |  |  |
| [Algorithmic Trading and DMA: An introduction to direct access trading strategies - Barry Johnson](https://amzn.to/3xYb0UN) |  |  |
## 传记
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [My Life as a Quant: Reflections on Physics and Finance - Emanuel Derman](https://amzn.to/3A8KudR) |  |  |
| [How I Became a Quant: Insights from 25 of Wall Street’s Elite: - Barry Schachter](https://amzn.to/3Alf8kz) |  |  |
## 编码
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [Python for Finance: Mastering Data-Driven Finance - Yves Hilpisch](https://amzn.to/3NhkTlP) |  |  |
| [Trading Evolved: Anyone can Build Killer Trading Strategies in Python - Andreas F. Clenow](https://amzn.to/3A0jcGB) |  |  |
| [Python for Algorithmic Trading: From Idea to Cloud Deployment - Yves Hilpisch](https://amzn.to/3bpkd0C) |  |  |
| [Algorithmic Trading with Python: Quantitative Methods and Strategy Development - Chris Conlan](https://amzn.to/3u3cxYo) |  |  |
| [Learn Algorithmic Trading: Build and deploy algorithmic trading systems and strategies using Python and advanced data analysis - Sebastien Donadio](https://amzn.to/3NqNghA) |  |  |
## 隐蔽性
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [The Bitcoin Standard: The Decentralized Alternative to Central Banking - Saifedean Ammous](https://amzn.to/3QMJgec) |  |  |
| [Bitcoin Billionaires: A True Story of Genius, Betrayal, and Redemption - Ben Mezrich](https://amzn.to/39SkdWt) |  |  |
| [Mastering Bitcoin: Programming the Open Blockchain - Andreas M. Antonopoulos](https://amzn.to/3NniZ3p) |  |  |
| [Why Buy Bitcoin: Investing Today in the Money of Tomorrow - Andy Edstrom](https://amzn.to/3OMcKqZ) |  |  |
## 一般
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [The Intelligent Investor: The Definitive Book on Value Investing - Benjamin Graham, Jason Zweig](https://www.amazon.fr/gp/product/0060555661/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0060555661&linkId=aba73910e4e3873b6cc8364487662bd6) |  |  |
| [How I Invest My Money: Finance experts reveal how they save, spend, and invest - Joshua Brown, Brian Portnoy](https://amzn.to/3A4rsoU) |  |  |
| [Naked Forex: High-Probability Techniques for Trading Without Indicators - Alex Nekritin](https://amzn.to/3NkrAUj) |  |  |
| [The Four Pillars of Investing: Lessons for Building a Winning Portfolio - William J. Bernstein](https://www.amazon.fr/gp/product/B0041842TW/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=B0041842TW&linkId=d9bc2fec4f3faa41ca4f24aed3c72122) |  |  |
| [Option Volatility and Pricing: Advanced Trading Strategies and Techniques, 2nd Edition - Sheldon Natenberg](https://amzn.to/3btOxXL) |  |  |
| [The Art and Science of Technical Analysis: Market Structure, Price Action, and Trading Strategies - Adam Grimes](https://www.amazon.fr/gp/product/1118115120/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118115120&linkId=d5dc1f0e6727b2663d2186a110a31ad0) |  |  |
| [The New Trading for a Living: Psychology, Discipline, Trading Tools and Systems, Risk Control, Trade Management (Wiley Trading) - Alexander Elder](https://www.amazon.fr/gp/product/1118467450/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118467450&linkId=67ee502653bc52a5240ced9fc88eb76d) |  |  |
| [Building Winning Algorithmic Trading Systems: A Trader’s Journey From Data Mining to Monte Carlo Simulation to Live Trading (Wiley Trading) - Kevin J Davey](https://amzn.to/39QnsxA) |  |  |
| [Systematic Trading: A unique new method for designing trading and investing systems - Robert Carver](https://www.amazon.fr/gp/product/0857194453/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0857194453&linkId=32d8bffc32c01041cde066bacab76c04) |  |  |
| [Quantitative Momentum: A Practitioner’s Guide to Building a Momentum-Based Stock Selection System (Wiley Finance) - Wesley R. Gray, Jack R. Vogel](https://www.amazon.fr/gp/product/111923719X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=111923719X&linkId=b825cb65462a4a9254af3b7dc5328131) |  |  |
| [Algorithmic Trading: Winning Strategies and Their Rationale - Ernest P. Chan](https://amzn.to/3xWi8kd) |  |  |
| [Leveraged Trading: A professional approach to trading FX, stocks on margin, CFDs, spread bets and futures for all traders - Robert Carver](https://amzn.to/3Nhl6p7) |  |  |
| [Trading Systems: A New Approach to System Development and Portfolio Optimisation - Emilio Tomasini, Urban Jaekle](https://www.amazon.fr/gp/product/1905641796/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1905641796&linkId=61e6634242c497498338f73641ce0a80) |  |  |
| [Trading and Exchanges: Market Microstructure for Practitioners - Larry Harris](https://www.amazon.fr/gp/product/0195144708/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0195144708&linkId=e47e596fc0696cbd624726cce05b4500) |  |  |
| [Trading Systems 2nd edition: A new approach to system development and portfolio optimisation - Emilio Tomasini, Urban Jaekle](https://www.amazon.fr/gp/product/085719755X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=085719755X&linkId=97aa558484a8dc2bf57a5296e7f38cad) |  |  |
| [Machine Trading: Deploying Computer Algorithms to Conquer the Markets - Ernest P. Chan](https://amzn.to/3OIBe4o) |  |  |
| [Quantitative Equity Portfolio Management: An Active Approach to Portfolio Construction and Management (McGraw-Hill Library of Investment and Finance) - Ludwig B Chincarini, Daehwan Kim](https://amzn.to/3yl9u0c) |  |  |
| [Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk - Richard Grinold, Ronald Kahn](https://amzn.to/3xMKaic) |  |  |
| [Quantitative Technical Analysis: An integrated approach to trading system development and trading management - Dr Howard B Bandy](https://www.amazon.fr/gp/product/0979183855/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0979183855&linkId=8ef7bda69477bdccf90f5ac02ee495b0) |  |  |
| [Advances in Active Portfolio Management: New Developments in Quantitative Investing - Richard Grinold, Ronald Kahn](https://amzn.to/3xUTK2z) |  |  |
| [Professional Automated Trading: Theory and Practice - Eugene A. Durenard](https://amzn.to/3yhfOpw) |  |  |
| [Algorithmic Trading and Quantitative Strategies (Chapman and Hall/CRC Financial Mathematics Series) - Raja Velu, Maxence Hardy, Daniel Nehren](https://amzn.to/3xUTQXZ) |  |  |
| [Quantitative Trading: Algorithms, Analytics, Data, Models, Optimization - Xin Guo, Tze Leung Lai, Howard Shek, Samuel Po-Shing Wong](https://www.amazon.fr/gp/product/0367871815/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0367871815&linkId=3f2ba1cbc0e1fe02e255da740423b2fb) |  |  |
## 高频交易
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [Inside the Black Box: A Simple Guide to Quantitative and High Frequency Trading - Rishi K. Narang](https://www.amazon.fr/gp/product/1118362411/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1118362411&linkId=35e02d4e636350366531a5033597a541) |  |  |
| [Algorithmic and High-Frequency Trading (Mathematics, Finance and Risk) - Álvaro Cartea, Sebastian Jaimungal, José Penalva](https://www.amazon.fr/gp/product/1107091144/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1107091144&linkId=64e3ceb66482d8db6827830964b85613) |  |  |
| [The Problem of HFT – Collected Writings on High Frequency Trading & Stock Market Structure Reform - Haim Bodek](https://www.amazon.fr/gp/product/1481978357/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1481978357&linkId=2f3acf998de645990b681e2ac9f0217c) |  |  |
| [An Introduction to High-Frequency Finance - Ramazan Gençay, Michel Dacorogna, Ulrich A. Muller, Olivier Pictet, Richard Olsen](https://www.amazon.fr/gp/product/0122796713/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0122796713&linkId=7e6c098026204f399e45d7fbb803dcca) |  |  |
| [Market Microstructure in Practice - Charles-Albert Lehalle, Sophie Laruelle](https://www.amazon.fr/Market-Microstructure-Practice-Sophie-Laruelle/dp/9813231122) |  |  |
| [The Financial Mathematics of Market Liquidity - Olivier Gueant](https://www.amazon.com/Financial-Mathematics-Market-Liquidity-Execution/dp/1498725473) |  |  |
| [High-Frequency Trading - Maureen O’Hara, David Easley, Marcos M López de Prado](https://www.amazon.fr/gp/product/178272009X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=178272009X&linkId=082f861ff6bbe4cca4ef7ccbe620a2c4) |  |  |
## 机器学习
| 标题 | 评论 | 评价 |
|----------|---------|--------|
| [Dark Pools: The rise of A.I. trading machines and the looming threat to Wall Street - Scott Patterson](https://www.amazon.fr/gp/product/0307887189/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0307887189&linkId=2572cae24ed7de0b279580312daf0f03) |  |  |
| [Advances in Financial Machine Learning - Marcos Lopez de Prado](https://www.amazon.fr/gp/product/1119482089/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1119482089&linkId=7eff4d3f3d9f2d00d05032f726386e53) |  |  |
| [Machine Learning for Algorithmic Trading: Predictive models to extract signals from market and alternative data for systematic trading strategies with Python, 2nd Edition - Stefan Jansen](https://www.amazon.fr/gp/product/1839217715/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1839217715&linkId=80e3e93e1b6027596858ed0f1fbf10c2) |  |  |
| [Machine Learning for Asset Managers (Elements in Quantitative Finance) - Marcos M López de Prado](https://www.amazon.fr/gp/product/1108792898/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1108792898&linkId=8eb7e3c369d38b36df8dfecf05a622db) |  |  |
| [Machine Learning in Finance: From Theory to Practice - Matthew F. Dixon, Igor Halperin, Paul Bilokon](https://www.amazon.fr/gp/product/3030410676/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=3030410676&linkId=5f5f1df6be62ae96ef7a0c536c3ecdb4) |  |  |
| [Artificial Intelligence in Finance: A Python-Based Guide - Yves Hilpisch](https://www.amazon.fr/gp/product/1492055433/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=1492055433&linkId=7c20249be4d35badb127d6a5423fc495) |  |  |
| [Algorithmic Trading Methods: Applications Using Advanced Statistics, Optimization, and Machine Learning Techniques - Robert Kissell](https://www.amazon.fr/gp/product/0128156309/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=darchimbaud-21&creative=6746&linkCode=as2&creativeASIN=0128156309&linkId=0a197c0b547a0ee63ccd19389bb42edd) |  |  |
# 视频
| 标题 | 喜欢 |
|--------------------------------------------------------------------|-------|
| [Krish Naik - Machine learning tutorials and their Application in Stock Prediction](https://www.youtube.com/watch?v=H6du_pfuznE) |  |
| [QuantInsti Youtube - webinars about Machine Learning for trading](https://www.youtube.com/user/quantinsti/search?query=machine+learning) |  |
| [Siraj Raval - Videos about stock market prediction using Deep Learning](https://www.youtube.com/channel/UCWN3xxRkmTPmbKwht9FuE5A/search?query=trading) |  |
| [Quantopian - Webinars about Machine Learning for trading](https://www.youtube.com/channel/UC606MUq45P3zFLa4VGKbxsg/search?query=machine+learning) |  |
| [Sentdex - Machine Learning for Forex and Stock analysis and algorithmic trading](https://www.youtube.com/watch?v=v_L9jR8P-54&list=PLQVvvaa0QuDe6ZBtkCNWNUbdaBo2vA4RO) |  |
| [QuantNews - Machine Learning for Algorithmic Trading 3 part series](https://www.youtube.com/playlist?list=PLHJACfjILJ-91qkw5YC83S6COKGscctzz) |  |
| [Sentdex - Python programming for Finance (a few videos including Machine Learning)](https://www.youtube.com/watch?v=Z-5wNWgRJpk&index=9&list=PLQVvvaa0QuDcOdF96TBtRtuQksErCEBYZ) |  |
| [Chat with Traders EP042 - Machine learning for algorithmic trading with Bert Mouler](https://www.youtube.com/watch?v=i8FNO8r7PaE) |  |
| [Tucker Balch - Applying Deep Reinforcement Learning to Trading](https://www.youtube.com/watch?v=Pka0DC_P17k) |  |
| [Ernie Chan - Machine Learning for Quantitative Trading Webinar](https://www.youtube.com/watch?v=72aEDjwGMr8&t=1023s) |  |
| [Chat with Traders EP147 - Detective work leading to viable trading strategies with Tom Starke](https://www.youtube.com/watch?v=JjXw9Mda7eY) |  |
| [Chat with Traders EP142 - Algo trader using automation to bypass human flaws with Bert Mouler](https://www.youtube.com/watch?v=ofL66mh6Tw0) |  |
| [Master Thesis presentation, Uni of Essex - Analyzing the Limit Order Book, A Deep Learning Approach](https://www.youtube.com/watch?v=qxSh2VFmRGw) |  |
| [Howard Bandy - Machine Learning Trading System Development Webinar](https://www.youtube.com/watch?v=v729evhMpYk&t=1s) |  |
| [Chat With Traders EP131 - Trading strategies, powered by machine learning with Morgan Slade](https://www.youtube.com/watch?v=EbWbeYu8zwg) |  |
| [Chat with Traders Quantopian 5 - Good Uses of Machine Learning in Finance with Max Margenot](https://www.youtube.com/watch?v=Zj5sXWv9SDM) |  |
| [Hitoshi Harada, CTO at Alpaca - Deep Learning in Finance Talk](https://www.youtube.com/watch?v=FoQKCeDuPiY) |  |
| [Better System Trader EP028 - David Aronson shares research into indicators that identify Bull and Bear markets.](https://www.youtube.com/watch?v=Q4rV0Y9NokI) |  |
| [Prediction Machines - Deep Learning with Python in Finance Talk](https://www.youtube.com/watch?v=xvm-M-R2fZY) |  |
| [Better System Trader EP064 - Cryptocurrencies and Machine Learning with Bert Mouler](https://www.youtube.com/watch?v=YgRTd4nLJoU) |  |
| [Better System Trader EP023 - Portfolio manager Michael Himmel talks AI and machine learning in trading](https://www.youtube.com/watch?v=9tZjeyhfG0g) |  |
| [Better System Trader EP082 - Machine Learning With Kris Longmore](https://www.youtube.com/watch?v=0syNgsd635M) |  |
# 博客
| 标题 |
|--------------------------------------------------------------------|
| [AAA Quants, Tom Starke Blog](http://aaaquants.com/category/blog/) |
| [AI & Systematic Trading](https://blog.paperswithbacktest.com/) |
| [Blackarbs blog](http://www.blackarbs.com/blog/) |
| [Hardikp, Hardik Patel blog](https://www.hardikp.com/) |
| [Max Dama on Automated Trading](https://bit.ly/3wVZbh9) |
| [Medallion.Club on Systematic Trading (FR)](https://medallion.club/trading-algorithmique-quantitatif-systematique/) |
| [Proof Engineering: The Algorithmic Trading Platform](https://bit.ly/3lX7zYN) |
| [Quantsportal, Jacques Joubert's Blog](http://www.quantsportal.com/blog-page/) |
| [Quantstart - Machine Learning for Trading articles](https://www.quantstart.com/articles) |
| [RobotWealth, Kris Longmore Blog](https://robotwealth.com/blog/) |
# 课程
| 标题 |
|--------------------------------------------------------------------|
| [AI in Finance](https://cfte.education/) |
| [AI & Systematic Trading](https://paperswithbacktest.com/course) |
| [Algorithmic Trading for Cryptocurrencies in Python](https://github.com/tudorelu/tudorials/tree/master/trading) |
| [Coursera, NYU - Guided Tour of Machine Learning in Finance](https://www.coursera.org/learn/guided-tour-machine-learning-finance) |
| [Coursera, NYU - Fundamentals of Machine Learning in Finance](https://www.coursera.org/learn/fundamentals-machine-learning-in-finance) |
| [Coursera, NYU - Reinforcement Learning in Finance](https://www.coursera.org/learn/reinforcement-learning-in-finance) |
| [Coursera, NYU - Overview of Advanced Methods for Reinforcement Learning in Finance](https://www.coursera.org/learn/advanced-methods-reinforcement-learning-finance) |
| [Hudson and Thames Quantitative Research](https://github.com/hudson-and-thames) |
| [NYU: Overview of Advanced Methods of Reinforcement Learning in Finance](https://www.coursera.org/learn/advanced-methods-reinforcement-learning-finance/home/welcome) |
| [Udacity: Artificial Intelligence for Trading](https://www.udacity.com/course/ai-for-trading--nd880) |
| [Udacity, Georgia Tech - Machine Learning for Trading](https://www.udacity.com/course/machine-learning-for-trading--ud501) |
================================================
FILE: static/strategies/12-month-cycle-in-cross-section-of-stocks-returns.py
================================================
# https://quantpedia.com/strategies/12-month-cycle-in-cross-section-of-stocks-returns/
#
# The top 30% of firms based on their market cap from NYSE and AMEX are part of the investment universe. Every month, stocks are grouped
# into ten portfolios (with an equal number of stocks in each portfolio) according to their performance in one month one year ago. Investors
# go long in stocks from the winner decile and shorts stocks from the loser decile. The portfolio is equally weighted and rebalanced every month.
#
# QC implementation changes:
# - Universe consists of top 3000 US stock by market cap from NYSE, AMEX and NASDAQ.
# - Portfolio is value weighted.
from AlgorithmImports import *
class Month12CycleinCrossSectionofStocksReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.coarse_count = 500
# Monthly close data.
self.data = {}
self.period = 13
self.weight = {}
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
# Update the rolling window every month.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period)
history = self.History(symbol, self.period*30, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
closes_len = len(closes.keys())
# Find monthly closes.
for index, time_close in enumerate(closes.iteritems()):
# index out of bounds check.
if index + 1 < closes_len:
date_month = time_close[0].date().month
next_date_month = closes.keys()[index + 1].month
# Found last day of month.
if date_month != next_date_month:
self.data[symbol].update(time_close[1])
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and x.CompanyReference.IsREIT != 1 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
else:
top_by_market_cap = fine
# Performance sorting. One month performance, one year ago with market cap data.
performance_market_cap = { x.Symbol : (self.data[x.Symbol].performance(), x.MarketCap) for x in top_by_market_cap if x.Symbol in self.data and self.data[x.Symbol].is_ready()}
long = []
short = []
if len(performance_market_cap) >= 10:
sorted_by_perf = sorted(performance_market_cap.items(), key = lambda x:x[1][0], reverse = True)
decile = int(len(sorted_by_perf) / 10)
long = [x for x in sorted_by_perf[:decile]]
short = [x for x in sorted_by_perf[-decile:]]
total_market_cap_long = sum([x[1][1] for x in long])
for symbol, perf_market_cap in long:
self.weight[symbol] = perf_market_cap[1] / total_market_cap_long
total_market_cap_short = sum([x[1][1] for x in short])
for symbol, perf_market_cap in short:
self.weight[symbol] = perf_market_cap[1] / total_market_cap_short
return [x[0] for x in self.weight.items()]
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.weight:
self.Liquidate(symbol)
for symbol, w in self.weight.items():
self.SetHoldings(symbol, w)
self.weight.clear()
def Selection(self):
self.selection_flag = True
class SymbolData():
def __init__(self, symbol, period):
self.Symbol = symbol
self.Window = RollingWindow[float](period)
def update(self, value):
self.Window.Add(value)
def is_ready(self):
return self.Window.IsReady
# One month performance, one year ago.
def performance(self):
values = [x for x in self.Window]
return (values[-2] / values[-1] - 1)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/52-weeks-high-effect-in-stocks.py
================================================
# https://quantpedia.com/strategies/52-weeks-high-effect-in-stocks/
#
# The investment universe consists of all stocks from NYSE, AMEX and NASDAQ (the research paper used the CRSP
# database for backtesting). The ratio between the current price and 52-week high is calculated for each stock
# at the end of each month (PRILAG i,t = Price i,t / 52-Week High i,t). Every month, the investor then calculates
# the weighted average of ratios (PRILAG i,t) from all firms in each industry (20 industries are used), where the
# weight is the market capitalization of the stock at the end of month t. The winners (losers) are stocks in the
# six industries with the highest (lowest) weighted averages of PRILAGi,t. The investor buys stocks in the winner
# portfolio and shorts stocks in the loser portfolio and holds them for three months. Stocks are weighted equally
# and the portfolio is rebalanced monthly (which means that 1/3 of the portfolio is rebalanced each month).
#
# QC implementation changes:
# - Universe consists of 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from numpy import floor
from AlgorithmImports import *
class Weeks52HighEffectinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
self.period = 12 * 21
# Tranching.
self.holding_period = 3
self.managed_queue = []
# Daily 'high' data.
self.data = {}
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.coarse_count = 500
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
if symbol in self.data:
# Store daily price.
self.data[symbol].update(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
group = {}
for stock in fine:
symbol = stock.Symbol
industry_group_code = stock.AssetClassification.MorningstarIndustryGroupCode
if industry_group_code == 0: continue
# Adding stocks in groups.
if not industry_group_code in group:
group[industry_group_code] = []
max_high = self.data[symbol].maximum()
price = self.data[symbol].get_latest_price()
stock_prilag = (stock, price / max_high)
group[industry_group_code].append(stock_prilag)
top_industries = []
low_industries = []
if len(group) != 0:
# Weighted average of ratios calc.
industry_prilag_weighted_avg = {}
for industry_code in group:
total_market_cap = sum([stock_prilag_data[0].MarketCap for stock_prilag_data in group[industry_code]])
if total_market_cap == 0: continue
industry_prilag_weighted_avg[industry_code] = sum([stock_prilag_data[1] * (stock_prilag_data[0].MarketCap / total_market_cap) for stock_prilag_data in group[industry_code]])
if len(industry_prilag_weighted_avg) != 0:
# Weighted average industry sorting.
sorted_by_weighted_avg = sorted(industry_prilag_weighted_avg.items(), key=lambda x: x[1], reverse = True)
top_industries = [x[0] for x in sorted_by_weighted_avg[:6]]
low_industries = [x[0] for x in sorted_by_weighted_avg[-6:]]
long = []
short = []
for industry_code in top_industries:
for stock_prilag_data in group[industry_code]:
symbol = stock_prilag_data[0].Symbol
long.append(symbol)
for industry_code in low_industries:
for stock_prilag_data in group[industry_code]:
symbol = stock_prilag_data[0].Symbol
short.append(symbol)
long_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(long)
short_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(short)
# symbol/quantity collection
long_symbol_q = [(x, floor(long_w / self.data[x].get_latest_price())) for x in long]
short_symbol_q = [(x, -floor(short_w / self.data[x].get_latest_price())) for x in short]
self.managed_queue.append(RebalanceQueueItem(long_symbol_q + short_symbol_q))
return long + short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
remove_item = None
# Rebalance portfolio
for item in self.managed_queue:
if item.holding_period == self.holding_period:
# Liquidate
for symbol, quantity in item.symbol_q:
self.MarketOrder(symbol, -quantity)
remove_item = item
# Trade execution
if item.holding_period == 0:
open_symbol_q = []
for symbol, quantity in item.symbol_q:
if self.Securities.ContainsKey(symbol) and self.Securities[symbol].IsTradable:
self.MarketOrder(symbol, quantity)
open_symbol_q.append((symbol, quantity))
# Only opened orders will be closed
item.symbol_q = open_symbol_q
item.holding_period += 1
# We need to remove closed part of portfolio after loop. Otherwise it will miss one item in self.managed_queue.
if remove_item:
self.managed_queue.remove(remove_item)
def Selection(self):
self.selection_flag = True
class RebalanceQueueItem():
def __init__(self, symbol_q):
# symbol/quantity collections
self.symbol_q = symbol_q
self.holding_period = 0
class SymbolData():
def __init__(self, symbol, period):
self.Symbol = symbol
self.Price = RollingWindow[float](period)
def update(self, value):
self.Price.Add(value)
def is_ready(self):
return self.Price.IsReady
def maximum(self):
return max([x for x in self.Price])
def get_latest_price(self):
return [x for x in self.Price][0]
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/accrual-anomaly.py
================================================
# https://quantpedia.com/strategies/accrual-anomaly/
#
# The investment universe consists of all stocks on NYSE, AMEX, and NASDAQ. Balance sheet based accruals (the non-cash component of
# earnings) are calculated as: BS_ACC = ( ∆CA – ∆Cash) – ( ∆CL – ∆STD – ∆ITP) – Dep
# Where:
# ∆CA = annual change in current assets
# ∆Cash = change in cash and cash equivalents
# ∆CL = change in current liabilities
# ∆STD = change in debt included in current liabilities
# ∆ITP = change in income taxes payable
# Dep = annual depreciation and amortization expense
# Stocks are then sorted into deciles and investor goes long stocks with the lowest accruals and short stocks with the highest accruals.
# The portfolio is rebalanced yearly during May (after all companies publish their earnings).
from AlgorithmImports import *
class AccrualAnomaly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2006, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.coarse_count = 1000
self.long = []
self.short = []
# Latest accruals data.
self.accrual_data = {}
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
for security in changes.RemovedSecurities:
symbol = security.Symbol
if symbol in self.accrual_data:
del self.accrual_data[symbol]
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
return selected
def FineSelectionFunction(self, fine):
fine = [x for x in fine if (float(x.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths) != 0)
and (float(x.FinancialStatements.BalanceSheet.CashAndCashEquivalents.TwelveMonths) != 0)
and (float(x.FinancialStatements.BalanceSheet.CurrentLiabilities.TwelveMonths) != 0)
and (float(x.FinancialStatements.BalanceSheet.CurrentDebt.TwelveMonths) != 0)
and (float(x.FinancialStatements.BalanceSheet.IncomeTaxPayable.TwelveMonths) != 0)
and (float(x.FinancialStatements.IncomeStatement.DepreciationAndAmortization.TwelveMonths) != 0)]
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
else:
top_by_market_cap = fine
accruals = {}
for stock in top_by_market_cap:
symbol = stock.Symbol
if symbol not in self.accrual_data:
self.accrual_data[symbol] = None
# Accrual calc.
current_accruals_data = AccrualsData(stock.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths, stock.FinancialStatements.BalanceSheet.CashAndCashEquivalents.TwelveMonths,
stock.FinancialStatements.BalanceSheet.CurrentLiabilities.TwelveMonths, stock.FinancialStatements.BalanceSheet.CurrentDebt.TwelveMonths, stock.FinancialStatements.BalanceSheet.IncomeTaxPayable.TwelveMonths,
stock.FinancialStatements.IncomeStatement.DepreciationAndAmortization.TwelveMonths, stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths)
# There is not previous accrual data.
if not self.accrual_data[symbol]:
self.accrual_data[symbol] = current_accruals_data
continue
# Accruals and market cap calc.
acc = self.CalculateAccruals(current_accruals_data, self.accrual_data[symbol])
accruals[symbol] = acc
# Update accruals data.
self.accrual_data[symbol] = current_accruals_data
# Accruals sorting.
sorted_by_accruals = sorted(accruals.items(), key = lambda x: x[1], reverse = True)
decile = int(len(sorted_by_accruals) / 10)
self.long = [x[0] for x in sorted_by_accruals[-decile:]]
self.short = [x[0] for x in sorted_by_accruals[:decile]]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long:
self.Liquidate(symbol)
for symbol in self.long:
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def Selection(self):
if self.Time.month == 4:
self.selection_flag = True
def CalculateAccruals(self, current_accrual_data, prev_accrual_data):
delta_assets = current_accrual_data.CurrentAssets - prev_accrual_data.CurrentAssets
delta_cash = current_accrual_data.CashAndCashEquivalents - prev_accrual_data.CashAndCashEquivalents
delta_liabilities = current_accrual_data.CurrentLiabilities - prev_accrual_data.CurrentLiabilities
delta_debt = current_accrual_data.CurrentDebt - prev_accrual_data.CurrentDebt
delta_tax = current_accrual_data.IncomeTaxPayable - prev_accrual_data.IncomeTaxPayable
dep = current_accrual_data.DepreciationAndAmortization
avg_total = (current_accrual_data.TotalAssets + prev_accrual_data.TotalAssets) / 2
bs_acc = ((delta_assets - delta_cash) - (delta_liabilities - delta_debt - delta_tax) - dep) / avg_total
return bs_acc
class AccrualsData():
def __init__(self, current_assets, cash_and_cash_equivalents, current_liabilities, current_debt, income_tax_payable, depreciation_and_amortization, total_assets):
self.CurrentAssets = current_assets
self.CashAndCashEquivalents = cash_and_cash_equivalents
self.CurrentLiabilities = current_liabilities
self.CurrentDebt = current_debt
self.IncomeTaxPayable = income_tax_payable
self.DepreciationAndAmortization = depreciation_and_amortization
self.TotalAssets = total_assets
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/asset-class-momentum-rotational-system.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/asset-class-momentum-rotational-system/
#
# Use 5 ETFs (SPY - US stocks, EFA - foreign stocks, IEF - bonds, VNQ - REITs, GSG - commodities).
# Pick 3 ETFs with strongest 12 month momentum into your portfolio and weight them equally.
# Hold for 1 month and then rebalance.
class MomentumAssetAllocationStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.data = {}
period = 12 * 21
self.SetWarmUp(period)
self.symbols = ["SPY", "EFA", "IEF", "VNQ", "GSG"]
for symbol in self.symbols:
self.AddEquity(symbol, Resolution.Daily)
self.data[symbol] = self.ROC(symbol, period, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
if self.IsWarmingUp:
return
# monthly rebalance
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
sorted_by_momentum = sorted(
[
x
for x in self.data.items()
if x[1].IsReady and x[0] in data and data[x[0]]
],
key=lambda x: x[1].Current.Value,
reverse=True,
)
count = 3
long = [x[0] for x in sorted_by_momentum][:count]
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
================================================
FILE: static/strategies/asset-class-trend-following.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/asset-class-trend-following/
#
# Use 5 ETFs (SPY - US stocks, EFA - foreign stocks, IEF - bonds, VNQ - REITs,
# GSG - commodities), equal weight the portfolio. Hold asset class ETF only when
# it is over its 10 month Simple Moving Average, otherwise stay in cash.
#
# QC implementation:
# - SMA with period of 210 days is used.
class AssetClassTrendFollowing(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.sma = {}
period = 10 * 21
self.SetWarmUp(period, Resolution.Daily)
self.symbols = ["SPY", "EFA", "IEF", "VNQ", "GSG"]
self.rebalance_flag = False
self.tracked_symbol = None
for symbol in self.symbols:
self.AddEquity(symbol, Resolution.Minute)
self.sma[symbol] = self.SMA(symbol, period, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
# rebalance once a month
if self.Time.month == self.recent_month:
return
if self.Time.hour != 9 and self.Time.minute != 31:
return
self.recent_month = self.Time.month
long = [
symbol
for symbol in self.symbols
if symbol in data
and data[symbol]
and self.sma[symbol].IsReady
and data[symbol].Value > self.sma[symbol].Current.Value
]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
================================================
FILE: static/strategies/asset-growth-effect.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/asset-growth-effect/
#
# The investment universe consists of all non-financial U.S. stocks listed on NYSE, AMEX, and NASDAQ. Stocks are then sorted each year at the end
# of June into ten equal groups based on the percentage change in total assets for the previous year. The investor goes long decile with low asset
# growth firms and short decile with high asset growth firms. The portfolio is weighted equally and rebalanced every year.
#
# QC implementation changes:
# - Top 3000 stocks by market cap are selected from QC stock universe.
class AssetGrowthEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol:Symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.long:list[Symbol] = []
self.short:list[Symbol] = []
self.coarse_count:int = 3000
self.quantile:int = 10
# Latest assets data.
self.total_assets:dict[Symbol, float] = {}
self.selection_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
# Select all stocks in universe.
return [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths > 0 and
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
fine = sorted_by_market_cap[:self.coarse_count]
assets_growth:dict[Symbol, float] = {}
for stock in fine:
symbol = stock.Symbol
if symbol not in self.total_assets:
self.total_assets[symbol] = None
current_assets = stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths
# There is not previous assets data.
if not self.total_assets[symbol]:
self.total_assets[symbol] = current_assets
continue
# Assets growth calc.
assets_growth[symbol] = (current_assets - self.total_assets[symbol]) / self.total_assets[symbol]
# Update data.
self.total_assets[symbol] = current_assets
# Asset growth sorting.
if len(assets_growth) >= self.quantile:
sorted_by_assets_growth = sorted(assets_growth.items(), key = lambda x: x[1], reverse = True)
decile = int(len(sorted_by_assets_growth) / self.quantile)
self.long = [x[0] for x in sorted_by_assets_growth[-decile:]]
self.short = [x[0] for x in sorted_by_assets_growth[:decile]]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long:
self.Liquidate(symbol)
for symbol in self.long:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def Selection(self):
if self.Time.month == 6:
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/betting-against-beta-factor-in-country-equity-indexes.py
================================================
# https://quantpedia.com/strategies/betting-against-beta-factor-in-country-equity-indexes/
#
# The investment universe consists of all country ETFs. The beta for each country is calculated with respect to the MSCI US
# Equity Index using a 1-year rolling window. ETFs are then ranked in ascending order based on their estimated beta. The ranked
# ETFs are assigned to one of two portfolios: low beta and high beta. Securities are weighted by the ranked betas, and the portfolios
# are rebalanced every calendar month. Both portfolios are rescaled to have a beta of one at portfolio formation. The “Betting-Against-Beta”
# is the zero-cost zero-beta portfolio that is long on the low-beta portfolio and that shorts the high-beta portfolio. There are a lot of
# simple modifications (like going long on the bottom beta decile and short on the top beta decile), which could probably improve the strategy’s performance.
import numpy as np
from AlgorithmImports import *
from collections import deque
class BettingAgainstBetaFactorinInternationalEquities(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2002, 2, 1)
self.SetCash(100000)
self.countries = [
"EWA", # iShares MSCI Australia Index ETF
"EWO", # iShares MSCI Austria Investable Mkt Index ETF
"EWK", # iShares MSCI Belgium Investable Market Index ETF
"EWZ", # iShares MSCI Brazil Index ETF
"EWC", # iShares MSCI Canada Index ETF
"FXI", # iShares China Large-Cap ETF
"EWQ", # iShares MSCI France Index ETF
"EWG", # iShares MSCI Germany ETF
"EWH", # iShares MSCI Hong Kong Index ETF
"EWI", # iShares MSCI Italy Index ETF
"EWJ", # iShares MSCI Japan Index ETF
"EWM", # iShares MSCI Malaysia Index ETF
"EWW", # iShares MSCI Mexico Inv. Mt. Idx
"EWN", # iShares MSCI Netherlands Index ETF
"EWS", # iShares MSCI Singapore Index ETF
"EZA", # iShares MSCI South Africe Index ETF
"EWY", # iShares MSCI South Korea ETF
"EWP", # iShares MSCI Spain Index ETF
"EWD", # iShares MSCI Sweden Index ETF
"EWL", # iShares MSCI Switzerland Index ETF
"EWT", # iShares MSCI Taiwan Index ETF
"THD", # iShares MSCI Thailand Index ETF
"EWU", # iShares MSCI United Kingdom Index ETF
]
self.leverage_cap = 5
# Daily price data.
self.data = {}
self.period = 12 * 21
self.symbol = 'SPY'
for symbol in self.countries + [self.symbol]:
data = self.AddEquity(symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(15)
self.data[symbol] = RollingWindow[float](self.period)
self.recent_month = -1
def OnData(self, data):
for symbol in self.data:
symbol_obj = self.Symbol(symbol)
if symbol_obj in data.Keys:
if data[symbol_obj]:
price = data[symbol_obj].Value
if price != 0:
self.data[symbol].Add(price)
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
beta = {}
for symbol in self.countries:
# Data is ready.
if self.data[self.symbol].IsReady and self.data[symbol].IsReady and self.symbol in data and symbol in data:
market_closes = np.array([x for x in self.data[self.symbol]])
asset_closes = np.array([x for x in self.data[symbol]])
market_returns = (market_closes[1:] - market_closes[:-1]) / market_closes[:-1]
asset_returns = (asset_closes[1:] - asset_closes[:-1]) / asset_closes[:-1]
cov = np.cov(asset_returns, market_returns)[0][1]
market_variance = np.var(market_returns)
beta[symbol] = cov / market_variance
weight = {}
if len(beta) != 0:
# Beta diff calc.
beta_median = np.median([x[1] for x in beta.items()])
long_diff = [(x[0], abs(beta_median - x[1])) for x in beta.items() if x[1] < beta_median]
short_diff = [(x[0], abs(beta_median - x[1])) for x in beta.items() if x[1] > beta_median]
# Beta rescale.
long_portfolio_beta = np.mean([beta[x[0]] for x in long_diff])
long_leverage = 1 / long_portfolio_beta
short_portfolio_beta = np.mean([beta[x[0]] for x in short_diff])
short_leverage = 1 / short_portfolio_beta
# Cap long and short leverage.
long_leverage = min(self.leverage_cap, long_leverage)
long_leverage = max(-self.leverage_cap, long_leverage)
short_leverage = min(self.leverage_cap, short_leverage)
short_leverage = max(-self.leverage_cap, short_leverage)
# self.Log(f"long: {long_leverage}; short: {short_leverage}")
total_long_diff = sum([x[1] for x in long_diff])
total_short_diff = sum([x[1] for x in short_diff])
# Beta diff weighting.
weight = {}
for symbol, diff in long_diff:
weight[symbol] = (diff / total_long_diff) * long_leverage
for symbol, diff in short_diff:
weight[symbol] = - (diff / total_short_diff) * short_leverage
# Trade execution.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in weight:
self.Liquidate(symbol)
for symbol, w in weight.items():
self.SetHoldings(symbol, w)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/betting-against-beta-factor-in-stocks.py
================================================
# https://quantpedia.com/strategies/betting-against-beta-factor-in-stocks/
#
# The investment universe consists of all stocks from the CRSP database. The beta for each stock is calculated with respect to the MSCI US Equity Index using a 1-year
# rolling window. Stocks are then ranked in ascending order on the basis of their estimated beta. The ranked stocks are assigned to one of two portfolios: low beta and
# high beta. Securities are weighted by the ranked betas, and portfolios are rebalanced every calendar month. Both portfolios are rescaled to have a beta of one at portfolio
# formation. The “Betting-Against-Beta” is the zero-cost zero-beta portfolio that is long on the low-beta portfolio and short on the high-beta portfolio. There are a lot of
# simple modifications (like going long on the bottom beta decile and short on the top beta decile), which could probably improve the strategy’s performance.
#
# QC implementation changes:
# - The investment universe consists of 1000 most liquid US stocks with price > 5$.
from scipy import stats
from AlgorithmImports import *
import numpy as np
class BettingAgainstBetaFactorinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# Daily price data.
self.data = {}
self.period = 12 * 21
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.data[self.symbol] = RollingWindow[float](self.period)
self.weight = {}
self.long = []
self.short = []
self.long_lvg = 1 # leverage for long portfolio calculated from average beta
self.short_lvg = 1 # leverage for short portfolio calculated from average beta
self.leverage_cap = 2
self.coarse_count = 1000
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(self.leverage_cap*3)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
if symbol in self.data:
# Store daily price.
self.data[symbol].Add(stock.AdjustedPrice)
# Selection once a month.
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5]
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = RollingWindow[float](self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].Add(close)
return [x for x in selected if self.data[x].IsReady]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
# top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
# else:
# top_by_market_cap = fine
beta = {}
if not self.data[self.symbol].IsReady: return []
for stock in fine:
symbol = stock.Symbol
market_closes = np.array([x for x in self.data[self.symbol]])
stock_closes = np.array([x for x in self.data[symbol]])
market_returns = (market_closes[:-1] - market_closes[1:]) / market_closes[1:]
stock_returns = (stock_closes[:-1] - stock_closes[1:]) / stock_closes[1:]
cov = np.cov(stock_returns[::-1], market_returns[::-1])[0][1]
market_variance = np.var(market_returns)
beta[symbol] = cov / market_variance
# beta_, intercept, r_value, p_value, std_err = stats.linregress(market_returns[::-1], stock_returns[::-1])
# beta[symbol] = beta_
if len(beta) >= 10:
# sort by beta
sorted_by_beta = sorted(beta.items(), key = lambda x:x[1], reverse=True)
decile = int(len(sorted_by_beta) / 10)
self.long = [x for x in sorted_by_beta[-decile:]]
self.short = [x for x in sorted_by_beta[:decile]]
# create zero-beta portfolio
long_mean_beta = np.mean([x[1] for x in self.long])
short_mean_beta = np.mean([x[1] for x in self.short])
self.long = [x[0] for x in self.long]
self.short = [x[0] for x in self.short]
self.long_lvg = 1/long_mean_beta
self.short_lvg = 1/short_mean_beta
# cap leverage
if self.long_lvg <= 0:
self.long_lvg = self.leverage_cap
else:
self.long_lvg = min(self.leverage_cap, self.long_lvg)
if self.short_lvg <= 0:
self.short_lvg = self.leverage_cap
else:
self.short_lvg = min(self.leverage_cap, self.short_lvg)
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
long_len = len(self.long)
short_len = len(self.short)
for symbol in self.long:
self.SetHoldings(symbol, (1/long_len)*self.long_lvg)
for symbol in self.short:
self.SetHoldings(symbol, -(1/short_len)*self.short_lvg)
self.long.clear()
self.short.clear()
self.long_lvg = 1
self.short_lvg = 1
def Selection(self):
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/combining-fundamental-fscore-and-equity-short-term-reversals.py
================================================
from AlgorithmImports import *
import numpy as np
def Return(values):
return (values[-1] - values[0]) / values[0]
def Volatility(values):
values = np.array(values)
returns = (values[1:] - values[:-1]) / values[:-1]
return np.std(returns)
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl free data
class QuandlFutures(PythonQuandl):
def __init__(self):
self.ValueColumnName = "settle"
# Quantpedia data
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["settle"] = float(split[1])
data.Value = float(split[1])
return data
# https://quantpedia.com/strategies/combining-fundamental-fscore-and-equity-short-term-reversals/
#
# The investment universe consists of common stocks (share code 10 or 11) listed in NYSE, AMEX, and NASDAQ exchanges.
# Stocks with prices less than $5 at the end of the formation period are excluded.
# The range of FSCORE is from zero to nine points. Each signal is equal to one (zero) point if the signal indicates a positive
# (negative) financial performance. A firm scores one point if it has realized a positive return-on-assets (ROA), a positive
# cash flow from operations, a positive change in ROA, a positive difference between net income from operations (Accrual),
# a decrease in the ratio of long-term debt to total assets, a positive change in the current ratio, no-issuance of new common
# equity, a positive change in gross margin ratio and lastly a positive change in asset turnover ratio. Firstly, construct a quarterly
# FSCORE using the most recently available quarterly financial statement information.
from AlgorithmImports import *
# Monthly reversal data are matched each month with a most recently available quarterly FSCORE. The firm is classified as a fundamentally
# strong firm if the firm’s FSCORE is greater than or equal to seven (7-9), fundamentally middle firm (4-6) and fundamentally weak firm (0-3).
# Secondly, identify the large stocks subset – those in the top 40% of all sample stocks in terms of market capitalization
# at the end of formation month t. After that, stocks are sorted on the past 1-month returns and firm’s most recently available quarterly FSCORE.
# Take a long position in past losers with favorable fundamentals (7-9) and simultaneously a short position in past winners with unfavorable
# fundamentals (0-3). The strategy is equally weighted and rebalanced monthly.
#
# QC implementation changes:
# - Instead of all listed stock, we select 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
class CombiningFSCOREShortTermReversals(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.SetSecurityInitializer(
lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))
)
self.coarse_count = 500
self.long = []
self.short = []
self.stock_data = {}
self.data = {}
self.period = 21
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5]
selected = [
x.Symbol
for x in sorted(
[
x
for x in coarse
if x.HasFundamentalData and x.Market == "usa" and x.Price > 5
],
key=lambda x: x.DollarVolume,
reverse=True,
)[: self.coarse_count]
]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [
x
for x in fine
if (x.EarningReports.BasicAverageShares.ThreeMonths != 0)
and (x.EarningReports.BasicEPS.TwelveMonths != 0)
and (x.ValuationRatios.PERatio != 0)
and (x.OperationRatios.ROA.ThreeMonths != 0)
and (
x.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.ThreeMonths
!= 0
)
and (
x.FinancialStatements.IncomeStatement.NormalizedIncome.ThreeMonths != 0
)
and (x.FinancialStatements.BalanceSheet.LongTermDebt.ThreeMonths != 0)
and (x.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths != 0)
and (x.OperationRatios.CurrentRatio.ThreeMonths != 0)
and (
x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths != 0
)
and (x.OperationRatios.GrossMargin.ThreeMonths != 0)
and (
x.FinancialStatements.IncomeStatement.TotalRevenueAsReported.ThreeMonths
!= 0
)
and (
(x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE")
)
]
# BM sorting
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
length = int((len(sorted_by_market_cap) / 100) * 40)
top_by_market_cap = [x for x in sorted_by_market_cap[:length]]
fine_symbols = [x.Symbol for x in top_by_market_cap]
score_performance = {}
for stock in top_by_market_cap:
symbol = stock.Symbol
if symbol not in self.stock_data:
self.stock_data[symbol] = StockData() # Contains latest data.
roa = stock.OperationRatios.ROA.ThreeMonths
cfo = (
stock.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.ThreeMonths
)
leverage = (
stock.FinancialStatements.BalanceSheet.LongTermDebt.ThreeMonths
/ stock.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths
)
liquidity = stock.OperationRatios.CurrentRatio.ThreeMonths
equity_offering = (
stock.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths
)
gross_margin = stock.OperationRatios.GrossMargin.ThreeMonths
turnover = (
stock.FinancialStatements.IncomeStatement.TotalRevenueAsReported.ThreeMonths
/ stock.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths
)
# Check if data has previous year's data ready.
stock_data = self.stock_data[symbol]
if (
(stock_data.ROA == 0)
or (stock_data.Leverage == 0)
or (stock_data.Liquidity == 0)
or (stock_data.Equity_offering == 0)
or (stock_data.Gross_margin == 0)
or (stock_data.Turnover == 0)
):
stock_data.Update(
roa, leverage, liquidity, equity_offering, gross_margin, turnover
)
continue
score = 0
if roa > 0:
score += 1
if cfo > 0:
score += 1
if roa > stock_data.ROA: # ROA change is positive
score += 1
if cfo > roa:
score += 1
if leverage < stock_data.Leverage:
score += 1
if liquidity > stock_data.Liquidity:
score += 1
if equity_offering < stock_data.Equity_offering:
score += 1
if gross_margin > stock_data.Gross_margin:
score += 1
if turnover > stock_data.Turnover:
score += 1
score_performance[symbol] = (score, self.data[symbol].performance())
# Update new (this year's) data.
stock_data.Update(
roa, leverage, liquidity, equity_offering, gross_margin, turnover
)
# Clear out not updated data.
for symbol in self.stock_data:
if symbol not in fine_symbols:
self.stock_data[symbol] = StockData()
# Performance sorting and F score sorting.
self.long = [
x[0] for x in score_performance.items() if x[1][0] >= 7 and x[1][1] < 0
]
self.short = [
x[0] for x in score_performance.items() if x[1][0] <= 3 and x[1][1] > 0
]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
long_count = len(self.long)
short_count = len(self.short)
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
for symbol in self.long:
self.SetHoldings(symbol, 1 / long_count)
for symbol in self.short:
self.SetHoldings(symbol, -1 / short_count)
self.long.clear()
self.short.clear()
def Selection(self):
self.selection_flag = True
class StockData:
def __init__(self):
self.ROA = 0
self.Leverage = 0
self.Liquidity = 0
self.Equity_offering = 0
self.Gross_margin = 0
self.Turnover = 0
def Update(self, ROA, leverage, liquidity, eq_offering, gross_margin, turnover):
self.ROA = ROA
self.Leverage = leverage
self.Liquidity = liquidity
self.Equity_offering = eq_offering
self.Gross_margin = gross_margin
self.Turnover = turnover
class SymbolData:
def __init__(self, symbol, period):
self.Symbol = symbol
self.Price = RollingWindow[float](period)
def update(self, value):
self.Price.Add(value)
def is_ready(self) -> bool:
return self.Price.IsReady
def performance(self, values_to_skip=0) -> float:
closes = [x for x in self.Price][values_to_skip:]
return closes[0] / closes[-1] - 1
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/combining-smart-factors-momentum-and-market-portfolio.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/combining-smart-factors-momentum-and-market-portfolio/
#
# The investment universe consists of factors from the Alpha Architect’s Factor Investing Data Library (factor for all major investment styles such as Value, Quality, Momentum, Size and Volatility)
# based on the top 1500 US stocks. Firstly construct the fast and slow signals for each factor. The fast signal is the past one-month return, and the slow signal is the past twelve-months return.
# For each type of signal, to obtain the weights, cross-sectionally rank signals’ based on their absolute values. The weight for the individual slow or fast signal is equal to the corresponding rank
# divided by the sum of all ranks and multiplied by the signal’s sign (equations 3 and 4 in the paper). For the dynamically blended strategy (smart factors strategy), each factor has a final weight of
# three-quarters of the weight of fast signal plus one-quarter of the weight of slow signal (equation 12). Nextly, consider the top 1500 US stocks as the market portfolio. The combined smart factors
# and market strategy finds the weights of the market and factor portfolio using past moving averages of the returns. The combined strategy looks back on the past twelve months, and twelve MAs of the
# returns. Suppose the MA for active investing (factor momentum) is larger than MA for market portfolio, then the active investing scores one point. Otherwise, the market portfolio gets one point.
# Therefore, each month, the weight of the factor momentum and market portfolio is determined by the number of “winning” (loosing) moving averages (equations 13 and 14). The strategy is rebalanced monthly.
#
# QC Implementation:
# - IWM etf is used as 'market' portfolio.
import numpy as np
class CombiningSmartFactorsMomentumandMarketPortfolio(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = {
"momentum": "US_EQUAL_DECILE_1500_12_2m_L_S",
"value": "US_EQUAL_DECILE_1500_B_M_L_S",
"quality": "US_EQUAL_DECILE_1500_ROA_L_S",
"size": "US_EQUAL_DECILE_1500_Size_L_S",
"volatility": "US_EQUAL_DECILE_1500_Volatility_L_S",
}
# monthly price data
self.data = {}
self.long_period = 13
self.short_period = 2
self.monthly_returns = {}
self.monthly_returns_period = 12
for symbol, equity_symbol in self.symbols.items():
data = self.AddData(USEquity, equity_symbol, Resolution.Daily)
data.SetLeverage(10)
data.SetFeeModel(CustomFeeModel(self))
self.data[symbol] = RollingWindow[float](self.long_period)
self.market = self.AddEquity("IWM", Resolution.Daily).Symbol
self.data[self.market] = RollingWindow[float](self.short_period)
self.monthly_returns["smart_factors"] = RollingWindow[float](
self.monthly_returns_period
)
self.monthly_returns["market"] = RollingWindow[float](
self.monthly_returns_period
)
self.selection_flag = False
self.Schedule.On(
self.DateRules.MonthStart(self.market),
self.TimeRules.AfterMarketOpen(self.market),
self.Rebalance,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel(self))
security.SetLeverage(5)
def OnData(self, data):
# store factor monthly prices
for symbol, equity_symbol in self.symbols.items():
if equity_symbol in data and data[equity_symbol]:
price = data[equity_symbol].Value
self.data[symbol].Add(price)
# store market prices
if self.market in data and data[self.market]:
market_price = data[self.market].Value
self.data[self.market].Add(market_price)
def Rebalance(self):
slow_momentum = {}
fast_momentum = {}
# calculate both momentum values
for symbol, equity_symbol in self.symbols.items():
if self.data[symbol].IsReady:
slow_momentum[symbol] = (
self.data[symbol][0] / self.data[symbol][self.long_period - 1] - 1
)
fast_momentum[symbol] = self.data[symbol][0] / self.data[symbol][1] - 1
if len(fast_momentum) != 0:
# momentum ranking
total_weight = {}
# weights
rank_sum = sum([x for x in range(1, len(slow_momentum) + 1)])
sorted_by_slow_momentum = sorted(
slow_momentum.items(), key=lambda x: abs(x[1]), reverse=False
)
slow_weight = {}
for i, (symbol, momentum) in enumerate(sorted_by_slow_momentum):
rank = i + 1
slow_weight[symbol] = (rank / rank_sum) * np.sign(momentum)
sorted_by_fast_momentum = sorted(
fast_momentum.items(), key=lambda x: abs(x[1]), reverse=False
)
fast_weight = {}
for i, (symbol, momentum) in enumerate(sorted_by_fast_momentum):
rank = i + 1
fast_weight[symbol] = (rank / rank_sum) * np.sign(momentum)
# total weight
for symbol, equity_symbol in self.symbols.items():
if symbol in slow_momentum and symbol in fast_momentum:
s_weight = slow_weight[symbol]
f_weight = fast_weight[symbol]
total_weight[symbol] = 0.75 * f_weight + 0.25 * s_weight
# retrun calculation for market and smart factors
if self.data[self.market].IsReady:
market_return = self.data[self.market][0] / self.data[self.market][1] - 1
self.monthly_returns["market"].Add(market_return)
# smart factor return calculation
smart_factors_return = 0
for symbol, momentum_1M in fast_momentum.items():
if symbol in total_weight:
w = total_weight[symbol]
symbol_ret = w * momentum_1M
smart_factors_return += symbol_ret
if smart_factors_return != 0:
self.monthly_returns["smart_factors"].Add(smart_factors_return)
score = {}
traded_weight = {}
# calculate 12 SMA's
if (
self.monthly_returns["smart_factors"].IsReady
and self.monthly_returns["market"].IsReady
):
score["smart_factors"] = 0
score["market"] = 0
for sma_period in range(1, 13):
factor_returns = [x for x in self.monthly_returns["smart_factors"]][
:sma_period
]
market_returns = [x for x in self.monthly_returns["market"]][
:sma_period
]
factor_mean_return = np.mean(factor_returns)
market_mean_return = np.mean(market_returns)
if factor_mean_return > market_mean_return:
score["smart_factors"] += 1
else:
score["market"] += 1
total_score = score["market"] + score["smart_factors"]
if total_score != 0:
traded_weight["market"] = score["market"] / total_score
traded_weight["smart_factors"] = (
score["smart_factors"] / total_score
)
# order execution
# market
self.SetHoldings(self.market, traded_weight["market"])
# smart factors
for symbol, equity_symbol in self.symbols.items():
if symbol in total_weight:
w = total_weight[symbol]
self.SetHoldings(
equity_symbol, traded_weight["smart_factors"] * w
)
class USEquity(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/equity/us_ew_decile/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
# File example.
# date;equity
# 1992-01-31;0.98
def Reader(self, config, line, date, isLiveMode):
data = USEquity()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
# Prevent lookahead bias.
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data.Value = float(split[1])
return data
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/consistent-momentum-strategy.py
================================================
# https://quantpedia.com/strategies/consistent-momentum-strategy/
#
# The investment universe consists of stocks listed at NYSE, AMEX, and NASDAQ, whose price data (at least for the past 7 months) are available
# at the CRSP database. The investor creates a zero-investment portfolio at the end of the month t, longing stocks that are in the top decile
# in terms of returns both in the period from t-7 to t-1 and from t-6 to t, while shorting stocks in the bottom decile in both periods (i.e.
# longing consistent winners and shorting consistent losers). The stocks in the portfolio are weighted equally. The holding period is six months,
# with no rebalancing during the period. There is a one-month skip between the formation and holding period.
#
# QC implementation changes:
# - Universe consists of 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from AlgorithmImports import *
class ConsistentMomentumStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.coarse_count = 500
self.long = []
self.short = []
self.data = {}
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.period = 7 * 21
self.months = 0
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Rebalance)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
symbol = security.Symbol
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and x.CompanyReference.IsREIT != 1 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
# top_by_market_cap = [x.Symbol for x in sorted_by_market_cap[:self.coarse_count]]
# else:
# top_by_market_cap = [x.Symbol for x in fine]
top_by_market_cap = [x.Symbol for x in fine]
momentum_t71_t60 = { x : (self.data[x].performance_t7t1(), self.data[x].performance_t6t0()) for x in top_by_market_cap}
# Momentum t-7 to t-1 sorting
sorted_by_perf_t71 = sorted(momentum_t71_t60.items(), key = lambda x: x[1][0], reverse = True)
decile = int(len(sorted_by_perf_t71) / 10)
high_by_perf_t71 = [x[0] for x in sorted_by_perf_t71[:decile]]
low_by_perf_t71 = [x[0] for x in sorted_by_perf_t71[-decile:]]
# Momentum t-6 to t sorting
sorted_by_perf_t60 = sorted(momentum_t71_t60.items(), key = lambda x: x[1][1], reverse = True)
decile = int(len(sorted_by_perf_t60) / 10)
high_by_perf_t60 = [x[0] for x in sorted_by_perf_t60[:decile]]
low_by_perf_t60 = [x[0] for x in sorted_by_perf_t60[-decile:]]
self.long = [x for x in high_by_perf_t71 if x in high_by_perf_t60]
self.short = [x for x in low_by_perf_t71 if x in low_by_perf_t60]
self.selection_flag = False
return self.long + self.short
def Rebalance(self):
if self.months == 0:
self.selection_flag = True
self.months += 1
return
if self.months == 1:
# Trade execution and liquidation.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
long_count = len(self.long)
short_count = len(self.short)
for symbol in self.long:
self.SetHoldings(symbol, 1/long_count)
for symbol in self.short:
self.SetHoldings(symbol, -1/short_count)
self.months += 1
if self.months == 6:
self.months = 0
class SymbolData():
def __init__(self, symbol, period):
self.Symbol = symbol
self.Price = RollingWindow[float](period)
def update(self, value):
self.Price.Add(value)
def is_ready(self):
return self.Price.IsReady
def performance_t7t1(self):
closes = [x for x in self.Price][21:]
return (closes[0] / closes[-1] - 1)
def performance_t6t0(self):
closes = [x for x in self.Price][:-21]
return (closes[0] / closes[-1] - 1)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/crude-oil-predicts-equity-returns.py
================================================
from AlgorithmImports import *
import numpy as np
from scipy.optimize import minimize
import statsmodels.api as sm
sp100_stocks = ['AAPL','MSFT','AMZN','FB','BRKB','GOOGL','GOOG','JPM','JNJ','V','PG','XOM','UNH','BAC','MA','T','DIS','INTC','HD','VZ','MRK','PFE','CVX','KO','CMCSA','CSCO','PEP','WFC','C','BA','ADBE','WMT','CRM','MCD','MDT','BMY','ABT','NVDA','NFLX','AMGN','PM','PYPL','TMO','COST','ABBV','ACN','HON','NKE','UNP','UTX','NEE','IBM','TXN','AVGO','LLY','ORCL','LIN','SBUX','AMT','LMT','GE','MMM','DHR','QCOM','CVS','MO','LOW','FIS','AXP','BKNG','UPS','GILD','CHTR','CAT','MDLZ','GS','USB','CI','ANTM','BDX','TJX','ADP','TFC','CME','SPGI','COP','INTU','ISRG','CB','SO','D','FISV','PNC','DUK','SYK','ZTS','MS','RTN','AGN','BLK']
def MonthDiff(d1, d2):
return (d1.year - d2.year) * 12 + d1.month - d2.month
def Return(values):
return (values[-1] - values[0]) / values[0]
def Volatility(values):
values = np.array(values)
returns = (values[1:] - values[:-1]) / values[:-1]
return np.std(returns)
def MultipleLinearRegression(x, y):
x = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl free data
class QuandlFutures(PythonQuandl):
def __init__(self):
self.ValueColumnName = "settle"
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'Value'
# Quandl short interest data.
class QuandlFINRA_ShortVolume(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'SHORTVOLUME' # also 'TOTALVOLUME' is accesible
# Commitments of Traders data.
# NOTE: IMPORTANT: Data order must be ascending (datewise).
# Data source: https://commitmentsoftraders.org/cot-data/
# Data description: https://commitmentsoftraders.org/wp-content/uploads/Static/CoTData/file_key.html
class CommitmentsOfTraders(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/cot/{0}.PRN".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
# File example.
# DATE OPEN HIGH LOW CLOSE VOLUME OI
# ---- ---- ---- --- ----- ------ --
# DATE LARGE SPECULATOR COMMERCIAL HEDGER SMALL TRADER
# LONG SHORT LONG SHORT LONG SHORT
def Reader(self, config, line, date, isLiveMode):
data = CommitmentsOfTraders()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(',')
# Prevent lookahead bias.
data.Time = datetime.strptime(split[0], "%Y%m%d") + timedelta(days=1)
data['LARGE_SPECULATOR_LONG'] = int(split[1])
data['LARGE_SPECULATOR_SHORT'] = int(split[2])
data['COMMERCIAL_HEDGER_LONG'] = int(split[3])
data['COMMERCIAL_HEDGER_SHORT'] = int(split[4])
data['SMALL_TRADER_LONG'] = int(split[5])
data['SMALL_TRADER_SHORT'] = int(split[6])
data['open_interest'] = int(split[1]) + int(split[2]) + int(split[3]) + int(split[4]) + int(split[5]) + int(split[6])
data.Value = int(split[1])
return data
# Quantpedia bond yield data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaIndices(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/index/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaIndices()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(',')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data['close'] = float(split[1])
data.Value = float(split[1])
return data
# Quantpedia bond yield data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBondYield(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/bond_yield/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaBondYield()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(',')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data['yield'] = float(split[1])
data.Value = float(split[1])
return data
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
return data
# Commitments of Traders data.
# NOTE: IMPORTANT: Data order must be ascending (datewise).
# Data source: https://commitmentsoftraders.org/cot-data/
# Data description: https://commitmentsoftraders.org/wp-content/uploads/Static/CoTData/file_key.html
class CommitmentsOfTraders(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/cot/{0}.PRN".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
# File example.
# DATE OPEN HIGH LOW CLOSE VOLUME OI
# ---- ---- ---- --- ----- ------ --
# DATE LARGE SPECULATOR COMMERCIAL HEDGER SMALL TRADER
# LONG SHORT LONG SHORT LONG SHORT
def Reader(self, config, line, date, isLiveMode):
data = CommitmentsOfTraders()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(',')
# Prevent lookahead bias.
data.Time = datetime.strptime(split[0], "%Y%m%d") + timedelta(days=1)
data['LARGE_SPECULATOR_LONG'] = int(split[1])
data['LARGE_SPECULATOR_SHORT'] = int(split[2])
data['COMMERCIAL_HEDGER_LONG'] = int(split[3])
data['COMMERCIAL_HEDGER_SHORT'] = int(split[4])
data['SMALL_TRADER_LONG'] = int(split[5])
data['SMALL_TRADER_SHORT'] = int(split[6])
data.Value = int(split[1])
return data
# NOTE: Manager for new trades. It's represented by certain count of equally weighted brackets for long and short positions.
# If there's a place for new trade, it will be managed for time of holding period.
class TradeManager():
def __init__(self, algorithm, long_size, short_size, holding_period):
self.algorithm = algorithm # algorithm to execute orders in.
self.long_size = long_size
self.short_size = short_size
self.long_len = 0
self.short_len = 0
# Arrays of ManagedSymbols
self.symbols = []
self.holding_period = holding_period # Days of holding.
# Add stock symbol object
def Add(self, symbol, long_flag):
# Open new long trade.
managed_symbol = ManagedSymbol(symbol, self.holding_period, long_flag)
if long_flag:
# If there's a place for it.
if self.long_len < self.long_size:
self.symbols.append(managed_symbol)
self.algorithm.SetHoldings(symbol, 1 / self.long_size)
self.long_len += 1
else:
self.algorithm.Log("There's not place for additional trade.")
# Open new short trade.
else:
# If there's a place for it.
if self.short_len < self.short_size:
self.symbols.append(managed_symbol)
self.algorithm.SetHoldings(symbol, - 1 / self.short_size)
self.short_len += 1
else:
self.algorithm.Log("There's not place for additional trade.")
# Decrement holding period and liquidate symbols.
def TryLiquidate(self):
symbols_to_delete = []
for managed_symbol in self.symbols:
managed_symbol.days_to_liquidate -= 1
# Liquidate.
if managed_symbol.days_to_liquidate == 0:
symbols_to_delete.append(managed_symbol)
self.algorithm.Liquidate(managed_symbol.symbol)
if managed_symbol.long_flag: self.long_len -= 1
else: self.short_len -= 1
# Remove symbols from management.
for managed_symbol in symbols_to_delete:
self.symbols.remove(managed_symbol)
def LiquidateTicker(self, ticker):
symbol_to_delete = None
for managed_symbol in self.symbols:
if managed_symbol.symbol.Value == ticker:
self.algorithm.Liquidate(managed_symbol.symbol)
symbol_to_delete = managed_symbol
if managed_symbol.long_flag: self.long_len -= 1
else: self.short_len -= 1
break
if symbol_to_delete: self.symbols.remove(symbol_to_delete)
else: self.algorithm.Debug("Ticker is not held in portfolio!")
class ManagedSymbol():
def __init__(self, symbol, days_to_liquidate, long_flag):
self.symbol = symbol
self.days_to_liquidate = days_to_liquidate
self.long_flag = long_flag
class PortfolioOptimization(object):
def __init__(self, df_return, risk_free_rate, num_assets):
self.daily_return = df_return
self.risk_free_rate = risk_free_rate
self.n = num_assets # numbers of risk assets in portfolio
self.target_vol = 0.05
def annual_port_return(self, weights):
# calculate the annual return of portfolio
return np.sum(self.daily_return.mean() * weights) * 252
def annual_port_vol(self, weights):
# calculate the annual volatility of portfolio
return np.sqrt(np.dot(weights.T, np.dot(self.daily_return.cov() * 252, weights)))
def min_func(self, weights):
# method 1: maximize sharp ratio
return - self.annual_port_return(weights) / self.annual_port_vol(weights)
# method 2: maximize the return with target volatility
#return - self.annual_port_return(weights) / self.target_vol
def opt_portfolio(self):
# maximize the sharpe ratio to find the optimal weights
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bnds = tuple((0, 1) for x in range(2)) + tuple((0, 0.25) for x in range(self.n - 2))
opt = minimize(self.min_func, # object function
np.array(self.n * [1. / self.n]), # initial value
method='SLSQP', # optimization method
bounds=bnds, # bounds for variables
constraints=cons) # constraint conditions
opt_weights = opt['x']
return opt_weights
# https://quantpedia.com/strategies/crude-oil-predicts-equity-returns/
#
# Several types of oil can be used (Brent, WTI, Dubai etc.) without big differences in results. The source paper for
# this anomaly uses Arab Light crude oil. Monthly oil returns are used in the regression equation as an independent
# variable and equity returns are used as a dependent variable. The model is re-estimated every month and
# observations of the last month are added. The investor determines whether the expected stock market return in
# a specific month (based on regression results and conditional on the oil price change in the previous month) is higher
# or lower than the risk-free rate. The investor is fully invested in the market portfolio if the expected
# return is higher (bull market); he invests in cash if the expected return is lower (bear market).
from data_tools import QuantpediaFutures, QuandlValue, CustomFeeModel
from AlgorithmImports import *
import numpy as np
from collections import deque
from scipy import stats
class CrudeOilPredictsEquityReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.data = {}
self.symbols = [
"CME_ES1", # E-mini S&P 500 Futures, Continuous Contract #1
"CME_CL1" # Crude Oil Futures, Continuous Contract #1
]
self.cash = self.AddEquity('SHY', Resolution.Daily).Symbol
self.risk_free_rate = self.AddData(QuandlValue, 'FRED/DGS3MO', Resolution.Daily).Symbol
# Monhtly price data.
self.data = {}
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetLeverage(5)
data.SetFeeModel(CustomFeeModel())
self.data[symbol] = deque()
self.recent_month = -1
def OnData(self, data):
rebalance_flag = False
for symbol in self.symbols:
if symbol in data:
if self.recent_month != self.Time.month:
rebalance_flag = True
if data[symbol]:
price = data[symbol].Value
self.data[symbol].append(price)
if rebalance_flag:
self.recent_month = self.Time.month
rf_rate = 0
if self.Securities[self.risk_free_rate].GetLastData() and (self.Time.date() - self.Securities[self.risk_free_rate].GetLastData().Time.date()).days < 5:
rf_rate = self.Securities[self.risk_free_rate].Price
else:
return
if self.Securities[self.cash].GetLastData() and (self.Time.date() - self.Securities[self.cash].GetLastData().Time.date()).days >= 5:
return
market_prices = np.array(self.data[self.symbols[0]])
oil_prices = np.array(self.data[self.symbols[1]])
# At least one year of data is ready.
if len(market_prices) < 13 or len(oil_prices) < 13:
return
# Trim price series lengths.
min_size = min(len(market_prices), len(oil_prices))
market_prices = market_prices[-min_size:]
oil_prices = oil_prices[-min_size:]
market_returns = (market_prices[1:] - market_prices[:-1]) / market_prices[:-1]
oil_returns = (oil_prices[1:] - oil_prices[:-1]) / oil_prices[:-1]
# Simple Linear Regression
# Y = C + (M * X)
# Y = α + (β ∗ X)
# Y = Dependent variable (output/outcome/prediction/estimation)
# C/α = Constant (Y-Intercept)
# M/β = Slope of the regression line (the effect that X has on Y)
# X = Independent variable (input variable used in the prediction of Y)
slope, intercept, r_value, p_value, std_err = stats.linregress(oil_returns[:-1], market_returns[1:])
X = oil_returns[-1]
expected_market_return = intercept + (slope * X)
if expected_market_return > rf_rate:
self.SetHoldings(self.symbols[0], 1)
else:
if self.Securities[self.cash].Price != 0:
self.SetHoldings(self.cash, 1)
================================================
FILE: static/strategies/currency-momentum-factor.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# https://quantpedia.com/strategies/currency-momentum-factor/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest 12-month momentum against USD
# and go short three currencies with the lowest 12-month momentum against USD. Cash not used as margin invest on overnight rates. Rebalance monthly.
import data_tools
from AlgorithmImports import *
class CurrencyMomentumFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.data = {}
self.period = 12 * 21
self.SetWarmUp(self.period, Resolution.Daily)
self.symbols = [
"CME_AD1", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1", # British Pound Futures, Continuous Contract #1
"CME_CD1", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1", # Euro FX Futures, Continuous Contract #1
"CME_JY1", # Japanese Yen Futures, Continuous Contract #1
"CME_MP1", # Mexican Peso Futures, Continuous Contract #1
"CME_NE1", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1", # Swiss Franc Futures, Continuous Contract #1
]
for symbol in self.symbols:
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(5)
self.data[symbol] = self.ROC(symbol, self.period, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
if self.IsWarmingUp:
return
# rebalance monthly
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
perf = {
x[0]: x[1].Current.Value
for x in self.data.items()
if self.data[x[0]].IsReady and x[0] in data and data[x[0]]
}
long = []
short = []
if len(perf) >= 6:
sorted_by_performance = sorted(
perf.items(), key=lambda x: x[1], reverse=True
)
long = [x[0] for x in sorted_by_performance[:3]]
short = [x[0] for x in sorted_by_performance[-3:]]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
================================================
FILE: static/strategies/currency-value-factor-ppp-strategy.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# https://quantpedia.com/strategies/currency-value-factor-ppp-strategy/
#
# Create an investment universe consisting of several currencies (10-20). Use the latest OECD Purchasing Power Parity figure to assess
# the fair value of each currency versus USD in the month of publishing and then use monthly CPI changes and exchange rate changes to
# create fair PPP value for the month prior to the current month. Go long three currencies that are the most undervalued (lowest PPP
# fair value figure) and go short three currencies that are the most overvalued (highest PPP fair value figure). Invest cash not used
# as margin on overnight rates. Rebalance quarterly or monthly.
#
# QC implementation changes:
# - Yearly rebalance instead of quarterly is performed.
import data_tools
from AlgorithmImports import *
class CurrencyValueFactorPPPStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# currency future symbol and PPP yearly quandl symbol
# PPP source: https://www.quandl.com/data/ODA-IMF-Cross-Country-Macroeconomic-Statistics?keyword=%20United%20States%20Implied%20PPP%20Conversion%20Rate
self.symbols = {
"CME_AD1": "ODA/AUS_PPPEX", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1": "ODA/GBR_PPPEX", # British Pound Futures, Continuous Contract #1
"CME_CD1": "ODA/CAD_PPPEX", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1": "ODA/DEU_PPPEX", # Euro FX Futures, Continuous Contract #1
"CME_JY1": "ODA/JPN_PPPEX", # Japanese Yen Futures, Continuous Contract #1
"CME_NE1": "ODA/NZL_PPPEX", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1": "ODA/CHE_PPPEX", # Swiss Franc Futures, Continuous Contract #1
}
for symbol in self.symbols:
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(5)
# PPP quandl data.
ppp_symbol = self.symbols[symbol]
self.AddData(data_tools.QuandlValue, ppp_symbol, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
# January rebalance
if self.recent_month == 1:
ppp = {}
for symbol, ppp_symbol in self.symbols.items():
# if symbol in data and data[symbol]:
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
< 3
):
# new ppp data arrived
if ppp_symbol in data and data[ppp_symbol]:
ppp[symbol] = data[ppp_symbol].Value
count = 3
long = []
short = []
if len(ppp) >= count * 2:
# ppp sorting
sorted_by_ppp = sorted(ppp.items(), key=lambda x: x[1], reverse=True)
long = [x[0] for x in sorted_by_ppp[-count:]]
short = [x[0] for x in sorted_by_ppp[:count]]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
================================================
FILE: static/strategies/dispersion-trading.py
================================================
# https://quantpedia.com/strategies/dispersion-trading/
#
# The investment universe consists of stocks from the S&P 100 index.
# Trading vehicles are options on stocks from this index and also options on the index itself.
# The investor uses analyst forecasts of earnings per share from the Institutional Brokers Estimate System (I/B/E/S) database and
# computes for each firm the mean absolute difference scaled by an indicator of earnings uncertainty (see page 24 in the source academic paper for detailed methodology).
# Each month, investor sorts stocks into quintiles based on the size of belief disagreement.
# He buys puts of stocks with the highest belief disagreement and sells the index puts with Black-Scholes deltas ranging from -0.8 to -0.2.
#
# QC Implementation:
# - Due to lack of data, strategy only buys puts of 100 liquid US stocks and sells the SPX index puts.
#region imports
from AlgorithmImports import *
from numpy import floor
#endregion
class DispersionTrading(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(1000000)
self.min_expiry = 20
self.max_expiry = 60
self.index_symbol = self.AddIndex('SPX').Symbol
self.percentage_traded = 1.0
self.spx_contract = None
self.selected_symbols = []
self.subscribed_contracts = {}
self.coarse_count = 100
self.UniverseSettings.Resolution = Resolution.Minute
self.AddUniverse(self.CoarseSelectionFunction)
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
# rebalance on SPX contract expiration (should be on monthly basis)
if len(self.selected_symbols) != 0:
return Universe.Unchanged
# select top n stocks by dollar volume
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5],
key=lambda x: x.DollarVolume, reverse=True)[:self.coarse_count]
self.selected_symbols = [x.Symbol for x in selected]
return self.selected_symbols
def OnData(self, data):
# liquidate portfolio, when SPX contract is about to expire in 2 days
if self.index_symbol in self.subscribed_contracts and self.subscribed_contracts[self.index_symbol].ID.Date.date() - timedelta(2) <= self.Time.date():
self.subscribed_contracts.clear() # perform new subscribtion
self.selected_symbols.clear() # perform new selection
self.Liquidate()
if len(self.subscribed_contracts) == 0:
if self.Portfolio.Invested:
self.Liquidate()
# NOTE order is important, index should come first
for symbol in [self.index_symbol] + self.selected_symbols:
# subscribe to contract
contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
# get current price for stock
underlying_price = self.Securities[symbol].Price
# get strikes from stock contracts
strikes = [i.ID.StrikePrice for i in contracts]
# check if there is at least one strike
if len(strikes) <= 0:
continue
# at the money
atm_strike = min(strikes, key=lambda x: abs(x-underlying_price))
# filtred contracts based on option rights and strikes
atm_puts = [i for i in contracts if i.ID.OptionRight == OptionRight.Put and
i.ID.StrikePrice == atm_strike and
self.min_expiry <= (i.ID.Date - self.Time).days <= self.max_expiry]
# index contract is found
if symbol == self.index_symbol and len(atm_puts) == 0:
# cancel whole selection since index contract was not found
return
# make sure there are enough contracts
if len(atm_puts) > 0:
# sort by expiry
atm_put = sorted(atm_puts, key = lambda item: item.ID.Date, reverse=True)[0]
# add contract
option = self.AddOptionContract(atm_put, Resolution.Minute)
option.PriceModel = OptionPriceModels.CrankNicolsonFD()
option.SetDataNormalizationMode(DataNormalizationMode.Raw)
# store subscribed atm put contract
self.subscribed_contracts[symbol] = atm_put
# perform trade, when spx and stocks contracts are selected
if not self.Portfolio.Invested and len(self.subscribed_contracts) != 0 and self.index_symbol in self.subscribed_contracts:
index_option_contract = self.subscribed_contracts[self.index_symbol]
# make sure subscribed SPX contract has data
if self.Securities.ContainsKey(index_option_contract):
if self.Securities[index_option_contract].Price != 0 and self.Securities[index_option_contract].IsTradable:
# sell SPX ATM put contract
self.Securities[index_option_contract].MarginModel = BuyingPowerModel(2)
price = self.Securities[self.index_symbol].Price
if price != 0:
q = floor((self.Portfolio.TotalPortfolioValue * self.percentage_traded) / (price*100))
self.Sell(index_option_contract, q)
# buy stock's ATM put contracts
long_count = len(self.subscribed_contracts) - 1 # minus index symbol
for stock_symbol, stock_option_contract in self.subscribed_contracts.items():
if stock_symbol == self.index_symbol:
continue
if self.Securities[stock_option_contract].Price != 0 and self.Securities[stock_option_contract].IsTradable:
# buy contract
self.Securities[stock_option_contract].MarginModel = BuyingPowerModel(2)
if self.Securities.ContainsKey(stock_option_contract):
price = self.Securities[stock_symbol].Price
if price != 0:
q = floor(((self.Portfolio.TotalPortfolioValue / long_count) * self.percentage_traded) / (price*100))
self.Buy(stock_option_contract, q)
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/dollar-carry-trade.py
================================================
# https://quantpedia.com/strategies/dollar-carry-trade/
#
# The investment universe consists of currencies from developed countries (the Euro area, Australia, Canada, Denmark, Japan, New Zealand, Norway, Sweden,
# Switzerland, and the United Kingdom). The average forward discount (AFD) is calculated for this basket of currencies (each currency has an equal weight).
# The average 3-month rate could be used instead of the AFD in the calculation. The AFD is then compared to the 3-month US Treasury rate. The investor
# goes long on the US dollar and goes short on the basket of currencies if the 3-month US Treasury rate is higher than the AFD. The investor goes short
# on the US dollar and long on the basket of currencies if the 3-month US Treasury rate is higher than the AFD. The portfolio is rebalanced monthly.
import numpy as np
from AlgorithmImports import *
class DollarCarryTrade(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = {
"CME_AD1": "OECD/KEI_IR3TIB01_AUS_ST_M", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1": "OECD/KEI_IR3TIB01_GBR_ST_M", # British Pound Futures, Continuous Contract #1
"CME_CD1": "OECD/KEI_IR3TIB01_CAN_ST_M", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1": "OECD/KEI_IR3TIB01_EA19_ST_M", # Euro FX Futures, Continuous Contract #1
"CME_JY1": "OECD/KEI_IR3TIB01_JPN_ST_M", # Japanese Yen Futures, Continuous Contract #1
"CME_MP1": "OECD/KEI_IR3TIB01_MEX_ST_M", # Mexican Peso Futures, Continuous Contract #1
"CME_NE1": "OECD/KEI_IR3TIB01_NZL_ST_M", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1": "SNB/ZIMOMA", # Swiss Franc Futures, Continuous Contract #1
}
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
# Interbank rate data.
cash_rate_symbol = self.symbols[symbol]
self.AddData(QuandlValue, cash_rate_symbol, Resolution.Daily)
self.treasury_rate = self.AddData(
QuandlValue, "FRED/DGS3MO", Resolution.Daily
).Symbol
def OnData(self, data):
fd = {}
for future_symbol, cash_rate_symbol in self.symbols.items():
if cash_rate_symbol in data and data[cash_rate_symbol]:
if (
self.Securities[future_symbol].GetLastData()
and (
self.Time.date()
- self.Securities[future_symbol].GetLastData().Time.date()
).days
< 5
):
cash_rate = data[cash_rate_symbol].Value
# Update cash rate only once a month.
fd[future_symbol] = cash_rate
if len(fd) == 0:
return
afd = np.mean([x[1] for x in fd.items()])
if (
self.Securities[self.treasury_rate].GetLastData()
and (
self.Time.date()
- self.Securities[self.treasury_rate].GetLastData().Time.date()
).days
< 5
):
treasuries_3m_rate = self.Securities[self.treasury_rate].Price
count = len(self.symbols)
if treasuries_3m_rate > afd:
# Long on the US dollar and goes short on the basket of currencies.
for symbol in self.symbols:
self.SetHoldings(symbol, -1 / count)
else:
# Short on the US dollar and long on the basket of currencies.
for symbol in self.symbols:
self.SetHoldings(symbol, 1 / count)
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/earnings-announcement-premium.py
================================================
# https://quantpedia.com/strategies/earnings-announcement-premium/
#
# The investment universe consists of all stocks from the CRSP database. At the beginning of every calendar month, stocks are ranked in ascending
# order on the basis of the volume concentration ratio, which is defined as the volume of the previous 16 announcement months divided by the total
# volume in the previous 48 months. The ranked stocks are assigned to one of 5 quintile portfolios. Within each quintile, stocks are assigned to
# one of two portfolios (expected announcers and expected non-announcers) using the predicted announcement based on the previous year. All stocks
# are value-weighted within a given portfolio, and portfolios are rebalanced every calendar month to maintain value weights. The investor invests
# in a long-short portfolio, which is a zero-cost portfolio that holds the portfolio of high volume expected announcers and sells short the
# portfolio of high volume expected non-announcers.
#
# QC implementation changes:
# - Universe consists of 1000 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from collections import deque
from AlgorithmImports import *
class EarningsAnnouncementPremium(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.period = 21
self.month_period = 48
# Volume daily data.
self.data = {}
# Volume monthly data.
self.monthly_volume = {}
self.coarse_count = 1000
self.weight = {}
self.selection_flag = True
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].Add(stock.Volume)
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup volume rolling windows.
for symbol in selected:
# Warmup data.
if symbol not in self.data:
self.data[symbol] = RollingWindow[float](self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Debug(f"No history for {symbol} yet")
continue
volumes = history.loc[symbol].volume
for _, volume in volumes.iteritems():
self.data[symbol].Add(volume)
return [x for x in selected if self.data[x].IsReady]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
# top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
# else:
# top_by_market_cap = fine
top_by_market_cap = fine
fine_symbols = [x.Symbol for x in top_by_market_cap]
# Ratio/market cap pair.
volume_concentration_ratio = {}
for stock in top_by_market_cap:
symbol = stock.Symbol
if symbol not in self.monthly_volume:
self.monthly_volume[symbol] = deque(maxlen = self.month_period)
monthly_vol = sum([x for x in self.data[symbol]])
last_month_date = self.Time - timedelta(days = self.Time.day)
last_file_date = stock.EarningReports.FileDate # stock annoucement day
was_announcement_month = (last_file_date.year == last_month_date.year and last_file_date.month == last_month_date.month) # Last month was announcement date.
self.monthly_volume[symbol].append(VolumeData(last_month_date, monthly_vol, was_announcement_month))
# 48 months of volume data is ready.
if len(self.monthly_volume[symbol]) == self.monthly_volume[symbol].maxlen:
# Volume concentration ratio calc.
announcement_count = 16
announcement_volumes = [x.Volume for x in self.monthly_volume[symbol] if x.WasAnnouncementMonth][-announcement_count:]
if len(announcement_volumes) == announcement_count:
announcement_months_volume = sum(announcement_volumes)
total_volume = sum([x.Volume for x in self.monthly_volume[symbol]])
if announcement_months_volume != 0 and total_volume != 0:
# Store ratio, market cap pair.
volume_concentration_ratio[stock] = announcement_months_volume / total_volume
# Volume sorting.
sorted_by_volume = sorted(volume_concentration_ratio.items(), key = lambda x: x[1], reverse = True)
quintile = int(len(sorted_by_volume) / 5)
high_volume = [x[0] for x in sorted_by_volume[:quintile]]
# Filering announcers and non-announcers.
month_to_lookup = self.Time.month
year_to_lookup = self.Time.year - 1
long = []
short = []
for stock in high_volume:
symbol = stock.Symbol
announcement_dates = [[x.Date.year, x.Date.month] for x in self.monthly_volume[symbol] if x.WasAnnouncementMonth]
if [year_to_lookup, month_to_lookup] in announcement_dates:
long.append(stock)
else:
short.append(stock)
# Delete not updated symbols.
symbols_to_remove = []
for symbol in self.monthly_volume:
if symbol not in fine_symbols:
symbols_to_remove.append(symbol)
for symbol in symbols_to_remove:
del self.monthly_volume[symbol]
# Market cap weighting.
total_market_cap_long = sum([x.MarketCap for x in long])
for stock in long:
self.weight[symbol] = stock.MarketCap / total_market_cap_long
total_market_cap_short = sum([x.MarketCap for x in short])
for stock in short:
self.weight[symbol] = -stock.MarketCap / total_market_cap_short
return [x[0] for x in self.weight.items()]
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.weight:
self.Liquidate(symbol)
for symbol, w in self.weight.items():
if self.Securities[symbol].Price != 0: # Prevent error message.
self.SetHoldings(symbol, w)
self.weight.clear()
def Selection(self):
self.selection_flag = True
# Monthly volume data.
class VolumeData():
def __init__(self, date, monthly_volume, was_announcement_month):
self.Date = date
self.Volume = monthly_volume
self.WasAnnouncementMonth = was_announcement_month
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/earnings-announcements-combined-with-stock-repurchases.py
================================================
# https://quantpedia.com/strategies/earnings-announcements-combined-with-stock-repurchases/
#
# The investment universe consists of stocks from NYSE/AMEX/Nasdaq (no ADRs, CEFs or REITs), bottom 25% of firms by market cap are dropped.
# Each quarter, the investor looks for companies that announce a stock repurchase program (with announced buyback for at least 5% of outstanding stocks)
# during days -30 to -15 before the earnings announcement date for each company.
# Investor goes long stocks with announced buybacks during days -10 to +15 around an earnings announcement.
# The portfolio is equally weighted and rebalanced daily.
#
# QC Implementation:
# - Universe consists of tickers, which have earnings annoucement.
#region imports
from AlgorithmImports import *
import numpy as np
#endregion
class EarningsAnnouncementsCombinedWithStockRepurchases(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2011, 1, 1) # Buyback data strats at 2011
self.SetCash(100000)
self.fine = {}
self.price = {}
self.managed_symbols = []
self.earnings_universe = []
self.earnings = {}
self.buybacks = {}
self.max_traded_stocks = 40 # maximum number of trading stocks
self.quantile = 4
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
# load earnings dates
csv_data = self.Download('data.quantpedia.com/backtesting_data/economic/earning_dates.csv')
lines = csv_data.split('\r\n')
for line in lines:
line_split = line.split(';')
date = line_split[0]
if date == '' :
continue
date = datetime.strptime(date, "%Y-%m-%d").date()
self.earnings[date] = []
for ticker in line_split[1:]: # skip date in current line
self.earnings[date].append(ticker)
if ticker not in self.earnings_universe:
self.earnings_universe.append(ticker)
# load buyback dates
csv_data = self.Download('data.quantpedia.com/backtesting_data/equity/BUY_BACKS.csv')
lines = csv_data.split('\r\n')
for line in lines[1:]: # skip header
line_split = line.split(';')
date = line_split[0]
if date == '' :
continue
date = datetime.strptime(date, "%d.%m.%Y").date()
self.buybacks[date] = []
for ticker in line_split[1:]: # skip date in current line
self.buybacks[date].append(ticker)
self.months_counter = 0
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
# update stocks last prices
for stock in coarse:
ticker = stock.Symbol.Value
if ticker in self.earnings_universe:
# store stock's last price
self.price[ticker] = stock.AdjustedPrice
# rebalance quarterly
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
# select stocks, which had spin off
selected = [x.Symbol for x in coarse if x.Symbol.Value in self.earnings_universe]
return selected
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and
((x.SecurityReference.ExchangeId == "NYS") or
(x.SecurityReference.ExchangeId == "NAS") or
(x.SecurityReference.ExchangeId == "ASE"))]
if len(fine) < self.quantile:
return Universe.Unchanged
# exclude 25% stocks with lowest market capitalization
quantile = int(len(fine) / self.quantile)
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap)
selected = sorted_by_market_cap[quantile:]
self.fine = {x.Symbol.Value : x.Symbol for x in selected}
return list(self.fine.values())
def OnData(self, data:Slice) -> None:
remove_managed_symbols = []
# maybe there should be BDay(15)
liquidate_date = self.Time.date() - timedelta(15)
# check if bought stocks have 15 days after earnings annoucemnet
for managed_symbol in self.managed_symbols:
if managed_symbol.earnings_date >= liquidate_date:
remove_managed_symbols.append(managed_symbol)
# liquidate stock by selling it's quantity
self.MarketOrder(managed_symbol.symbol, -managed_symbol.quantity)
# remove liquidated stocks from self.managed_symbols
for managed_symbol in remove_managed_symbols:
self.managed_symbols.remove(managed_symbol)
# maybe there should be BDay(10)
after_current = self.Time.date() + timedelta(10)
if after_current in self.earnings:
# this stocks has earnings annoucement after 10 days
stocks_with_earnings = self.earnings[after_current]
# 30 days before earnings annoucement
buyback_start = self.Time.date() - timedelta(20)
# 15 days before earnings annoucement
buyback_end = self.Time.date() - timedelta(5)
stocks_with_buyback = [] # storing stocks with buyback in period -30 to -15 days before earnings annoucement
for buyback_date, tickers in self.buybacks.items():
# check if buyback date is in period before earnings annoucement
if buyback_date >= buyback_start and buyback_date <= buyback_end:
# iterate through each stock ticker for buyback date
for ticker in tickers:
# add stock ticker if it isn't already added, it has earnings annoucement after 10 days and was selected in fine
if (ticker not in stocks_with_buyback) and (ticker in stocks_with_earnings) and (ticker in self.fine):
stocks_with_buyback.append(self.fine[ticker])
# buying stocks buyback in period -30 to -15 days before earnings annoucement
# and stocks, which have earnings date -10 days before current date
for symbol in stocks_with_buyback:
# check if there is a place in Portfolio for trading current stock
if not len(self.managed_symbols) < self.max_traded_stocks:
continue
# calculate stock quantity
weight = self.Portfolio.TotalPortfolioValue / self.max_traded_stocks
quantity = np.floor(weight / self.price[symbol.Value])
# go long stock
self.MarketOrder(symbol, quantity)
# store stock's ticker, earnings date and traded quantity
if symbol in data and data[symbol]:
self.managed_symbols.append(ManagedSymbol(symbol, after_current, quantity))
def Selection(self):
# quarterly selection
if self.months_counter % 3 == 0:
self.selection_flag = True
self.months_counter += 1
class ManagedSymbol():
def __init__(self, symbol, earnings_date, quantity):
self.symbol = symbol
self.earnings_date = earnings_date
self.quantity = quantity
# custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/earnings-quality-factor.py
================================================
# https://quantpedia.com/strategies/earnings-quality-factor/
#
# The investment universe consists of all non-financial stocks from NYSE, Amex and Nasdaq. Big stocks are defined as the largest stocks
# that make up 90% of the total market cap within the region, while small stocks make up the remaining 10% of the market cap. Investor defines
# breakpoints by the 30th and 70th percentiles of the multiple “Earnings Quality” ratios between large caps and small caps.
# The first “Earnings Quality” ratio is defined by cash flow relative to reported earnings. The high-quality earnings firms are characterized
# by high cash flows (relative to reported earnings) while the low-quality firms are characterized by high reported earnings (relative to cash flow).
# The second factor is based on return on equity (ROE) to exploit the well-documented “profitability anomaly” by going long high-ROE firms
# (top 30%) and short low-ROE firms (bottom 30%). The third ratio – CF/A (cash flow to assets) factor goes long firms with high cash flow to total assets.
# The fourth ratio – D/A (debt to assets) factor goes long firms with low leverage and short firms with high leverage.
# The investor builds a scored composite quality metric by computing the percentile score of each stock on each of the four quality metrics
# (where “good” quality has a high score, so ideally a stock has low accruals, low leverage, high ROE, and high cash flow) and then add up
# the percentiles to get a score for each stock from 0 to 400. He then forms the composite factor by going long the top 30% of small-cap
# stocks and also large-cap stocks and short the bottom 30% of the small-cap stocks and also large-cap stocks and cap-weighting individual
# stocks within the portfolios. The final factor portfolio is formed at the end of each June and is rebalanced yearly.
#
# QC implementation changes:
# - Universe consists of top 3000 US non-financial stocks by market cap from NYSE, AMEX and NASDAQ.
class EarningsQualityFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.coarse_count = 3000
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.accruals_data = {}
self.long = []
self.short = []
self.data = {}
self.selection_flag = True
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetLeverage(10)
security.SetFeeModel(CustomFeeModel(self))
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = [
x.Symbol for x in coarse if x.HasFundamentalData and x.Market == "usa"
]
return selected
def FineSelectionFunction(self, fine):
fine = [
x
for x in fine
if x.MarketCap != 0
and x.CompanyReference.IndustryTemplateCode != "B"
and (
(x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE")
)
and x.FinancialStatements.BalanceSheet.CurrentAssets.Value != 0
and x.FinancialStatements.BalanceSheet.CashAndCashEquivalents.Value != 0
and x.FinancialStatements.BalanceSheet.CurrentLiabilities.Value != 0
and x.FinancialStatements.BalanceSheet.CurrentDebt.Value != 0
and x.FinancialStatements.IncomeStatement.DepreciationAndAmortization.Value
!= 0
and x.FinancialStatements.BalanceSheet.GrossPPE.Value != 0
and x.FinancialStatements.IncomeStatement.TotalRevenueAsReported.Value != 0
and x.FinancialStatements.CashFlowStatement.OperatingCashFlow.Value != 0
and x.EarningReports.BasicEPS.Value != 0
and x.EarningReports.BasicAverageShares.Value != 0
and x.OperationRatios.DebttoAssets.Value != 0
and x.OperationRatios.ROE.Value != 0
]
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
top_by_market_cap = [x for x in sorted_by_market_cap[: self.coarse_count]]
else:
top_by_market_cap = fine
for stock in top_by_market_cap:
symbol = stock.Symbol
if symbol not in self.accruals_data:
# Data for previous year.
self.accruals_data[symbol] = None
# Accrual calc.
current_accruals_data = AcrrualsData(
stock.FinancialStatements.BalanceSheet.CurrentAssets.Value,
stock.FinancialStatements.BalanceSheet.CashAndCashEquivalents.Value,
stock.FinancialStatements.BalanceSheet.CurrentLiabilities.Value,
stock.FinancialStatements.BalanceSheet.CurrentDebt.Value,
stock.FinancialStatements.BalanceSheet.IncomeTaxPayable.Value,
stock.FinancialStatements.IncomeStatement.DepreciationAndAmortization.Value,
stock.FinancialStatements.BalanceSheet.TotalAssets.Value,
stock.FinancialStatements.IncomeStatement.TotalRevenueAsReported.Value,
)
# There is not previous accruals data.
if not self.accruals_data[symbol]:
self.accruals_data[symbol] = current_accruals_data
continue
current_accruals = self.CalculateAccruals(
current_accruals_data, self.accruals_data[symbol]
)
# cash flow to assets
CFA = (
stock.FinancialStatements.CashFlowStatement.OperatingCashFlow.Value
/ (
stock.EarningReports.BasicEPS.Value
* stock.EarningReports.BasicAverageShares.Value
)
)
# debt to assets
DA = stock.OperationRatios.DebttoAssets.Value
# return on equity
ROE = stock.OperationRatios.ROE.Value
if symbol not in self.data:
self.data[symbol] = None
self.data[symbol] = StockData(current_accruals, CFA, DA, ROE)
self.accruals_data[symbol] = current_accruals_data
# Remove not updated symbols.
updated_symbols = [x.Symbol for x in top_by_market_cap]
not_updated = [x for x in self.data if x not in updated_symbols]
for symbol in not_updated:
del self.data[symbol]
del self.accruals_data[symbol]
return [x[0] for x in self.data.items()]
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Sort stocks by four factors respectively.
sorted_by_accruals = sorted(
self.data.items(), key=lambda x: x[1].Accruals, reverse=True
) # high score with low accrual
sorted_by_CFA = sorted(
self.data.items(), key=lambda x: x[1].CFA
) # high score with high CFA
sorted_by_DA = sorted(
self.data.items(), key=lambda x: x[1].DA, reverse=True
) # high score with low leverage
sorted_by_ROE = sorted(
self.data.items(), key=lambda x: x[1].ROE
) # high score with high ROE
score = {}
# Assign a score to each stock according to their rank with different factors.
for i, obj in enumerate(sorted_by_accruals):
score_accruals = i
score_CFA = sorted_by_CFA.index(obj)
score_DA = sorted_by_DA.index(obj)
score_ROE = sorted_by_ROE.index(obj)
score[obj[0]] = score_accruals + score_CFA + score_DA + score_ROE
sorted_by_score = sorted(score.items(), key=lambda x: x[1], reverse=True)
tercile = int(len(sorted_by_score) / 3)
long = [x[0] for x in sorted_by_score[:tercile]]
short = [x[0] for x in sorted_by_score[-tercile:]]
# Trade execution.
# NOTE: Skip year 2007 due to data error.
if self.Time.year == 2007:
self.Liquidate()
return
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
): # Prevent error message.
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
): # Prevent error message.
self.SetHoldings(symbol, -1 / len(short))
# Source: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3188172
def CalculateAccruals(self, current_accrual_data, prev_accrual_data):
delta_assets = (
current_accrual_data.CurrentAssets - prev_accrual_data.CurrentAssets
)
delta_cash = (
current_accrual_data.CashAndCashEquivalents
- prev_accrual_data.CashAndCashEquivalents
)
delta_liabilities = (
current_accrual_data.CurrentLiabilities
- prev_accrual_data.CurrentLiabilities
)
delta_debt = current_accrual_data.CurrentDebt - prev_accrual_data.CurrentDebt
dep = current_accrual_data.DepreciationAndAmortization
total_assets_prev_year = prev_accrual_data.TotalAssets
acc = (
delta_assets - delta_liabilities - delta_cash + delta_debt - dep
) / total_assets_prev_year
return acc
def Selection(self):
if self.Time.month == 7:
self.selection_flag = True
class AcrrualsData:
def __init__(
self,
current_assets,
cash_and_cash_equivalents,
current_liabilities,
current_debt,
income_tax_payable,
depreciation_and_amortization,
total_assets,
sales,
):
self.CurrentAssets = current_assets
self.CashAndCashEquivalents = cash_and_cash_equivalents
self.CurrentLiabilities = current_liabilities
self.CurrentDebt = current_debt
self.IncomeTaxPayable = income_tax_payable
self.DepreciationAndAmortization = depreciation_and_amortization
self.TotalAssets = total_assets
self.Sales = sales
class StockData:
def __init__(self, accruals, cfa, da, roe):
self.Accruals = accruals
self.CFA = cfa
self.DA = da
self.ROE = roe
def MultipleLinearRegression(x, y):
x = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/esg-factor-momentum-strategy.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/esg-factor-momentum-strategy/
#
# The investment universe consists of stocks in the MSCI World Index. Paper uses MSCI ESG Ratings as the ESG database.
# The ESG Momentum strategy is built by overweighting, relative to the MSCI World Index, companies that increased their
# ESG ratings most during the recent past and underweight those with decreased ESG ratings, where the increases and decreases
# are based on a 12-month ESG momentum. The paper uses the Barra Global Equity Model (GEM3) for portfolio construction with
# constraints that can be found in Appendix 2. Therefore, this strategy is very specific, but we aim to present the idea, not
# the portfolio construction. The strategy is rebalanced monthly.
#
# QC implementation:
# - Universe consists of ~700 stocks with ESG score data.
from numpy import floor
class ESGFactorMomentumStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2009, 6, 1)
self.SetEndDate(2019, 12, 31)
self.SetCash(100000)
# Decile weighting.
# True - Value weighted
# False - Equally weighted
self.value_weighting = True
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.esg_data = self.AddData(ESGData, 'ESG', Resolution.Daily)
self.tickers = []
self.holding_period = 3
self.managed_queue = []
# Monthly ESG decile data.
self.esg = {}
self.period = 14
self.latest_price = {}
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel(self))
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
self.latest_price.clear()
selected = [x for x in coarse if (x.Symbol.Value).lower() in self.tickers]
for stock in selected:
symbol = stock.Symbol
self.latest_price[symbol] = stock.AdjustedPrice
return [x.Symbol for x in selected]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0]
momentum = {}
# Momentum calc.
for stock in fine:
symbol = stock.Symbol
ticker = symbol.Value
# ESG data for 14 months is ready.
if ticker in self.esg and self.esg[ticker].IsReady:
esg_data = [x for x in self.esg[ticker]]
esg_decile_2_months_ago = esg_data[1]
esg_decile_14_months_ago = esg_data[13]
if esg_decile_14_months_ago != 0 and esg_decile_2_months_ago != 0:
# Momentum as difference.
# momentum_ = esg_decile_2_months_ago - esg_decile_14_months_ago
# Momentum as ratio.
momentum_ = (esg_decile_2_months_ago / esg_decile_14_months_ago) - 1
# Store momentum/market cap pair.
momentum[stock] = momentum_
# Momentum sorting.
sorted_by_momentum = sorted(momentum.items(), key = lambda x: x[1], reverse = True)
decile = int(len(sorted_by_momentum) / 10)
long = [x[0] for x in sorted_by_momentum[:decile]]
short = [x[0] for x in sorted_by_momentum[-decile:]]
long_symbol_q = []
short_symbol_q = []
# ew
if not self.value_weighting:
if len(long) != 0:
long_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(long)
long_symbol_q = [(x.Symbol, floor(long_w / self.latest_price[x.Symbol])) for x in long]
if len(short) != 0:
short_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(short)
short_symbol_q = [(x.Symbol, -floor(short_w / self.latest_price[x.Symbol])) for x in short]
# vw
else:
if len(long) != 0:
total_market_cap_long = sum([x.MarketCap for x in long])
long_w = self.Portfolio.TotalPortfolioValue / self.holding_period
long_symbol_q = [(x.Symbol, floor((long_w * (x.MarketCap / total_market_cap_long))) / self.latest_price[x.Symbol]) for x in long]
short_symbol_q = []
if len(short) != 0:
total_market_cap_short = sum([x.MarketCap for x in short])
short_w = self.Portfolio.TotalPortfolioValue / self.holding_period
short_symbol_q = [(x.Symbol, -floor((short_w * (x.MarketCap / total_market_cap_short))) / self.latest_price[x.Symbol]) for x in short]
self.managed_queue.append(RebalanceQueueItem(long_symbol_q + short_symbol_q))
return [x.Symbol for x in long + short]
def OnData(self, data):
new_data_arrived = False
if 'ESG' in data and data['ESG']:
# Store universe tickers.
if len(self.tickers) == 0:
# TODO '_typename' in storage dictionary?
self.tickers = [x.Key for x in self.esg_data.GetLastData().GetStorageDictionary()][:-1]
# Store history for every ticker.
for ticker in self.tickers:
ticker_u = ticker.upper()
if ticker_u not in self.esg:
self.esg[ticker_u] = RollingWindow[float](self.period)
decile = self.esg_data.GetLastData()[ticker]
self.esg[ticker_u].Add(decile)
# trigger selection after new esg data arrived.
if not self.selection_flag:
new_data_arrived = True
if new_data_arrived:
self.selection_flag = True
return
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution
remove_item = None
# Rebalance portfolio
for item in self.managed_queue:
if item.holding_period == self.holding_period:
for symbol, quantity in item.symbol_q:
if quantity >= 1:
self.MarketOrder(symbol, -quantity)
remove_item = item
elif item.holding_period == 0:
open_symbol_q = []
for symbol, quantity in item.symbol_q:
if quantity >= 1:
if self.Securities[symbol].Price != 0 and self.Securities[symbol].IsTradable:
self.MarketOrder(symbol, quantity)
open_symbol_q.append((symbol, quantity))
# Only opened orders will be closed
item.symbol_q = open_symbol_q
item.holding_period += 1
if remove_item:
self.managed_queue.remove(remove_item)
class RebalanceQueueItem():
def __init__(self, symbol_q):
# symbol/quantity collections
self.symbol_q = symbol_q
self.holding_period = 0
# ESG data.
class ESGData(PythonData):
def __init__(self):
self.tickers = []
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/esg_deciles_data.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = ESGData()
data.Symbol = config.Symbol
if not line[0].isdigit():
self.tickers = [x for x in line.split(';')][1:]
return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
index = 1
for ticker in self.tickers:
data[ticker] = float(split[index])
index += 1
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/fed-model.py
================================================
# https://quantpedia.com/strategies/fed-model/
#
# Each month, the investor conducts a one-month predictive regression (using all available data up to that date) predicting excess stock market
# returns using the yield gap as an independent variable. The “Yield gap” is calculated as YG = EY − y, with earnings yield EY ≡ ln (1 ++ E/P)
# and y = ln (1 ++ Y) is the log 10 year Treasury bond yield. Then, the strategy allocates 100% in the risky asset if the forecasted excess
# returns are positive, and otherwise, it invests 100% in the risk-free rate.
from collections import deque
from AlgorithmImports import *
import numpy as np
from scipy import stats
class FEDModel(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# monthly price data and yield gap data
self.data = {}
self.period = 12 * 21
self.SetWarmUp(self.period)
self.market = self.AddEquity("SPY", Resolution.Daily).Symbol
self.market_data = deque()
self.cash = self.AddEquity("SHY", Resolution.Daily).Symbol
# risk free rate
self.risk_free_rate = self.AddData(
QuandlValue, "FRED/DGS3MO", Resolution.Daily
).Symbol
# 10Y bond yield symbol
self.bond_yield = self.AddData(
QuantpediaBondYield, "US10YT", Resolution.Daily
).Symbol
# SP500 earnings yield data
self.sp_earnings_yield = self.AddData(
QuandlValue, "MULTPL/SP500_EARNINGS_YIELD_MONTH", Resolution.Daily
).Symbol
self.yield_gap = deque()
self.recent_month = -1
def OnData(self, data):
rebalance_flag = False
if self.sp_earnings_yield in data and data[self.sp_earnings_yield]:
if self.Time.month != self.recent_month:
self.recent_month = self.Time.month
rebalance_flag = True
if not rebalance_flag:
# earnings yield data is no longer coming in
if self.Securities[self.sp_earnings_yield].GetLastData():
if (
self.Time.date()
- self.Securities[self.sp_earnings_yield].GetLastData().Time.date()
).days > 31:
self.Liquidate()
return
# pdate market price data
if (
self.market in data
and self.risk_free_rate in data
and self.bond_yield in data
):
if (
data[self.market]
and data[self.risk_free_rate]
and data[self.bond_yield]
):
market_price = data[self.market].Value
rf_rate = data[self.risk_free_rate].Value
bond_yield = data[self.bond_yield].Value
sp_ey = data[self.sp_earnings_yield].Value
if (
market_price != 0
and rf_rate != 0
and bond_yield != 0
and sp_ey != 0
):
self.market_data.append((market_price, rf_rate))
yield_gap = np.log(sp_ey) - np.log(bond_yield)
self.yield_gap.append(yield_gap)
rebalance_flag = True
# ensure minimum data points to calculate regression
min_count = 6
if len(self.market_data) >= min_count:
market_closes = np.array([x[0] for x in self.market_data])
market_returns = (market_closes[1:] - market_closes[:-1]) / market_closes[
:-1
]
rf_rates = np.array([x[1] for x in self.market_data][1:])
excess_returns = market_returns - rf_rates
yield_gaps = [x for x in self.yield_gap]
# linear regression
# Y = α + (β ∗ X)
# intercept = alpha
# slope = beta
beta, alpha, r_value, p_value, std_err = stats.linregress(
yield_gaps[1:-1], market_returns[1:]
)
X = yield_gaps[-1]
# predicted market return
Y = alpha + (beta * X)
# trade execution / rebalance
if Y > 0:
if self.Portfolio[self.cash].Invested:
self.Liquidate(self.cash)
self.SetHoldings(self.market, 1)
else:
if self.Portfolio[self.market].Invested:
self.Liquidate(self.market)
self.SetHoldings(self.cash, 1)
# Quantpedia bond yield data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBondYield(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/bond_yield/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaBondYield()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(",")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data["yield"] = float(split[1])
data.Value = float(split[1])
return data
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
================================================
FILE: static/strategies/fx-carry-trade.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel:
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/fx-carry-trade/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest central bank prime rates and
# go short three currencies with the lowest central bank prime rates. The cash not used as the margin is invested in overnight rates. The strategy
# is rebalanced monthly.
import data_tools
class ForexCarryTrade(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# Source: https://www.quandl.com/data/OECD-Organisation-for-Economic-Co-operation-and-Development
self.symbols = {
"CME_AD1": "OECD/KEI_IR3TIB01_AUS_ST_M", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1": "OECD/KEI_IR3TIB01_GBR_ST_M", # British Pound Futures, Continuous Contract #1
"CME_CD1": "OECD/KEI_IR3TIB01_CAN_ST_M", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1": "OECD/KEI_IR3TIB01_EA19_ST_M", # Euro FX Futures, Continuous Contract #1
"CME_JY1": "OECD/KEI_IR3TIB01_JPN_ST_M", # Japanese Yen Futures, Continuous Contract #1
"CME_MP1": "OECD/KEI_IR3TIB01_MEX_ST_M", # Mexican Peso Futures, Continuous Contract #1
"CME_NE1": "OECD/KEI_IR3TIB01_NZL_ST_M", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1": "SNB/ZIMOMA", # Swiss Franc Futures, Continuous Contract #1
}
for symbol, rate_symbol in self.symbols.items():
self.AddData(Quandl, rate_symbol, Resolution.Daily)
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(5)
self.recent_month = -1
def OnData(self, data):
rebalance_flag: bool = False
rate: dict[str, float] = {}
for symbol, int_rate in self.symbols.items():
# futures data is present in the algorithm
if symbol in data and data[symbol]:
if self.recent_month != self.Time.month:
rebalance_flag = True
self.recent_month = self.Time.month
# IR data is still coming in
if (
self.Securities[int_rate].GetLastData()
and (
self.Time.date()
- self.Securities[int_rate].GetLastData().Time.date()
).days
<= 31
):
rate[symbol] = self.Securities[int_rate].Price
if rebalance_flag:
long = []
short = []
if len(rate) >= 3:
# interbank rate sorting
sorted_by_rate = sorted(rate.items(), key=lambda x: x[1], reverse=True)
traded_count = 3
long = [x[0] for x in sorted_by_rate[:traded_count]]
short = [x[0] for x in sorted_by_rate[-traded_count:]]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
================================================
FILE: static/strategies/how-to-use-lexical-density-of-company-filings.py
================================================
from QuantConnect.DataSource import *
import numpy as np
from enum import Enum
class BrainLanguageMetrics(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.init_cash = 100000
self.SetCash(self.init_cash)
self.market = self.AddEquity('SPY', Resolution.Daily).Symbol
self.mkt = [] # benchmark chart data
# metric dictionary with signal optimism flag
# metric_dictionary:dict[int, (str, bool)] = {
# # 1 : ('SentenceCount', True),
# # 2 : ('MeanSentenceLength', True),
# # 3 : ('Sentiment', True),
# # 4 : ('Uncertainty', False),
# # 5 : ('Litigious', False),
# # 6 : ('Constraining', False),
# # 7 : ('Interesting', True),
# # 8 : ('Readability', True),
# 9 : ('LexicalRichness', True),
# 10 : ('LexicalDensity', True),
# 11 : ('SpecificDensity', True),
# 12 : ('SPY', True),
# }
self.metric_values = [
#'LexicalRichness', #9
'LexicalDensity', #10
'SpecificDensity' #11
]
# opt parameters
# self.metric_property:tuple = metric_dictionary[int(self.GetParameter("metric"))]
# self.metric_property:tuple = metric_dictionary[11]
# self.portfolio_size_property:int = int(self.GetParameter("portfolio_size"))
self.portfolio_size_property:int = 10
# self.universe_size_property:int = int(self.GetParameter("universe_size"))
self.universe_size_property:int = 500
# self.long = []
# self.short = []
self.traded_quantity = {}
self.metric = {}
self.metric_symbols = {}
self.price = {}
self.recent_universe = []
self.coarse_count = self.universe_size_property
self.selection_flag = False
self.rebalance_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.market), self.TimeRules.AfterMarketOpen(self.market), self.Selection)
# self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.AfterMarketOpen(self.market), self.PrintBenchmark)
def PrintBenchmark(self):
mkt_price = self.History(self.market, 2, Resolution.Daily)['close'].unstack(level=0).iloc[-1]
self.mkt.append(mkt_price)
mkt_perf = self.init_cash * self.mkt[-1] / self.mkt[0]
self.Plot('Strategy Equity', self.market, mkt_perf)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel(self))
security.SetLeverage(10)
# remove recently stored metric value
for security in changes.RemovedSecurities:
symbol = security.Symbol
if symbol in self.metric:
del self.metric[symbol]
def CoarseSelectionFunction(self, coarse):
# return old universe if selection is not needed
if self.rebalance_flag and not self.selection_flag:
for stock in coarse:
symbol = stock.Symbol
if symbol in self.recent_universe:
self.price[symbol] = stock.AdjustedPrice
return self.recent_universe
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
if self.universe_size_property == 500 or self.universe_size_property == 1000:
# select top n stocks by dollar volume
selected = [x for x in sorted([x for x in coarse if x.HasFundamentalData],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
elif self.universe_size_property == 3000:
selected = [x for x in coarse if x.HasFundamentalData]
for stock in selected:
symbol = stock.Symbol
self.price[symbol] = stock.AdjustedPrice
if symbol in self.metric:
continue
# create RollingWindow for specific stock symbol
# self.metric[symbol] = RollingWindow[float](self.period)
self.metric[symbol] = None
# subscribe to Brain Language Metrics data
dataset_symbol = self.AddData(BrainCompanyFilingLanguageMetrics10K , symbol).Symbol
# warmup Brain Language Metrics data
history = self.History(dataset_symbol, 3*30, Resolution.Daily)
# self.Debug(f"We got {len(history)} items from our history request for {dataset_symbol}")
if not history.empty:
metrics = []
for metric_value in self.metric_values:
m = getattr(history['reportsentiment'].iloc[-1], metric_value)
metrics.append(m)
# sent = history['reportsentiment'].iloc[-1].Sentiment
self.metric[symbol] = (history.iloc[-1].reportdate, metrics[0], metrics[1])#, metrics[2])
# store metric symbol under stock symbol
self.metric_symbols[symbol] = dataset_symbol
# return stock, which have short interest data ready
return [x.Symbol for x in selected if x.Symbol in self.metric and x.Symbol in self.price]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0
and ((x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE"))]
if self.universe_size_property == 3000:
fine = sorted(fine, key = lambda x:x.MarketCap, reverse=True)[:self.coarse_count]
self.recent_universe = [x.Symbol for x in fine]
metric_cnt = len(self.metric_values)
for ms_i in range(metric_cnt):
metric = { stock.Symbol : self.metric[stock.Symbol][ms_i+1] for stock in fine \
if stock.Symbol in self.metric and \
self.metric[stock.Symbol] is not None and \
self.metric[stock.Symbol][ms_i+1] is not None and \
(self.Time - self.metric[stock.Symbol][0]).days <= 30
}
if len(metric) < self.portfolio_size_property:
continue
# sorting by metric
sorted_by_metric = sorted(metric.items(), key = lambda x: x[1], reverse=True)
percentile = int(len(sorted_by_metric) / self.portfolio_size_property)
long = [x[0] for x in sorted_by_metric[:percentile]]
short = [x[0] for x in sorted_by_metric[-percentile:]]
# calculate quantity for every stock in every portfolio
long_cnt = len(long)
short_cnt = len(short)
for symbol in long:
q = int(((self.Portfolio.TotalPortfolioValue / metric_cnt) / long_cnt) / self.price[symbol])
if symbol not in self.traded_quantity:
self.traded_quantity[symbol] = 0
self.traded_quantity[symbol] += q
for symbol in short:
q = -int(((self.Portfolio.TotalPortfolioValue / metric_cnt) / short_cnt) / self.price[symbol])
if symbol not in self.traded_quantity:
self.traded_quantity[symbol] = 0
self.traded_quantity[symbol] += q
# self.short = []
# self.long = []
return list(self.traded_quantity.keys())
def OnData(self, data):
# update metric value for each stock
for stock_symbol, metric_symbol in self.metric_symbols.items():
# check if there are data for subscribed metric_symbol
if metric_symbol in data and data[metric_symbol]:
metrics = []
for metric_value in self.metric_values:
m = getattr(data[metric_symbol].ReportSentiment, metric_value)
metrics.append(m)
# sent = data[metric_symbol].ReportSentiment.Sentiment
# update metric value for specific stock
self.metric[stock_symbol] = (self.Time, metrics[0], metrics[1])#, metrics[2])
# monthly rebalance
if not self.rebalance_flag:
return
self.rebalance_flag = False
if self.universe_size_property == 3000:
if self.Time.year in [2014, 2016] and self.Time.month == 6:
self.Liquidate()
return
self.Liquidate()
for symbol, q in self.traded_quantity.items():
if q != 0:
self.MarketOrder(symbol, q)
# long_c = len(self.long)
# short_c = len(self.short)
# for symbol in self.long:
# self.SetHoldings(symbol, 1/long_c)
# for symbol in self.short:
# self.SetHoldings(symbol, -1/short_c)
# self.weight.clear()
# self.long.clear()
# self.short.clear()
self.traded_quantity.clear()
def Selection(self):
# if metric is market, hold SPY only without rebalance and selection
# if self.metric_property[0] == self.market.Value:
# if not self.Portfolio[self.market].Invested:
# self.SetHoldings(self.market, 1)
# else:
# new universe selection every three months
if self.Time.month % 3 == 0:
self.selection_flag = True
# rebalance once a month
self.rebalance_flag = True
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl short interest data.
class QuandlShortVolume(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'SHORTVOLUME' # also 'TOTALVOLUME' is accesible
================================================
FILE: static/strategies/intraday-seasonality-in-bitcoin.py
================================================
# https://quantpedia.com/strategies/intraday-seasonality-in-bitcoin/
#
# The investment universe consists of Bitcoin and the data are obtained from Gemini exchange. To exploit the seasonality, open a long position in the BTC at 22:00 (UTC +0) and hold it for two hours. The position is closed after the two hour holding period.
#
# QC implementation changes:
# - BTC data are obtained from Bitfinex exchange.
# region imports
from AlgorithmImports import *
# endregion
class OvernightSeasonalityinBitcoin(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1)
self.SetCash(100000)
# NOTE Coinbase Pro, CoinAPI, and Bitfinex data is all set in UTC Time. This means that when accessing data from this brokerage, all data will be time stamped in UTC Time.
self.crypto = self.AddCrypto("BTCUSD", Resolution.Minute, Market.Bitfinex)
self.crypto.SetLeverage(10)
self.crypto.SetFeeModel(CustomFeeModel())
self.crypto = self.crypto.Symbol
self.open_trade_hour:int = 22
self.close_trade_hour:int = 0
def OnData(self, data):
if self.crypto in data and data[self.crypto]:
time:datetime.datetime = self.UtcTime
# open long position
if time.hour == self.open_trade_hour and time.minute == 0:
self.SetHoldings(self.crypto, 1)
# close position
if time.hour == self.close_trade_hour and time.minute == 0:
if self.Portfolio[self.crypto].Invested:
self.Liquidate(self.crypto)
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/january-barometer.py
================================================
# https://quantpedia.com/strategies/january-barometer/
#
# Invest in the equity market in each January. Stay invested in equity markets (via ETF, fund, or futures) only if January return is positive; otherwise, switch investments to T-Bills.
from AlgorithmImports import *
class JanuaryBarometer(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
data = self.AddEquity("SPY", Resolution.Daily)
data.SetLeverage(10)
self.market = data.Symbol
data = self.AddEquity("BIL", Resolution.Daily)
data.SetLeverage(10)
self.t_bills = data.Symbol
self.start_price = None
self.recent_month = -1
def OnData(self, data):
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
if (
self.Securities[self.market].GetLastData()
and self.Securities[self.t_bills].GetLastData()
):
if (
self.Time.date()
- self.Securities[self.market].GetLastData().Time.date()
).days < 5 and (
self.Time.date()
- self.Securities[self.t_bills].GetLastData().Time.date()
).days < 5:
if self.Time.month == 1:
self.Liquidate(self.t_bills)
self.SetHoldings(self.market, 1)
self.start_price = self.Securities[self.market].Price
if self.Time.month == 2 and self.start_price:
returns = (
self.Securities[self.market].Price - self.start_price
) / self.start_price
if returns > 0:
self.SetHoldings(self.market, 1)
else:
self.start_price = None
self.Liquidate(self.market)
self.SetHoldings(self.t_bills, 1)
else:
self.Liquidate()
else:
self.Liquidate()
================================================
FILE: static/strategies/low-volatility-factor-effect-in-stocks.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/low-volatility-factor-effect-in-stocks-long-only-version/
#
# The investment universe consists of global large-cap stocks (or US large-cap stocks). At the end of each month, the investor constructs
# equally weighted decile portfolios by ranking the stocks on the past three-year volatility of weekly returns. The investor goes long
# stocks in the top decile (stocks with the lowest volatility).
#
# QC implementation changes:
# - Top quartile (stocks with the lowest volatility) is selected instead of decile.
import numpy as np
class LowVolatilityFactorEffectStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.period = 12*21
self.coarse_count = 3000
self.last_coarse = []
self.data = {}
self.long = []
self.selection_flag = True
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store daily price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
# selected = [x.Symbol
# for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
# key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0]
# market cap sorting
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
fine = sorted_by_market_cap[:self.coarse_count]
weekly_vol = {x.Symbol : self.data[x.Symbol].volatility() for x in fine}
# volatility sorting
sorted_by_vol = sorted(weekly_vol.items(), key = lambda x: x[1], reverse = True)
quartile = int(len(sorted_by_vol) / 4)
self.long = [x[0] for x in sorted_by_vol[-quartile:]]
return self.long
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long:
self.Liquidate(symbol)
for symbol in self.long:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1 / len(self.long))
self.long.clear()
def Selection(self):
self.selection_flag = True
class SymbolData():
def __init__(self, period):
self.price = RollingWindow[float](period)
def update(self, value):
self.price.Add(value)
def is_ready(self) -> bool:
return self.price.IsReady
def volatility(self) -> float:
closes = [x for x in self.price]
# Weekly volatility calc.
separete_weeks = [closes[x:x+5] for x in range(0, len(closes), 5)]
weekly_returns = [(x[0] - x[-1]) / x[-1] for x in separete_weeks]
return np.std(weekly_returns)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/market-sentiment-and-an-overnight-anomaly.py
================================================
# https://quantpedia.com/strategies/market-sentiment-and-an-overnight-anomaly/
#
# The investment universe consists of SPY ETF, and the price of SPY, price of VIX and Brain Market Sentiment (BMS) indicator
# are used to identify the market sentiment. The investor buys SPY ETF and holds it overnight; when the price of SPY is above its 20-day moving average,
# the price of VIX is below its moving average, and the value of the BMS indicator is greater than its 20-day moving average.
# Note that the authors suggest using this strategy as an overlay when deciding whether to make a trade rather than using this system on its own.
#
# QC Implementation:
# region imports
from AlgorithmImports import *
# endregion
class MarketSentimentAndAnOvernightAnomaly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.period: int = 20 # sma period
self.weight: float = 0
self.price_data: dict = {}
self.spy_symbol: Symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
self.vix_symbol: Symbol = self.AddData(
QuandlVix, "CBOE/VIX", Resolution.Daily
).Symbol # starts in 2004
self.bms_symbol: Symbol = self.AddData(
QuantpediaBMS, "BMS_GLOBAL", Resolution.Daily
).Symbol # starts in 2018
for symbol in [self.spy_symbol, self.vix_symbol, self.bms_symbol]:
self.price_data[symbol] = RollingWindow[float](self.period)
def OnData(self, data: Slice):
# calculate signal from SPY 16 minutes before close
if (
self.spy_symbol in data
and data[self.spy_symbol]
and self.Time.hour == 15
and self.Time.minute == 44
):
weight: float = 0.0
for symbol in [self.spy_symbol, self.vix_symbol, self.bms_symbol]:
# trade only sub-strategies with underlying data available
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
<= 3
):
price: float = self.Securities[symbol].GetLastData().Price
rolling_window: RollingWindow = self.price_data[symbol]
if rolling_window.IsReady and self.GetSignal(
price,
rolling_window,
True if symbol != self.vix_symbol else False,
):
weight += 1 / 3
rolling_window.Add(price)
q: int = int(
(self.Portfolio.TotalPortfolioValue * weight)
/ data[self.spy_symbol].Value
)
if q != 0:
self.MarketOnCloseOrder(self.spy_symbol, q)
self.MarketOnOpenOrder(self.spy_symbol, -q)
def GetSignal(
self, curr_value: float, rolling_window: RollingWindow, signal_above_sma: bool
) -> bool:
prices: list[float] = [x for x in rolling_window]
moving_average: float = sum(prices) / len(prices)
result: bool = False
if signal_above_sma and (curr_value > moving_average):
result = True
elif not signal_above_sma and (curr_value < moving_average):
result = True
return result
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBMS(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/index/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data: QuantpediaBMS = QuantpediaBMS()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split: list = line.split(",")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data.Value = float(split[2])
return data
class QuandlVix(PythonQuandl):
def __init__(self):
self.ValueColumnName = "VIX Close"
================================================
FILE: static/strategies/momentum-and-reversal-combined-with-volatility-effect-in-stocks.py
================================================
# https://quantpedia.com/strategies/momentum-and-reversal-combined-with-volatility-effect-in-stocks/
#
# The investment universe consists of NYSE, AMEX, and NASDAQ stocks with prices higher than $5 per share. At the beginning of each month,
# the sample is divided into equal halves, at the size median, and only larger stocks are used. Then each month, realized returns and realized
# (annualized) volatilities are calculated for each stock for the past six months. One week (seven calendar days) prior to the beginning of
# each month is skipped to avoid biases due to microstructures. Stocks are then sorted into quintiles based on their realized past returns
# and past volatility. The investor goes long on stocks from the highest performing quintile from the highest volatility group and short on
# stocks from the lowest-performing quintile from the highest volatility group. Stocks are equally weighted and held for six months
# (therefore, 1/6 of the portfolio is rebalanced every month).
#
# QC implementation changes:
# - Instead of all listed stock, we select 1000 most liquid stocks from QC filtered stock universe (~8000 stocks) due to time complexity issues tied to whole universe filtering.
import numpy as np
from AlgorithmImports import *
class MomentumReversalCombinedWithVolatilityEffectinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2002, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
# EW Tranching.
self.holding_period = 6
self.managed_queue = []
# Daily price data.
self.data = {}
self.period = 6 * 21
self.coarse_count = 1000
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
self.data[symbol].LastPrice = stock.AdjustedPrice
if not self.selection_flag:
return Universe.Unchanged
# selected = [x for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5]
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5], \
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]
# Warmup price rolling windows.
for stock in selected:
symbol = stock.Symbol
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
self.data[symbol].LastPrice = close
return [x.Symbol for x in selected if self.data[x.Symbol].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
# top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
# else:
# top_by_market_cap = fine
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
half = int(len(sorted_by_market_cap) / 2)
top_by_market_cap = [x.Symbol for x in sorted_by_market_cap][:half]
# Performance and volatility tuple.
perf_volatility = {}
for symbol in top_by_market_cap:
performance = self.data[symbol].performance()
annualized_volatility = self.data[symbol].volatility()
perf_volatility[symbol] = (performance, annualized_volatility)
long = []
short = []
if len(perf_volatility) >= 5:
sorted_by_perf = sorted(perf_volatility.items(), key = lambda x: x[1][0], reverse = True)
quintile = int(len(sorted_by_perf) / 5)
top_by_perf = [x[0] for x in sorted_by_perf[:quintile]]
low_by_perf = [x[0] for x in sorted_by_perf[-quintile:]]
sorted_by_vol = sorted(perf_volatility.items(), key = lambda x: x[1][1], reverse = True)
quintile = int(len(sorted_by_vol) / 5)
top_by_vol = [x[0] for x in sorted_by_vol[:quintile]]
low_by_vol = [x[0] for x in sorted_by_vol[-quintile:]]
long = [x for x in top_by_perf if x in top_by_vol]
short = [x for x in low_by_perf if x in top_by_vol]
if len(long) != 0:
long_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(long)
# symbol/quantity collection
long_symbol_q = [(x, np.ceil(long_w / self.data[x].LastPrice)) for x in long]
else:
long_symbol_q = []
if len(short) != 0:
short_w = self.Portfolio.TotalPortfolioValue / self.holding_period / len(short)
# symbol/quantity collection
short_symbol_q = [(x, -np.ceil(short_w / self.data[x].LastPrice)) for x in short]
else:
short_symbol_q = []
self.managed_queue.append(RebalanceQueueItem(long_symbol_q + short_symbol_q))
return long + short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
remove_item = None
# Rebalance portfolio
for item in self.managed_queue:
if item.holding_period == self.holding_period:
for symbol, quantity in item.symbol_q:
if self.Securities[symbol].Price != 0 and self.Securities[symbol].IsTradable:
self.MarketOrder(symbol, -quantity)
remove_item = item
# Trade execution
if item.holding_period == 0:
open_symbol_q = []
for symbol, quantity in item.symbol_q:
if self.Securities[symbol].Price != 0 and self.Securities[symbol].IsTradable:
self.MarketOrder(symbol, quantity)
open_symbol_q.append((symbol, quantity))
# Only opened orders will be closed
item.symbol_q = open_symbol_q
item.holding_period += 1
# We need to remove closed part of portfolio after loop. Otherwise it will miss one item in self.managed_queue.
if remove_item:
self.managed_queue.remove(remove_item)
def Selection(self):
self.selection_flag = True
class RebalanceQueueItem():
def __init__(self, symbol_q):
# symbol/quantity collections
self.symbol_q = symbol_q
self.holding_period = 0
class SymbolData():
def __init__(self, symbol, period):
self.Symbol = symbol
self.Price = RollingWindow[float](period)
self.LastPrice = 0
def update(self, value):
self.Price.Add(value)
def is_ready(self):
return self.Price.IsReady
def update(self, close):
self.Price.Add(close)
def volatility(self):
closes = np.array([x for x in self.Price][5:]) # Skip last week.
daily_returns = closes[:-1] / closes[1:] - 1
return np.std(daily_returns) * np.sqrt(252 / (len(closes)))
def performance(self):
closes = [x for x in self.Price][5:] # Skip last week.
return (closes[0] / closes[-1] - 1)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/momentum-effect-in-commodities.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/1-month-momentum-in-commodities/
#
# Create a universe of tradable commodity futures. Rank futures performance for each commodity for the last 12 months and divide them into quintiles.
# Go long on the quintile with the highest momentum and go short on the quintile with the lowest momentum. Rebalance each month.
class MomentumEffectCommodities(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = [
"CME_S1", # Soybean Futures, Continuous Contract
"CME_W1", # Wheat Futures, Continuous Contract
"CME_SM1", # Soybean Meal Futures, Continuous Contract
"CME_BO1", # Soybean Oil Futures, Continuous Contract
"CME_C1", # Corn Futures, Continuous Contract
"CME_O1", # Oats Futures, Continuous Contract
"CME_LC1", # Live Cattle Futures, Continuous Contract
"CME_FC1", # Feeder Cattle Futures, Continuous Contract
"CME_LN1", # Lean Hog Futures, Continuous Contract
"CME_GC1", # Gold Futures, Continuous Contract
"CME_SI1", # Silver Futures, Continuous Contract
"CME_PL1", # Platinum Futures, Continuous Contract
"CME_CL1", # Crude Oil Futures, Continuous Contract
"CME_HG1", # Copper Futures, Continuous Contract
"CME_LB1", # Random Length Lumber Futures, Continuous Contract
"CME_NG1", # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
"CME_PA1", # Palladium Futures, Continuous Contract
"CME_RR1", # Rough Rice Futures, Continuous Contract
"CME_DA1", # Class III Milk Futures
"ICE_RS1", # Canola Futures, Continuous Contract
"ICE_GO1", # Gas Oil Futures, Continuous Contract
"CME_RB2", # Gasoline Futures, Continuous Contract
"CME_KW2", # Wheat Kansas, Continuous Contract
"ICE_WT1", # WTI Crude Futures, Continuous Contract
"ICE_CC1", # Cocoa Futures, Continuous Contract
"ICE_CT1", # Cotton No. 2 Futures, Continuous Contract
"ICE_KC1", # Coffee C Futures, Continuous Contract
"ICE_O1", # Heating Oil Futures, Continuous Contract
"ICE_OJ1", # Orange Juice Futures, Continuous Contract
"ICE_SB1", # Sugar No. 11 Futures, Continuous Contract
]
self.period = 12 * 21
self.SetWarmUp(self.period, Resolution.Daily)
self.data = {}
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[symbol] = self.ROC(symbol, self.period, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
if self.IsWarmingUp:
return
# rebalance once a month
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
perf = { x[0] : x[1].Current.Value for x in self.data.items() if self.data[x[0]].IsReady and x[0] in data and data[x[0]] }
long = []
short = []
if len(perf) >= 5:
sorted_by_performance = sorted(perf.items(), key = lambda x:x[1], reverse=True)
quintile = int(len(sorted_by_performance) / 5)
long = [x[0] for x in sorted_by_performance[:quintile]]
short = [x[0] for x in sorted_by_performance[-quintile:]]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel():
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/momentum-factor-and-style-rotation-effect.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/momentum-factor-and-style-rotation-effect/
#
# Russell’s ETFs for six equity styles are used
# (small-cap value, mid-cap value, large-cap value, small-cap growth, mid-cap growth, large-cap growth).
# Each month, the investor calculates 12-month momentum for each style and goes long on the winner and short on the loser.
# The portfolio is rebalanced each month.
#
# QC Implementation:
# - Trading IVW in 10/2020 is skipped due to data error.
class MomentumFactorAndStyleRotationEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.tickers = [
'IWS', # iShares Russell Midcap Value ETF
'IWP', # iShares Russell Midcap Growth ETF
'IWN', # iShares Russell 2000 Value ETF
'IWO', # iShares Russell 2000 Growth ETF
'IVE', # iShares S&P 500 Value ETF
'IVW' # iShares S&P 500 Growth ETF
]
self.mom = {}
self.period = 12 * 21
self.SetWarmUp(self.period)
for ticker in self.tickers:
security = self.AddEquity(ticker, Resolution.Daily)
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
self.mom[security.Symbol] = self.MOM(security.Symbol, self.period)
self.recent_month = -1
def OnData(self, data):
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
mom_ready = [ s for s in self.mom if self.mom[s].IsReady and s in data]
if mom_ready:
sorted_mom = sorted(mom_ready, key = lambda x: self.mom[x].Current.Value, reverse=True)
for symbol in sorted_mom[1:-1]:
if self.Portfolio[symbol].Invested:
self.Liquidate(symbol)
winner = sorted_mom[0]
loser = sorted_mom[-1]
if self.Securities[winner].Price != 0 and self.Securities[winner].IsTradable:
if (self.Time.month == 10 and self.Time.year == 2020) and winner.Value == 'IVW': # prevent data error
self.Liquidate(winner)
else:
self.SetHoldings(winner, 1)
if self.Securities[loser].Price != 0 and self.Securities[loser].IsTradable:
if (self.Time.month == 10 and self.Time.year == 2020) and loser.Value == 'IVW': # prevent data error
self.Liquidate(loser)
else:
self.SetHoldings(loser, -1)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/momentum-factor-combined-with-asset-growth-effect.py
================================================
# https://quantpedia.com/strategies/momentum-factor-combined-with-asset-growth-effect/
#
# The investment universe consists of NYSE, AMEX and NASDAQ stocks (data for the backtest in the source paper are from Compustat).
# Stocks with a market capitalization less than the 20th NYSE percentile (smallest stocks) are removed. The asset growth variable
# is defined as the yearly percentage change in balance sheet total assets. Data from year t-2 to t-1 are used to calculate asset
# growth, and July is the cut-off month. Every month, stocks are then sorted into deciles based on asset growth and only stocks
# with the highest asset growth are used. The next step is to sort stocks from the highest asset growth decile into quintiles,
# based on their past 11-month return (with the last month’s performance skipped in the calculation). The investor then goes long
# on stocks with the strongest momentum and short on stocks with the weakest momentum. The portfolio is equally weighted and is
# rebalanced monthly. The investor holds long-short portfolios only during February-December -> January is excluded as this month
# has been repeatedly documented as a negative month for a momentum strategy (see “January Effect Filter and Momentum in Stocks”).
#
# QC implementation changes:
# - Universe consists of 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from AlgorithmImports import *
class MomentumFactorAssetGrowthEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# Monthly close data.
self.data = {}
self.period = 13
self.total_assets_history_period = 2
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.spy_consolidator = TradeBarConsolidator(timedelta(days=21))
self.spy_consolidator.DataConsolidated += self.CustomHandler
self.SubscriptionManager.AddConsolidator(self.symbol, self.spy_consolidator)
self.data[self.symbol] = SymbolData(self.symbol, self.period, self.total_assets_history_period)
# Warmup market history.
history = self.History(self.symbol, self.period, Resolution.Daily)
if not history.empty:
closes = history.loc[self.symbol].close
closes_len = len(closes.keys())
# Find monthly closes.
for index, time_close in enumerate(closes.iteritems()):
# index out of bounds check.
if index + 1 < closes_len:
date_month = time_close[0].date().month
next_date_month = closes.keys()[index + 1].month
# Found last day of month.
if date_month != next_date_month:
self.data[self.symbol].update(time_close[1])
self.coarse_count = 500
self.long = []
self.short = []
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol), self.Selection)
def CustomHandler(self, sender, consolidated):
self.data[self.symbol].update(consolidated.Close)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
# Update the rolling window every month.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(symbol, self.period, self.total_assets_history_period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
closes_len = len(closes.keys())
# Find monthly closes.
for index, time_close in enumerate(closes.iteritems()):
# index out of bounds check.
if index + 1 < closes_len:
date_month = time_close[0].date().month
next_date_month = closes.keys()[index + 1].month
# Found last day of month.
if date_month != next_date_month:
self.data[symbol].update(time_close[1])
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths > 0 and
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
# top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
# else:
# top_by_market_cap = fine
top_by_market_cap = fine
# Asset growth calc.
asset_growth = {}
for stock in top_by_market_cap:
symbol = stock.Symbol
if self.data[symbol].asset_data_is_ready():
asset_growth[symbol] = self.data[symbol].asset_growth()
self.data[symbol].update_assets(stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths)
sorted_by_growth = sorted(asset_growth.items(), key = lambda x: x[1], reverse = True)
decile = int(len(sorted_by_growth) / 10)
top_by_growth = [x[0] for x in sorted_by_growth][:decile]
performance = { x : self.data[x].performance(1) for x in top_by_growth}
sorted_by_performance = sorted(performance.items(), key = lambda x: x[1], reverse = True)
quintile = int(len(sorted_by_performance) / 5)
self.long = [x[0] for x in sorted_by_performance][:quintile]
self.short = [x[0] for x in sorted_by_performance][-quintile:]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
for symbol in self.long:
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def Selection(self):
# Exclude January trading.
if self.Time.month != 12:
self.selection_flag = True
else:
self.Liquidate()
class SymbolData():
def __init__(self, symbol, period, total_assets_history_period):
self.Symbol = symbol
self.Price = RollingWindow[float](period)
self.TotalAssets = RollingWindow[float](total_assets_history_period)
def update(self, value):
self.Price.Add(value)
def update_assets(self, assets_value):
self.TotalAssets.Add(assets_value)
def asset_data_is_ready(self) -> bool:
return self.TotalAssets.IsReady
def asset_growth(self) -> float:
asset_values = [x for x in self.TotalAssets]
return (asset_values[0] - asset_values[1]) / asset_values[1]
def is_ready(self) -> bool:
return self.Price.IsReady
# Performance, one month skipped.
def performance(self, values_to_skip = 0) -> float:
closes = [x for x in self.Price][values_to_skip:]
return (closes[0] / closes[-1] - 1)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/momentum-factor-effect-in-stocks.py
================================================
# https://quantpedia.com/strategies/momentum-factor-effect-in-stocks/
#
# The investment universe consists of NYSE, AMEX, and NASDAQ stocks. We define momentum as the past 12-month return, skipping the most
# recent month’s return (to avoid microstructure and liquidity biases). To capture “momentum”, UMD portfolio goes long stocks that have
# high relative past one-year returns and short stocks that have low relative past one-year returns.
#
# QC implementation changes:
# - Instead of all listed stock, we select top 500 stocks by market cap from QC stock universe.
from AlgorithmImports import *
class MomentumFactorEffectinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.weight = {}
self.data = {}
self.period = 12 * 21
self.quantile = 5
self.coarse_count = 500
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(symbol), self.TimeRules.AfterMarketOpen(symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].Add(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
# selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5]
selected = [x.Symbol
for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = RollingWindow[float](self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].Add(close)
return [x for x in selected if self.data[x].IsReady]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
# if len(fine) > self.coarse_count:
# sorted_by_market_cap = sorted(fine, key = lambda x:x.MarketCap, reverse=True)
# top_by_market_cap = [x for x in sorted_by_market_cap[:self.coarse_count]]
# else:
# top_by_market_cap = fine
perf = {x.Symbol : self.data[x.Symbol][0] / self.data[x.Symbol][self.period-1] - 1 for x in fine}
if len(perf) >= self.quantile:
sorted_by_perf = sorted(perf.items(), key = lambda x:x[1], reverse=True)
quantile = int(len(sorted_by_perf) / self.quantile)
long = [x[0] for x in sorted_by_perf[:quantile]]
short = [x[0] for x in sorted_by_perf[-quantile:]]
long_count = len(long)
short_count = len(short)
for symbol in long:
self.weight[symbol] = 1 / long_count
for symbol in short:
self.weight[symbol] = -1 / short_count
return list(self.weight.keys())
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.weight:
self.Liquidate(symbol)
for symbol, w in self.weight.items():
if symbol in data and data[symbol]:
self.SetHoldings(symbol, w)
self.weight.clear()
def Selection(self):
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/momentum-in-mutual-fund-returns.py
================================================
# https://quantpedia.com/strategies/momentum-in-mutual-fund-returns/
#
# The investment universe consists of equity funds from the CRSP Mutual Fund database.
# This universe is then shrunk to no-load funds (to remove entrance fees).
# Investors then sort mutual funds based on their past 6-month return and divide them into deciles.
# The top decile of mutual funds is then picked into an investment portfolio (equally weighted), and funds are held for three months.
# Other measures of momentum could also be used in sorting (fund’s closeness to 1 year high in NAV and momentum factor loading),
# and it is highly probable that the combined predictor would have even better results than only the simple 6-month momentum.
#
# QC Implementation:
# - Universe consist of approximately 850 mutual funds.
#region imports
from AlgorithmImports import *
#endregion
class MomentuminMutualFundReturns(QCAlgorithm):
def Initialize(self):
# NOTE: most of the data start from 2014 and until 2015 there wasn't any trade
self.SetStartDate(2014, 1, 1)
self.SetCash(100000)
self.data = {}
self.symbols = []
self.period = 21 * 6 # Storing 6 months of daily prices
self.quantile = 10
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
# Load csv file with etf symbols and split line with semi-colon
etf_symbols_csv = self.Download("data.quantpedia.com/backtesting_data/equity/mutual_funds/symbols.csv")
splitted_csv = etf_symbols_csv.split(';')
for symbol in splitted_csv:
self.symbols.append(symbol)
# Subscribe for QuantpediaETF by etf symbol, then set fee model and leverage
data = self.AddData(QuantpediaETF, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[symbol] = RollingWindow[float](self.period)
self.recent_month = -1
def OnData(self, data):
# Update daily prices of etfs
for symbol in self.symbols:
if symbol in data and data[symbol]:
price = data[symbol].Value
self.data[symbol].Add(price)
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
# Rebalance quarterly
if self.recent_month % 3 != 0:
return
performance = {}
for symbol in self.symbols:
# If data for etf are ready calculate it's 6 month performance
if self.data[symbol].IsReady:
if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days <= 3:
prices = [x for x in self.data[symbol]]
performance[symbol] = (prices[0] - prices[-1]) / prices[-1]
if len(performance) < self.quantile:
self.Liquidate()
return
decile = int(len(performance) / self.quantile)
# sort dictionary by performance and based on it create sorted list
sorted_by_perf = [x[0] for x in sorted(performance.items(), key=lambda item: item[1], reverse=True)]
# select top decile etfs for investment based on performance
long = sorted_by_perf[:decile]
# Trade execution
invested_etfs = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested_etfs:
if symbol not in long:
self.Liquidate(symbol)
long_length = len(long)
for symbol in long:
self.SetHoldings(symbol, 1 / long_length)
# Quantpedia data
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaETF(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/equity/mutual_funds/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaETF()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['settle'] = float(split[1])
data.Value = float(split[1])
return data
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/option-expiration-week-effect.py
================================================
# https://quantpedia.com/strategies/option-expiration-week-effect/
#
# Investors choose stocks from the S&P 100 index as his/her investment universe (stocks could be easily tracked via ETF or index fund).
# He/she then goes long S&P 100 stocks during the option-expiration week and stays in cash during other days.
from AlgorithmImports import *
class OptionExpirationWeekEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(10000)
self.symbol = self.AddEquity("OEF", Resolution.Minute).Symbol
option = self.AddOption("OEF")
option.SetFilter(-3, 3, timedelta(0), timedelta(days = 60))
self.SetBenchmark("OEF")
self.near_expiry = datetime.min
self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Monday), self.TimeRules.AfterMarketOpen(self.symbol, 1), self.Rebalance)
def OnData(self, slice):
if self.Time.date() == self.near_expiry.date():
self.Liquidate()
def Rebalance(self):
calendar = self.TradingCalendar.GetDaysByType(TradingDayType.OptionExpiration, self.Time, self.EndDate)
expiries = [i.Date for i in calendar]
if len(expiries) == 0: return
self.near_expiry = expiries[0]
if (self.near_expiry - self.Time).days <= 5:
self.SetHoldings(self.symbol, 1)
================================================
FILE: static/strategies/paired-switching.py
================================================
# https://quantpedia.com/strategies/paired-switching/
#
# This strategy is very flexible. Investors could use stocks, funds, or ETFs as an investment vehicle. We show simple trading rules for a sample strategy
# from the source research paper. The investor uses two Vanguard funds as his investment vehicles – one equity fund (VFINX) and one government bond
from AlgorithmImports import *
# fund (VUSTX). These two funds have a negative correlation as they are proxies for two negatively correlated asset classes. The investor looks at the
# performance of the two funds over the prior quarter and buys the fund that has a higher return during the ranking period. The position is held for one
# quarter (the investment period). At the end of the investment period, the cycle is repeated.
class PairedSwitching(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2004, 1, 1)
self.SetCash(100000)
self.first_symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.second_symbol = self.AddEquity("AGG", Resolution.Daily).Symbol
self.recent_month = -1
def OnData(self, data):
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
if self.recent_month % 3 == 0:
if self.first_symbol in data and self.second_symbol in data:
history_call = self.History(
[self.first_symbol, self.second_symbol], timedelta(days=90)
)
if not history_call.empty:
first_bars = history_call.loc[self.first_symbol.Value]
last_p1 = first_bars["close"].iloc[0]
second_bars = history_call.loc[self.second_symbol.Value]
last_p2 = second_bars["close"].iloc[0]
# Calculates performance of funds over the prior quarter.
first_performance = (
float(self.Securities[self.first_symbol].Price) - float(last_p1)
) / (float(self.Securities[self.first_symbol].Price))
second_performance = (
float(self.Securities[self.second_symbol].Price)
- float(last_p2)
) / (float(self.Securities[self.second_symbol].Price))
# Buys the fund that has the higher return during the period.
if first_performance > second_performance:
if self.Securities[self.second_symbol].Invested:
self.Liquidate(self.second_symbol)
self.SetHoldings(self.first_symbol, 1)
else:
if self.Securities[self.first_symbol].Invested:
self.Liquidate(self.first_symbol)
self.SetHoldings(self.second_symbol, 1)
================================================
FILE: static/strategies/pairs-trading-with-country-etfs.py
================================================
# https://quantpedia.com/strategies/pairs-trading-with-country-etfs/
#
# The investment universe consists of 22 international ETFs. A normalized cumulative total return index is created for each ETF (dividends
# included), and the starting price during the formation period is set to $1 (price normalization). The selection of pairs is made after
# a 120 day formation period. Pair’s distance for all ETF pairs is calculated as the sum of squared deviations between two normalized
# price series. The top 5 pairs with the smallest distance are used in the subsequent 20 day trading period. The strategy is monitored
# daily, and trade is opened when the divergence between the pairs exceeds 0.5x the historical standard deviation. Investors go long
# on the undervalued ETF and short on the overvalued ETF. The trade is exited if a pair converges or after 20 days (if the pair does
# not converge within the next 20 business days). Pairs are weighted equally, and the portfolio is rebalanced on a daily basis.
#
# QC Implementation:
import numpy as np
from AlgorithmImports import *
import itertools as it
class PairsTradingwithCountryETFs(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = [
"EWA", # iShares MSCI Australia Index ETF
"EWO", # iShares MSCI Austria Investable Mkt Index ETF
"EWK", # iShares MSCI Belgium Investable Market Index ETF
"EWZ", # iShares MSCI Brazil Index ETF
"EWC", # iShares MSCI Canada Index ETF
"FXI", # iShares China Large-Cap ETF
"EWQ", # iShares MSCI France Index ETF
"EWG", # iShares MSCI Germany ETF
"EWH", # iShares MSCI Hong Kong Index ETF
"EWI", # iShares MSCI Italy Index ETF
"EWJ", # iShares MSCI Japan Index ETF
"EWM", # iShares MSCI Malaysia Index ETF
"EWW", # iShares MSCI Mexico Inv. Mt. Idx
"EWN", # iShares MSCI Netherlands Index ETF
"EWS", # iShares MSCI Singapore Index ETF
"EZA", # iShares MSCI South Africe Index ETF
"EWY", # iShares MSCI South Korea ETF
"EWP", # iShares MSCI Spain Index ETF
"EWD", # iShares MSCI Sweden Index ETF
"EWL", # iShares MSCI Switzerland Index ETF
"EWT", # iShares MSCI Taiwan Index ETF
"THD", # iShares MSCI Thailand Index ETF
"EWU", # iShares MSCI United Kingdom Index ETF
"SPY", # SPDR S&P 500 ETF
]
self.period = 120
self.max_traded_pairs = 5 # The top 5 pairs with the smallest distance are used.
self.history_price = {}
self.traded_pairs = []
self.traded_quantity = {}
for symbol in self.symbols:
data = self.AddEquity(symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
symbol_obj = data.Symbol
if symbol not in self.history_price:
self.history_price[symbol] = RollingWindow[float](self.period)
history = self.History(self.Symbol(symbol), self.period, Resolution.Daily)
if history.empty:
self.Log(f"Note enough data for {symbol} yet")
else:
closes = history.loc[symbol].close[:-1]
for time, close in closes.iteritems():
self.history_price[symbol].Add(close)
self.sorted_pairs = []
self.symbol_pairs = list(it.combinations(self.symbols, 2))
self.days = 20
def OnData(self, data):
# Update the price series everyday
for symbol in self.history_price:
symbol_obj = self.Symbol(symbol)
if symbol_obj in data and data[symbol_obj]:
price = data[symbol_obj].Value
self.history_price[symbol].Add(price)
# Start of trading period.
if self.days == 20:
# minimize the sum of squared deviations
distances = {}
for pair in self.symbol_pairs:
if self.history_price[pair[0]].IsReady and self.history_price[pair[1]].IsReady:
if (self.Time.date() - self.Securities[pair[0]].GetLastData().Time.date()).days <= 3 and (self.Time.date() - self.Securities[pair[1]].GetLastData().Time.date()).days <= 3:
distances[pair] = self.Distance([x for x in self.history_price[pair[0]]], [x for x in self.history_price[pair[1]]])
if len(distances) != 0:
self.sorted_pairs = sorted(distances.items(), key = lambda x: x[1])[:self.max_traded_pairs]
self.sorted_pairs = [x[0] for x in self.sorted_pairs]
self.Liquidate()
self.traded_pairs.clear()
self.traded_quantity.clear()
self.days = 0
self.days += 1
if self.sorted_pairs is None: return
pairs_to_remove = []
for pair in self.sorted_pairs:
# Calculate the spread of two price series.
price_a = [x for x in self.history_price[pair[0]]]
price_b = [x for x in self.history_price[pair[1]]]
norm_a = np.array(price_a) / price_a[-1]
norm_b = np.array(price_b) / price_b[-1]
spread = norm_a - norm_b
mean = np.mean(spread)
std = np.std(spread)
actual_spread = spread[0]
# Long-short position is opened when pair prices have diverged by two standard deviations.
traded_portfolio_value = self.Portfolio.TotalPortfolioValue / self.max_traded_pairs
if actual_spread > mean + 0.5*std or actual_spread < mean - 0.5*std:
if pair not in self.traded_pairs:
# open new position for pair, if there's place for it.
if len(self.traded_pairs) < self.max_traded_pairs:
symbol_a = pair[0]
symbol_b = pair[1]
a_price_norm = norm_a[0]
b_price_norm = norm_b[0]
a_price = price_a[0]
b_price = price_b[0]
# a etf's price > b etf's price
if a_price_norm > b_price_norm:
long_q = traded_portfolio_value / b_price # long b etf
short_q = -traded_portfolio_value / a_price # short a etf
if self.Securities.ContainsKey(symbol_a) and self.Securities.ContainsKey(symbol_b) and \
self.Securities[symbol_a].Price != 0 and self.Securities[symbol_a].IsTradable and \
self.Securities[symbol_b].Price != 0 and self.Securities[symbol_b].IsTradable:
self.MarketOrder(symbol_a, short_q)
self.MarketOrder(symbol_b, long_q)
self.traded_quantity[pair] = (short_q, long_q)
self.traded_pairs.append(pair)
# b etf's price > a etf's price
else:
long_q = traded_portfolio_value / a_price # long a etf
short_q = -traded_portfolio_value / b_price # short b etf
if self.Securities.ContainsKey(symbol_a) and self.Securities.ContainsKey(symbol_b) and \
self.Securities[symbol_a].Price != 0 and self.Securities[symbol_a].IsTradable and \
self.Securities[symbol_b].Price != 0 and self.Securities[symbol_b].IsTradable:
self.MarketOrder(symbol_a, long_q)
self.MarketOrder(symbol_b, short_q)
self.traded_quantity[pair] = (long_q, short_q)
self.traded_pairs.append(pair)
# The position is closed when prices revert back.
else:
if pair in self.traded_pairs and pair in self.traded_quantity:
# make opposite order to opened position
self.MarketOrder(pair[0], -self.traded_quantity[pair][0])
self.MarketOrder(pair[1], -self.traded_quantity[pair][1])
pairs_to_remove.append(pair)
for pair in pairs_to_remove:
self.traded_pairs.remove(pair)
del self.traded_quantity[pair]
def Distance(self, price_a, price_b):
# Calculate the sum of squared deviations between two normalized price series.
norm_a = np.array(price_a) / price_a[-1]
norm_b = np.array(price_b) / price_b[-1]
return sum((norm_a - norm_b)**2)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/pairs-trading-with-stocks
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/pairs-trading-with-stocks/
#
# The investment universe consists of stocks from NYSE, AMEX, and NASDAQ, while illiquid stocks are removed from the investment universe. Cumulative
# total return index is then created for each stock (dividends included), and the starting price during the formation period is set to $1 (price normalization).
# Pairs are formed over twelve months (formation period) and are then traded in the next six-month period (trading period). The matching partner for each stock
# is found by looking for the security that minimizes the sum of squared deviations between two normalized price series. Top 20 pairs with the smallest historical
# distance measure are then traded, and a long-short position is opened when pair prices have diverged by two standard deviations, and the position is closed
# when prices revert.
#
# QC implementation changes:
# - Universe consists of top 500 most liquid US stocks with price > 5$.
# - Maximum number of pairs traded at one time is set to 5.
import numpy as np
import itertools as it
class PairsTradingwithStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2005, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
# Daily price data.
self.history_price = {}
self.period = 12 * 21
# Equally weighted brackets.
self.max_traded_pairs = 5
self.traded_pairs = []
self.traded_quantity = {}
self.sorted_pairs = []
self.coarse_count = 500
self.month = 6
self.selection_flag = True
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
for security in changes.RemovedSecurities:
symbol = security.Symbol
if symbol in self.history_price:
del self.history_price[symbol]
symbols = [x for x in self.history_price.keys() if x != self.symbol]
self.symbol_pairs = list(it.combinations(symbols, 2))
# minimize the sum of squared deviations
distances = {}
for pair in self.symbol_pairs:
if self.history_price[pair[0]].IsReady and self.history_price[pair[1]].IsReady:
distances[pair] = self.Distance(self.history_price[pair[0]], self.history_price[pair[1]])
if len(distances) != 0:
self.sorted_pairs = [x[0] for x in sorted(distances.items(), key = lambda x: x[1])[:20]]
self.Liquidate()
self.traded_pairs.clear()
self.traded_quantity.clear()
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
if symbol in self.history_price:
self.history_price[symbol].Add(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5 and x.Market == 'usa'],
key=lambda x: x.DollarVolume, reverse=True)[:self.coarse_count]
# Warmup price rolling windows.
for stock in selected:
symbol = stock.Symbol
if symbol in self.history_price:
continue
self.history_price[symbol] = RollingWindow[float](self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.history_price[symbol].Add(close)
return [x.Symbol for x in selected if self.history_price[x.Symbol].IsReady]
def OnData(self, data):
if self.sorted_pairs is None: return
pairs_to_remove = []
for pair in self.sorted_pairs:
# Calculate the spread of two price series.
price_a = [x for x in self.history_price[pair[0]]]
price_b = [x for x in self.history_price[pair[1]]]
norm_a = np.array(price_a) / price_a[-1]
norm_b = np.array(price_b) / price_b[-1]
spread = norm_a - norm_b
mean = np.mean(spread)
std = np.std(spread)
actual_spread = spread[0]
# Long-short position is opened when pair prices have diverged by two standard deviations.
traded_portfolio_value = self.Portfolio.TotalPortfolioValue / self.max_traded_pairs
if actual_spread > mean + 2*std or actual_spread < mean - 2*std:
if pair not in self.traded_pairs:
# open new position for pair, if there's place for it.
if len(self.traded_pairs) < self.max_traded_pairs:
symbol_a = pair[0]
symbol_b = pair[1]
a_price_norm = norm_a[0]
b_price_norm = norm_b[0]
a_price = price_a[0]
b_price = price_b[0]
# a stock's price > b stock's price
if a_price_norm > b_price_norm:
long_q = traded_portfolio_value / b_price # long b stock
short_q = -traded_portfolio_value / a_price # short a stock
if self.Securities.ContainsKey(symbol_a) and self.Securities.ContainsKey(symbol_b) and \
self.Securities[symbol_a].Price != 0 and self.Securities[symbol_a].IsTradable and \
self.Securities[symbol_b].Price != 0 and self.Securities[symbol_b].IsTradable:
self.MarketOrder(symbol_a, short_q)
self.MarketOrder(symbol_b, long_q)
self.traded_quantity[pair] = (short_q, long_q)
self.traded_pairs.append(pair)
# b stock's price > a stock's price
else:
long_q = traded_portfolio_value / a_price
short_q = -traded_portfolio_value / b_price
if self.Securities.ContainsKey(symbol_a) and self.Securities.ContainsKey(symbol_b) and \
self.Securities[symbol_a].Price != 0 and self.Securities[symbol_a].IsTradable and \
self.Securities[symbol_b].Price != 0 and self.Securities[symbol_b].IsTradable:
self.MarketOrder(symbol_a, long_q)
self.MarketOrder(symbol_b, short_q)
self.traded_quantity[pair] = (long_q, short_q)
self.traded_pairs.append(pair)
# The position is closed when prices revert back.
else:
if pair in self.traded_pairs and pair in self.traded_quantity:
# make opposite order to opened position
self.MarketOrder(pair[0], -self.traded_quantity[pair][0])
self.MarketOrder(pair[1], -self.traded_quantity[pair][1])
pairs_to_remove.append(pair)
for pair in pairs_to_remove:
self.traded_pairs.remove(pair)
del self.traded_quantity[pair]
def Distance(self, price_a, price_b):
# Calculate the sum of squared deviations between two normalized price series.
price_a = [x for x in price_a]
price_b = [x for x in price_b]
norm_a = np.array(price_a) / price_a[-1]
norm_b = np.array(price_b) / price_b[-1]
return sum((norm_a - norm_b)**2)
def Selection(self):
if self.month == 6:
self.selection_flag = True
self.month += 1
if self.month > 12:
self.month = 1
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/payday-anomaly.py
================================================
# https://quantpedia.com/strategies/payday-anomaly/
#
# The investment universe consists of the S&P500 index. Simply, buy and hold the index during the 16th day in the month during each month of the year.
from dateutil.relativedelta import relativedelta
from AlgorithmImports import *
class PayDayAnomaly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
self.liquidate_next_day = False
self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol, 1), self.Purchase)
def Purchase(self):
alg_time = self.Time
paydate = self.PaydayDate(alg_time)
if alg_time.date() == paydate:
self.SetHoldings(self.symbol, 1)
self.liquidate_next_day = True
# self.algorithm.EmitInsights(Insight.Price(self.symbol, timedelta(days=1), InsightDirection.Up, None, None, None, self.weight))
if self.liquidate_next_day:
self.liquidate_next_day = False
return
if self.Portfolio[self.symbol].IsLong:
self.Liquidate(self.symbol)
def PaydayDate(self, date_time):
payday = date(date_time.year, date_time.month, 1) + relativedelta(day=15)
if payday.weekday() == 5: # Is saturday.
payday = payday - timedelta(days=1)
elif payday.weekday() == 6: # Is sunday.
payday = payday - timedelta(days=2)
return payday
================================================
FILE: static/strategies/rd-expenditures-and-stock-returns.py
================================================
# https://quantpedia.com/strategies/rd-expenditures-and-stock-returns/
#
# The investment universe consists of stocks that are listed on NYSE NASDAQ or AMEX. At the end of April, for each stock in the universe, calculate a measure of total R&D expenditures in the past 5 years scaled by the firm’s Market cap (defined on page 7, eq. 1).
# Go long (short) on the quintile of firms with the highest (lowest) R&D expenditures relative to their Market Cap. Weight the portfolio equally and rebalance next year. The backtested performance of the paper is substituted by our more recent backtest in Quantconnect.
# region imports
from AlgorithmImports import *
from numpy import log, average
from scipy import stats
import numpy as np
# endregion
class RDExpendituresandStockReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(1998, 1, 1)
self.SetCash(100000)
self.weight = {}
self.coarse_count = 3000
# R&D history.
self.RD = {}
self.rd_period = 5
self.long = []
self.short = []
data = self.AddEquity("XLK", Resolution.Daily)
data.SetLeverage(10)
self.technology_sector = data.Symbol
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.selection_flag = True
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetLeverage(10)
security.SetFeeModel(CustomFeeModel())
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Price > 5]
return selected
def FineSelectionFunction(self, fine):
fine = [
x
for x in fine
if (
x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths
)
and (x.MarketCap != 0)
and (
(x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE")
)
]
# and x.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology]
top_by_market_cap = None
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
top_by_market_cap = sorted_by_market_cap[: self.coarse_count]
else:
top_by_market_cap = fine
fine_symbols = [x.Symbol for x in top_by_market_cap]
ability = {}
updated_flag = [] # updated this year already
for stock in top_by_market_cap:
symbol = stock.Symbol
# prevent storing duplicated value for the same stock in one year
if symbol not in updated_flag:
# Update RD.
if symbol not in self.RD:
self.RD[symbol] = RollingWindow[float](self.rd_period)
# rd = stock.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths
# self.RD[symbol].Add(rd)
if self.RD[symbol].IsReady:
coefs = np.array([1, 0.8, 0.6, 0.4, 0.2])
rds = np.array([x for x in self.RD[symbol]])
rdc = sum(coefs * rds)
ability[stock] = rdc / stock.MarketCap
rd = (
stock.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths
)
self.RD[symbol].Add(rd)
# prevent storing duplicated value for the same stock in one year
if fine_symbols.count(symbol) > 1:
updated_flag.append(symbol)
# Ability market cap weighting.
# total_market_cap = sum([x.MarketCap for x in ability])
# for stock, rdc in ability.items():
# ability[stock] = rdc * (stock.MarketCap / total_market_cap)
# Remove not updated symbols
symbols_to_delete = []
for symbol in self.RD.keys():
if symbol not in fine_symbols:
symbols_to_delete.append(symbol)
for symbol in symbols_to_delete:
if symbol in self.RD:
del self.RD[symbol]
# starts trading after data storing period
if len(ability) != 0:
# Ability sorting.
sorted_by_ability = sorted(
ability.items(), key=lambda x: x[1], reverse=True
)
decile = int(len(sorted_by_ability) / 5)
high_by_ability = [x[0].Symbol for x in sorted_by_ability[:decile]]
low_by_ability = [x[0].Symbol for x in sorted_by_ability[-decile:]]
self.long = high_by_ability
self.short = low_by_ability
# self.short = [self.technology_sector]
return self.long + self.short
def Selection(self):
if self.Time.month == 4:
self.selection_flag = True
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
long_count = len(self.long)
short_count = len(self.short)
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
for symbol in self.long:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
):
self.SetHoldings(symbol, 1 / long_count)
for symbol in self.short:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
):
self.SetHoldings(symbol, -1 / short_count)
self.long.clear()
self.short.clear()
class SymbolData:
def __init__(self, tested_growth, period):
self.TestedGrowth = tested_growth
self.RD = RollingWindow[float](period)
def update(self, window_value):
self.RD.Add(window_value)
def is_ready(self):
return self.RD.IsReady
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/rebalancing-premium-in-cryptocurrencies.py
================================================
# https://quantpedia.com/strategies/rebalancing-premium-in-cryptocurrencies/
#
# The investment universe consists of 27 cryptocurrencies: BAT (Basic Attention Token), BTC (Bitcoin), BTG (Bitcoin Gold),
# DAI (Dai), DATA (Data Coin), DGB (DigiByte), EOS (EIS.io), ETH (Ethereum), FUN (FUN Token), IOTA (Iota), LRC (Loopring token),
# LTC (Litecoin), MANA (Mana coin), NEO (Neo), OMG (OMG, Formally known as OmiseGo), REQ (Request), SAN (Santiment Network Token),
# SNT (Status), TRX (Tron), WAX (Wax), XLM (Stellar), XMR (Monero), XRP (Ripple), XVG (Verge), ZEC (Zcash), ZIL (Zilliqa) and ZRX (0x).
# Two portfolios are created. The first portfolio is the daily rebalanced portfolio of all 27 cryptos to ensure that the assets have equal weights.
# The second portfolio is not rebalanced at all: an investor buys the equally-weighted crypto portfolio and lets the weights drift.
# Then the investor goes long the first portfolio and shorts the second portfolio with 70% weight.
#
# QC Implementation:
# - BTGUSD is not traded due to data error.
from AlgorithmImports import *
class RebalancingPremiumInCryptocurrencies(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000000)
self.cryptos = [
"BTCUSD",
"BATUSD",
# "BTGUSD",
"DAIUSD",
"DGBUSD", "EOSUSD",
"ETHUSD", "FUNUSD",
"LTCUSD", "NEOUSD",
"OMGUSD", "SNTUSD",
"TRXUSD", "XLMUSD",
"XMRUSD", "XRPUSD",
"XVGUSD", "ZECUSD",
"ZRXUSD", "LRCUSD",
"REQUSD", "SANUSD",
"WAXUSD", "ZILUSD",
"IOTAUSD",
"MANAUSD",
"DATAUSD"
]
self.short_side_percentage = 0.7
self.data = {}
self.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto in self.cryptos:
# GDAX is coinmarket, but it doesn't support this many cryptos, so we choose Bitfinex
data = self.AddCrypto(crypto, Resolution.Minute, Market.Bitfinex)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(10)
self.data[crypto] = SymbolData()
self.was_traded_already = False # wait for the price data to come only once
self.prev_short_portfolio_equity = 0 # short leg equity tracking
def OnData(self, data):
if not (self.Time.hour == 9 and self.Time.minute == 30):
return
all_cryptos_are_ready = True # data warmup flag
# check if all cryptos has ready data
for crypto in self.cryptos:
if crypto in data and data[crypto]:
# update crypto price for weight calculation
self.data[crypto].last_price = data[crypto].Value
# if there is at least one crypto, which doesn't have data, then don't trade and break cycle
else:
all_cryptos_are_ready = False
break
if all_cryptos_are_ready or self.was_traded_already:
self.was_traded_already = True
# long strategy equity calculation
long_portfolio_equity = self.Portfolio.TotalPortfolioValue
long_equity_to_trade = long_portfolio_equity / len(self.cryptos)
# short strategy equity calculation
short_portfolio_equity = self.Portfolio.TotalPortfolioValue * self.short_side_percentage
short_equity_to_trade = short_portfolio_equity / len(self.cryptos)
# trading/rebalance
for crypto, symbol_obj in self.data.items():
if crypto in data and data[crypto]:
# short strategy
if not self.Portfolio[crypto].Invested:
short_q = np.floor(short_equity_to_trade / symbol_obj.last_price)
if abs(short_q) >= self.Securities[crypto].SymbolProperties.MinimumOrderSize:
self.MarketOrder(crypto, -short_q)
# long strategy
long_q = np.floor(long_equity_to_trade / symbol_obj.last_price)
# currency was traded before
if symbol_obj.quantity is not None:
# calculate quantity difference
diff_q = long_q - symbol_obj.quantity
# rebalance position
if abs(diff_q) >= self.Securities[crypto].SymbolProperties.MinimumOrderSize:
self.MarketOrder(crypto, diff_q)
# change new quantity
symbol_obj.quantity += diff_q
else:
# rebalance position
if abs(long_q) >= self.Securities[crypto].SymbolProperties.MinimumOrderSize:
self.MarketOrder(crypto, long_q)
# change new quantity
symbol_obj.quantity = long_q
class SymbolData():
def __init__(self):
self.last_price = None
self.quantity = None
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/residual-momentum-factor.py
================================================
# https://quantpedia.com/strategies/residual-momentum-factor/
#
# The investment universe consists of all domestic, primary stocks listed on the New York (NYSE), American (AMEX), and NASDAQ
# stock markets with a price higher than $1. Closed-end funds, REITs, unit trusts, ADRs, and foreign stocks are removed. The
# 10% largest stocks in terms of market capitalization are then selected for trading.
# The residual momentum strategy is defined as a zero-investment top-minus-bottom decile portfolio based on ranking stocks
# every month on their past 12-month residual returns, excluding the most recent month, standardized by the standard deviation
# of the residual returns over the same period. Residual returns are estimated each month for all stocks over the past 36 months
# using a regression model. The regression model is calculated every month for all eligible stocks using the Fama and French
# three factors as independent variables. The portfolio is equally weighted and rebalanced monthly.
#
# QC implementation changes:
# - Universe consists of top 3000 US stock by market cap from NYSE, AMEX and NASDAQ.
import numpy as np
from AlgorithmImports import *
import statsmodels.api as sm
class ResidualMomentumFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.coarse_count = 500
# Monthly price data.
self.data = {}
self.period = 37
# Warmup market monthly data.
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.data[self.symbol] = RollingWindow[float](self.period)
history = self.History(self.symbol, self.period * 21, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {self.symbol} yet.")
else:
closes = history.loc[self.symbol].close
closes_len = len(closes.keys())
# Find monthly closes.
for index, time_close in enumerate(closes.iteritems()):
# index out of bounds check.
if index + 1 < closes_len:
date_month = time_close[0].date().month
next_date_month = closes.keys()[index + 1].month
# Found last day of month.
if date_month != next_date_month:
self.data[self.symbol].Add(time_close[1])
# Factors.
self.size_factor_symbols = [] # Symbol,long_flag tuple.
self.size_factor_vector = RollingWindow[float](self.period - 1) # Monthly performance.
self.value_factor_symbols = []
self.value_factor_vector = RollingWindow[float](self.period - 1)
# Monthly residual returns for each stock.
self.residual_return = {}
self.residual_momentum_period = 12
self.long = []
self.short = []
self.last_month = -1
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetLeverage(10)
security.SetFeeModel(CustomFeeModel())
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
# Update the rolling window every month.
for stock in coarse:
symbol = stock.Symbol
# Store monthly market price.
if symbol == self.symbol:
self.data[self.symbol].Add(stock.AdjustedPrice)
else:
# Store monthly stock price.
if symbol in self.data:
self.data[symbol].Add(stock.AdjustedPrice)
selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
# selected = [x.Symbol
# for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'],
# key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = RollingWindow[float](self.period)
history = self.History(symbol, self.period * 21, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet.")
continue
closes = history.loc[symbol].close
closes_len = len(closes.keys())
# Find monthly closes.
for index, time_close in enumerate(closes.iteritems()):
# index out of bounds check.
if index + 1 < closes_len:
date_month = time_close[0].date().month
next_date_month = closes.keys()[index + 1].month
# Found last day of month.
if date_month != next_date_month:
self.data[symbol].Add(time_close[1])
return [x for x in selected if self.data[x].IsReady]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0 and x.CompanyReference.IsREIT == 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))]
if len(fine) > self.coarse_count:
sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)
top_by_market_cap = sorted_by_market_cap[:self.coarse_count]
else:
top_by_market_cap = fine
# Size factor.
# sorted_by_market_cap = sorted(top_by_market_cap, key = lambda x:(x.MarketCap), reverse=False)
quintile = int(len(top_by_market_cap) / 5)
size_factor_long = [ (i.Symbol,True) for i in top_by_market_cap[-quintile:]]
size_factor_short = [(i.Symbol,False) for i in top_by_market_cap[:quintile]]
# Calculate last month's performance.
if len(self.size_factor_symbols) != 0:
monthly_return = self.CalculateFactorPerformance(self.data, self.size_factor_symbols)
if monthly_return != 0:
self.size_factor_vector.Add(monthly_return)
# Store new factor symbols.
self.size_factor_symbols = size_factor_long + size_factor_short
# Value factor.
sorted_by_pb = sorted(top_by_market_cap, key = lambda x:(x.ValuationRatios.PBRatio), reverse=False)
quintile = int(len(sorted_by_pb) / 5)
value_factor_long = [(i.Symbol,True) for i in sorted_by_pb[:quintile]]
value_factor_short = [(i.Symbol,False) for i in sorted_by_pb[-quintile:]]
# Calculate last month's performance.
if len(self.value_factor_symbols) != 0:
monthly_return = self.CalculateFactorPerformance(self.data, self.value_factor_symbols)
if monthly_return != 0:
self.value_factor_vector.Add(monthly_return)
# Store new factor symbols.
self.value_factor_symbols = value_factor_long + value_factor_short
# Every factor vector is ready.
if self.size_factor_vector.IsReady and self.value_factor_vector.IsReady:
# Market factor.
market_factor = []
if self.symbol in self.data and self.data[self.symbol].IsReady:
market_factor_prices = np.array([x for x in self.data[self.symbol]])
market_factor = (market_factor_prices[:-1] - market_factor_prices[1:]) / market_factor_prices[1:]
if len(market_factor) == (self.period - 1):
# Residual return calc.
x = [
[x for x in market_factor],
[x for x in self.size_factor_vector],
[x for x in self.value_factor_vector]
]
standardized_residual_momentum = {}
for stock in top_by_market_cap:
symbol = stock.Symbol
if symbol in self.data and self.data[symbol].IsReady:
monthly_prices = np.array([x for x in self.data[symbol]])
monthly_returns = (monthly_prices[:-1] - monthly_prices[1:]) / monthly_prices[1:]
regression_model = self.MultipleLinearRegression(x, monthly_returns)
alpha = regression_model.params[0]
if symbol not in self.residual_return:
self.residual_return[symbol] = RollingWindow[float](self.residual_momentum_period)
self.residual_return[symbol].Add(alpha)
# Residual data for 12 months is ready.
if self.residual_return[symbol].IsReady:
residual_returns = [x for x in self.residual_return[symbol]]
standardized_residual_momentum[symbol] = sum(residual_returns) / np.std(residual_returns)
sorted_by_resid_momentum = sorted(standardized_residual_momentum.items(), key = lambda x: x[1], reverse=True)
decile = int(len(sorted_by_resid_momentum) / 10)
self.long = [x[0] for x in sorted_by_resid_momentum[:decile]]
self.short = [x[0] for x in sorted_by_resid_momentum[-decile:]]
return self.long + self.short
def OnData(self, data):
if self.Time.month != self.last_month:
self.last_month = self.Time.month
self.selection_flag = True
return
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
for symbol in self.long:
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def CalculateFactorPerformance(self, data, factor_symbols):
monthly_return = 0
if len(factor_symbols) != 0:
for symbol, long_flag in factor_symbols:
if symbol in data and data[symbol].Count >= 2:
closes = [x for x in data[symbol]]
if long_flag:
monthly_return += ((closes[0] / closes[1] - 1) / len(factor_symbols))
else:
monthly_return -= ((closes[0] / closes[1] - 1) / len(factor_symbols))
return monthly_return
def MultipleLinearRegression(self, x, y):
x = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/return-asymmetry-effect-in-commodity-futures.py
================================================
# https://quantpedia.com/strategies/return-asymmetry-effect-in-commodity-futures/
#
# The investment universe consists of 22 commodity futures, namely:
# soybean oil, corn, cocoa, cotton, feeder cattle, gold, copper, heating oil, coffee, live cattle, lean hogs,
# natural gas, oats, orange juice, palladium, platinum, soybean, sugar, silver, soybean meal, wheat, and crude oil.
# Firstly, at the beginning of each month, construct the asymmetry measure (IE) for each commodity based on the latest 260 daily returns using the following formula
# (the formula originally consists of theoretical density and integrals, however the solution is simple when empirical distribution is utilized):
# IE = (number of trading days when the daily return is greater than the average plus two standard deviations) –
# (number of trading days when the daily return is smaller than the average minus two standard deviations).
# Then rank the commodities according to their IE.
# Buy the bottom seven commodities with the lowest IE in the previous month and sell the top seven commodities with the highest IE in the previous month.
# Weigh the portfolio equally and rebalance monthly.
#
# QC Implementation:
# - Universe consists of Quantpedia comodity futures.
# - Buying bottom 7 commodities and selling top 7 commodities according to IE.
from AlgorithmImports import *
class ReturnAsymmetryEffectInCommodityFutures(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.tickers = [
"CME_S1", # Soybean Futures, Continuous Contract
"CME_W1", # Wheat Futures, Continuous Contract
"CME_SM1", # Soybean Meal Futures, Continuous Contract
"CME_BO1", # Soybean Oil Futures, Continuous Contract
"CME_C1", # Corn Futures, Continuous Contract
"CME_O1", # Oats Futures, Continuous Contract
"CME_LC1", # Live Cattle Futures, Continuous Contract
"CME_FC1", # Feeder Cattle Futures, Continuous Contract
"CME_LN1", # Lean Hog Futures, Continuous Contract
"CME_GC1", # Gold Futures, Continuous Contract
"CME_SI1", # Silver Futures, Continuous Contract
"CME_PL1", # Platinum Futures, Continuous Contract
"CME_CL1", # Crude Oil Futures, Continuous Contract
"CME_HG1", # Copper Futures, Continuous Contract
"CME_LB1", # Random Length Lumber Futures, Continuous Contract
# "CME_NG1", # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
"CME_PA1", # Palladium Futures, Continuous Contract
"CME_RR1", # Rough Rice Futures, Continuous Contract
"CME_RB2", # Gasoline Futures, Continuous Contract
"CME_KW2", # Wheat Kansas, Continuous Contract
"ICE_CC1", # Cocoa Futures, Continuous Contract
"ICE_CT1", # Cotton No. 2 Futures, Continuous Contract
"ICE_KC1", # Coffee C Futures, Continuous Contract
"ICE_O1", # Heating Oil Futures, Continuous Contract
"ICE_OJ1", # Orange Juice Futures, Continuous Contract
"ICE_SB1" # Sugar No. 11 Futures, Continuous Contract
"ICE_RS1", # Canola Futures, Continuous Contract
"ICE_GO1", # Gas Oil Futures, Continuous Contract
"ICE_WT1", # WTI Crude Futures, Continuous Contract
]
self.data = {} # storing objects of SymbolData class keyed by comodity symbols
self.period = 261 # need 261 daily prices, to calculate 260 daily returns
self.buy_count = 7 # buy n comodities on each rebalance
self.sell_count = 7 # sell n comodities on each rebalance
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
# subscribe to futures contracts
for ticker in self.tickers:
security = self.AddData(QuantpediaFutures, ticker, Resolution.Daily)
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
self.data[security.Symbol] = SymbolData(self.period)
self.rebalance_flag = False
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol, 0), self.Rebalance)
def OnData(self, data):
# update daily closes
for symbol in self.data:
if symbol in data and data[symbol]:
close = data[symbol].Value
self.data[symbol].update_closes(close)
# rebalance monthly
if not self.rebalance_flag:
return
self.rebalance_flag = False
IE = {}
for symbol, symbol_obj in self.data.items():
# check if comodity has ready prices
if not symbol_obj.is_ready():
continue
# calculate IE
IE_value = symbol_obj.calculate_IE()
# store IE value under comodity symbol
IE[symbol] = IE_value
# make sure, there are enough comodities for rebalance
if len(IE) < (self.buy_count + self.sell_count):
return
# sort commodities based on IE values
sorted_by_IE = [x[0] for x in sorted(IE.items(), key=lambda item: item[1])]
# select long and short parts
long = sorted_by_IE[:self.buy_count]
short = sorted_by_IE[-self.sell_count:]
# trade execution
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / self.buy_count)
for symbol in short:
self.SetHoldings(symbol, -1 / self.sell_count)
def Rebalance(self):
self.rebalance_flag = True
class SymbolData():
def __init__(self, period):
self.closes = RollingWindow[float](period)
def update_closes(self, close):
self.closes.Add(close)
def is_ready(self):
return self.closes.IsReady
def calculate_IE(self):
closes = np.array([x for x in self.closes])
daily_returns = (closes[:-1] - closes[1:]) / closes[1:]
average_daily_returns = np.average(daily_returns)
two_daily_returns_std = 2 * np.std(daily_returns)
avg_plus_two_std = average_daily_returns + two_daily_returns_std
avg_minus_two_std = average_daily_returns - two_daily_returns_std
over_avg_plus_two_std = 0 # counting number of daily returns, which were over avg_plus_two_std
under_avg_minus_two_std = 0 # counting number of daily returns, which were under avg_minus_two_std
for daily_return in daily_returns:
if daily_return > avg_plus_two_std:
over_avg_plus_two_std += 1
elif daily_return < avg_minus_two_std:
under_avg_minus_two_std += 1
IE_value = over_avg_plus_two_std - under_avg_minus_two_std
return IE_value
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/reversal-during-earnings-announcements.py
================================================
from AlgorithmImports import *
import numpy as np
import statsmodels.api as sm
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# NOTE: Manager for new trades. It's represented by certain count of equally weighted brackets for long and short positions.
# If there's a place for new trade, it will be managed for time of holding period.
class TradeManager:
def __init__(self, algorithm, long_size, short_size, holding_period):
self.algorithm = algorithm # algorithm to execute orders in.
self.long_size = long_size
self.short_size = short_size
self.long_len = 0
self.short_len = 0
# Arrays of ManagedSymbols
self.symbols = []
self.holding_period = holding_period # Days of holding.
# Add stock symbol object
def Add(self, symbol, long_flag):
# Open new long trade.
managed_symbol = ManagedSymbol(symbol, self.holding_period, long_flag)
if long_flag:
# If there's a place for it.
if self.long_len < self.long_size:
self.symbols.append(managed_symbol)
self.algorithm.SetHoldings(symbol, 1 / self.long_size)
self.long_len += 1
else:
self.algorithm.Log("There's not place for additional trade.")
# Open new short trade.
else:
# If there's a place for it.
if self.short_len < self.short_size:
self.symbols.append(managed_symbol)
self.algorithm.SetHoldings(symbol, -1 / self.short_size)
self.short_len += 1
else:
self.algorithm.Log("There's not place for additional trade.")
# Decrement holding period and liquidate symbols.
def TryLiquidate(self):
symbols_to_delete = []
for managed_symbol in self.symbols:
managed_symbol.days_to_liquidate -= 1
# Liquidate.
if managed_symbol.days_to_liquidate == 0:
symbols_to_delete.append(managed_symbol)
self.algorithm.Liquidate(managed_symbol.symbol)
if managed_symbol.long_flag:
self.long_len -= 1
else:
self.short_len -= 1
# Remove symbols from management.
for managed_symbol in symbols_to_delete:
self.symbols.remove(managed_symbol)
def LiquidateTicker(self, ticker):
symbol_to_delete = None
for managed_symbol in self.symbols:
if managed_symbol.symbol.Value == ticker:
self.algorithm.Liquidate(managed_symbol.symbol)
symbol_to_delete = managed_symbol
if managed_symbol.long_flag:
self.long_len -= 1
else:
self.short_len -= 1
break
if symbol_to_delete:
self.symbols.remove(symbol_to_delete)
else:
self.algorithm.Debug("Ticker is not held in portfolio!")
class ManagedSymbol:
def __init__(self, symbol, days_to_liquidate, long_flag):
self.symbol = symbol
self.days_to_liquidate = days_to_liquidate
self.long_flag = long_flag
# https://quantpedia.com/strategies/reversal-during-earnings-announcements/
#
# The investment universe consists of stocks listed at NYSE, AMEX, and NASDAQ, whose daily price data are available at the CRSP database.
# Earnings-announcement dates are collected from Compustat. Firstly, the investor sorts stocks into quintiles based on firm size. Then he
# further sorts the stocks in the top quintile (the biggest) into quintiles based on their average returns in the 3-day window between
# t-4 and t-2, where t is the day of the earnings announcement. The investor goes long on the bottom quintile (past losers) and short on
# the top quintile (past winners) and holds the stocks during the 3-day window between t-1, t, and t+1. Stocks in the portfolios are
# weighted equally.
#
# QC Impelmentation:
# - Universe consits of stocks, which have earnings dates in Quantpedia data.
# - Maximum of 20 long and 20 short stock are held at the same time.
import data_tools
from AlgorithmImports import *
import numpy as np
from collections import deque
class ReversalDuringEarningsAnnouncements(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1) # earnings dates start in 2010
self.SetCash(100000)
self.ear_period = 4
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
# Daily price data.
self.data = {}
# Import earnigns data.
self.earnings_data = {}
# Available symbols from earning_dates.csv.
self.tickers: Set(str) = set()
self.first_date: datetime.date | None = None
earnings_data: str = self.Download(
"data.quantpedia.com/backtesting_data/economic/earnings_dates_eps.json"
)
earnings_data_json: list[dict] = json.loads(earnings_data)
for obj in earnings_data_json:
date: datetime.date = datetime.strptime(obj["date"], "%Y-%m-%d").date()
self.earnings_data[date] = []
if not self.first_date:
self.first_date = date
for stock_data in obj["stocks"]:
ticker: str = stock_data["ticker"]
self.earnings_data[date].append(ticker)
self.tickers.add(ticker)
# EAR history for previous quarter used for statistics.
self.ear_previous_quarter = []
self.ear_actual_quarter = []
# 5 equally weighted brackets for traded symbols. - 20 symbols long , 20 for short, 3 days of holding.
self.trade_manager = data_tools.TradeManager(self, 20, 20, 3)
self.month: int = 0
self.selection_flag = False
self.rebalance_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(data_tools.CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
# update daily prices
for stock in coarse:
symbol = stock.Symbol
if symbol in self.data:
self.data[symbol].Add(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
selected = [x.Symbol for x in coarse if x.Symbol.Value in self.tickers]
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = RollingWindow[float](self.ear_period)
history = self.History(symbol, self.ear_period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].Add(close)
return selected
def OnData(self, data):
date_to_lookup = (self.Time + timedelta(days=1)).date()
# Liquidate opened symbols after three days.
self.trade_manager.TryLiquidate()
ret_t4_t2 = {}
for symbol in self.data:
# Data is ready.
if self.data[symbol].IsReady:
# Earnings is in next two day for the symbol.
if (
date_to_lookup in self.earnings_data
and symbol.Value in self.earnings_data[date_to_lookup]
):
closes = [x for x in self.data[symbol]]
# Calculate t-4 to t-2 return.
ret = (closes[0] - closes[-1]) / closes[-1]
ret_t4_t2[symbol] = ret
# Store return in this month's history.
self.ear_actual_quarter.append(ret)
# Wait until we have history data for previous three months.
if len(self.ear_previous_quarter) != 0:
# Sort by EAR.
ear_values = self.ear_previous_quarter
top_ear_quintile = np.percentile(ear_values, 80)
bottom_ear_quintile = np.percentile(ear_values, 20)
# Store symbol to set.
long = [
x[0]
for x in ret_t4_t2.items()
if x[1] <= bottom_ear_quintile and x[0] in data and data[x[0]]
]
short = [
x[0]
for x in ret_t4_t2.items()
if x[1] >= top_ear_quintile and x[0] in data and data[x[0]]
]
# Open new trades.
for symbol in long:
self.trade_manager.Add(symbol, True)
for symbol in short:
self.trade_manager.Add(symbol, False)
def Selection(self):
# There is no earnings data yet.
if self.Time.date() < self.first_date:
return
self.selection_flag = True
# Every three months.
if self.month % 3 == 0:
# Save quarter history.
self.ear_previous_quarter = [x for x in self.ear_actual_quarter]
self.ear_actual_quarter.clear()
self.month += 1
================================================
FILE: static/strategies/roa-effect-within-stocks.py
================================================
# https://quantpedia.com/strategies/roa-effect-within-stocks/
#
# The investment universe contains all stocks on NYSE and AMEX and Nasdaq with Sales greater than 10 million USD. Stocks are then sorted into
# two halves based on market capitalization. Each half is then divided into deciles based on Return on assets (ROA) calculated as quarterly
# earnings (Compustat quarterly item IBQ – income before extraordinary items) divided by one-quarter-lagged assets (item ATQ – total assets).
# The investor then goes long the top three deciles from each market capitalization group and goes short bottom three deciles. The strategy is
# rebalanced monthly, and stocks are equally weighted.
#
# QC implementation changes:
# - Instead of all listed stock, we select 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ.
from AlgorithmImports import *
class ROAEffectWithinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.course_count = 500
self.long = []
self.short = []
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.MonthEnd(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = sorted(
[
x
for x in coarse
if x.HasFundamentalData and x.Market == "usa" and x.Price > 5
],
key=lambda x: x.DollarVolume,
reverse=True,
)
return [x.Symbol for x in selected[: self.course_count]]
def FineSelectionFunction(self, fine):
fine = [
x
for x in fine
if x.MarketCap != 0
and x.ValuationRatios.SalesPerShare
* x.EarningReports.DilutedAverageShares.Value
> 10000000
and x.OperationRatios.ROA.ThreeMonths != 0
and (
(x.SecurityReference.ExchangeId == "NYS")
or (x.SecurityReference.ExchangeId == "NAS")
or (x.SecurityReference.ExchangeId == "ASE")
)
]
# Sorting by market cap.
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
half = int(len(sorted_by_market_cap) / 2)
top_mc = [x for x in sorted_by_market_cap[:half]]
bottom_mc = [x for x in sorted_by_market_cap[half:]]
if len(top_mc) >= 10 and len(bottom_mc) >= 10:
# Sorting by ROA.
sorted_top_by_roa = sorted(
top_mc, key=lambda x: (x.OperationRatios.ROA.Value), reverse=True
)
decile = int(len(sorted_top_by_roa) / 10)
long_top = [x.Symbol for x in sorted_top_by_roa[: decile * 3]]
short_top = [x.Symbol for x in sorted_top_by_roa[-(decile * 3) :]]
sorted_bottom_by_roa = sorted(
bottom_mc, key=lambda x: (x.OperationRatios.ROA.Value), reverse=True
)
decile = int(len(sorted_bottom_by_roa) / 10)
long_bottom = [x.Symbol for x in sorted_bottom_by_roa[: decile * 3]]
short_bottom = [x.Symbol for x in sorted_bottom_by_roa[-(decile * 3) :]]
self.long = long_top + long_bottom
self.short = short_top + short_bottom
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
long_count = len(self.long)
short_count = len(self.short)
for symbol in self.long:
self.SetHoldings(symbol, 1 / long_count)
for symbol in self.short:
self.SetHoldings(symbol, -1 / short_count)
self.long.clear()
self.short.clear()
def Selection(self):
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/sector-momentum-rotational-system.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/sector-momentum-rotational-system/
#
# Use ten sector ETFs. Pick 3 ETFs with the strongest 12-month momentum into your portfolio and weight them equally. Hold them for one month and then rebalance.
class SectorMomentumAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# Daily ROC data.
self.data = {}
self.period = 12 * 21
self.SetWarmUp(self.period)
self.symbols = [
"VNQ", # Vanguard Real Estate Index Fund
"XLK", # Technology Select Sector SPDR Fund
"XLE", # Energy Select Sector SPDR Fund
"XLV", # Health Care Select Sector SPDR Fund
"XLF", # Financial Select Sector SPDR Fund
"XLI", # Industrials Select Sector SPDR Fund
"XLB", # Materials Select Sector SPDR Fund
"XLY", # Consumer Discretionary Select Sector SPDR Fund
"XLP", # Consumer Staples Select Sector SPDR Fund
"XLU", # Utilities Select Sector SPDR Fund
]
for symbol in self.symbols:
data = self.AddEquity(symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[symbol] = self.ROC(symbol, self.period, Resolution.Daily)
self.data[self.symbols[0]].Updated += self.OnROCUpdated
self.recent_month = -1
self.rebalance_flag = False
def OnROCUpdated(self, sender, updated):
# set rebalance flag
if self.recent_month != self.Time.month:
self.recent_month = self.Time.month
self.rebalance_flag = True
def OnData(self, data):
if self.IsWarmingUp:
return
# rebalance once a month
if self.rebalance_flag:
self.rebalance_flag = False
sorted_by_momentum = sorted(
[
x
for x in self.data.items()
if x[1].IsReady and x[0] in data and data[x[0]]
],
key=lambda x: x[1].Current.Value,
reverse=True,
)
long = [x[0] for x in sorted_by_momentum[:3]]
# Trade execution.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/short-interest-effect-long-short-version.py
================================================
# https://quantpedia.com/strategies/short-interest-effect-long-short-version/
#
# All stocks from NYSE, AMEX, and NASDAQ are part of the investment universe. Stocks are then sorted each month into short-interest deciles based on
# the ratio of short interest to shares outstanding. The investor then goes long on the decile with the lowest short ratio and short on the decile
# with the highest short ratio. The portfolio is rebalanced monthly, and stocks in the portfolio are weighted equally.
from AlgorithmImports import *
class ShortInterestEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
# NOTE: We use only s&p 100 stocks so it's possible to fetch short interest data from quandl.
self.symbols = [
"AAPL",
"MSFT",
"AMZN",
"FB",
"GOOGL",
"GOOG",
"JPM",
"JNJ",
"V",
"PG",
"XOM",
"UNH",
"BAC",
"MA",
"T",
"DIS",
"INTC",
"HD",
"VZ",
"MRK",
"PFE",
"CVX",
"KO",
"CMCSA",
"CSCO",
"PEP",
"WFC",
"C",
"BA",
"ADBE",
"WMT",
"CRM",
"MCD",
"MDT",
"BMY",
"ABT",
"NVDA",
"NFLX",
"AMGN",
"PM",
"PYPL",
"TMO",
"COST",
"ABBV",
"ACN",
"HON",
"NKE",
"UNP",
"UTX",
"NEE",
"IBM",
"TXN",
"AVGO",
"LLY",
"ORCL",
"LIN",
"SBUX",
"AMT",
"LMT",
"GE",
"MMM",
"DHR",
"QCOM",
"CVS",
"MO",
"LOW",
"FIS",
"AXP",
"BKNG",
"UPS",
"GILD",
"CHTR",
"CAT",
"MDLZ",
"GS",
"USB",
"CI",
"ANTM",
"BDX",
"TJX",
"ADP",
"TFC",
"CME",
"SPGI",
"COP",
"INTU",
"ISRG",
"CB",
"SO",
"D",
"FISV",
"PNC",
"DUK",
"SYK",
"ZTS",
"MS",
"RTN",
"AGN",
"BLK",
]
for symbol in self.symbols:
data = self.AddEquity(symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.AddData(
QuandlFINRA_ShortVolume, "FINRA/FNSQ_" + symbol, Resolution.Daily
)
self.recent_month = -1
def OnData(self, data):
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
short_interest = {}
for symbol in self.symbols:
sym = "FINRA/FNSQ_" + symbol
if sym in data and data[sym] and symbol in data and data[symbol]:
short_vol = data[sym].GetProperty("SHORTVOLUME")
total_vol = data[sym].GetProperty("TOTALVOLUME")
short_interest[symbol] = short_vol / total_vol
long = []
short = []
if len(short_interest) >= 10:
sorted_by_short_interest = sorted(
short_interest.items(), key=lambda x: x[1], reverse=True
)
decile = int(len(sorted_by_short_interest) / 10)
long = [x[0] for x in sorted_by_short_interest[-decile:]]
short = [x[0] for x in sorted_by_short_interest[:decile]]
# trade execution
stocks_invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, -1 / len(short))
class QuandlFINRA_ShortVolume(PythonQuandl):
def __init__(self):
self.ValueColumnName = "SHORTVOLUME" # also 'TOTALVOLUME' is accesible
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/short-term-reversal-in-stocks.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/short-term-reversal-in-stocks/
#
# The investment universe consists of the 100 biggest companies by market capitalization.
# The investor goes long on the ten stocks with the lowest performance in the previous week and
# goes short on the ten stocks with the greatest performance of the prior month. The portfolio is rebalanced weekly.
#
# QC implementation changes:
# - Instead of all listed stocks, we first select 500 most liquid stock from QC as a first filter due to time complexity issues tied to whole universe filtering.
# - Then top 100 market cap stocks are used in momentum sorting.
class ShortTermReversalEffectinStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.coarse_count = 500
self.stock_selection = 10
self.top_by_market_cap_count = 100
self.period = 21
self.long = []
self.short = []
# Daily close data
self.data = {}
self.day = 1
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(
self.DateRules.EveryDay(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol),
self.Selection,
)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
# Update the rolling window every day.
for stock in coarse:
symbol = stock.Symbol
# Store monthly price.
if symbol in self.data:
self.data[symbol].update(stock.AdjustedPrice)
if not self.selection_flag:
return Universe.Unchanged
selected = sorted(
[
x
for x in coarse
if x.HasFundamentalData and x.Market == "usa" and x.Price > 1
],
key=lambda x: x.DollarVolume,
reverse=True,
)
selected = [x.Symbol for x in selected][: self.coarse_count]
# Warmup price rolling windows.
for symbol in selected:
if symbol in self.data:
continue
self.data[symbol] = SymbolData(self.period)
history = self.History(symbol, self.period, Resolution.Daily)
if history.empty:
self.Log(f"Not enough data for {symbol} yet")
continue
closes = history.loc[symbol].close
for time, close in closes.iteritems():
self.data[symbol].update(close)
return [x for x in selected if self.data[x].is_ready()]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.MarketCap != 0]
sorted_by_market_cap = sorted(fine, key=lambda x: x.MarketCap, reverse=True)
top_by_market_cap = [
x.Symbol for x in sorted_by_market_cap[: self.top_by_market_cap_count]
]
month_performances = {
symbol: self.data[symbol].monthly_return() for symbol in top_by_market_cap
}
week_performances = {
symbol: self.data[symbol].weekly_return() for symbol in top_by_market_cap
}
sorted_by_month_perf = [
x[0]
for x in sorted(
month_performances.items(), key=lambda item: item[1], reverse=True
)
]
sorted_by_week_perf = [
x[0] for x in sorted(week_performances.items(), key=lambda item: item[1])
]
self.long = sorted_by_week_perf[: self.stock_selection]
for symbol in sorted_by_month_perf: # Month performances are sorted descending
if symbol not in self.long:
self.short.append(symbol)
if len(self.short) == 10:
break
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
# Leveraged portfolio - 100% long, 100% short.
for symbol in self.long:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
):
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
if (
self.Securities[symbol].Price != 0
and self.Securities[symbol].IsTradable
):
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def Selection(self):
if self.day == 5:
self.selection_flag = True
self.day += 1
if self.day > 5:
self.day = 1
class SymbolData:
def __init__(self, period):
self.closes = RollingWindow[float](period)
self.period = period
def update(self, close):
self.closes.Add(close)
def is_ready(self) -> bool:
return self.closes.IsReady
def weekly_return(self) -> float:
return self.closes[0] / self.closes[5] - 1
def monthly_return(self) -> float:
return self.closes[0] / self.closes[self.period - 1] - 1
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/short-term-reversal-with-futures.py
================================================
# region imports
from AlgorithmImports import *
# endregion
# https://quantpedia.com/strategies/short-term-reversal-with-futures/
#
# The investment universe consists of 24 types of US futures contracts (4 currencies, 5 financials, 8 agricultural, 7 commodities).
# A weekly time frame is used – a Wednesday- Wednesday interval. The contract closest to expiration is used, except within the delivery
# month, in which the second-nearest contract is used. Rolling into the second nearest contract is done at the beginning of the delivery month.
# The contract is defined as the high- (low-) volume contract if the contract’s volume changes between period from t-1 to t and period from t-2
# to t-1 is above (below) the median volume change of all contracts (weekly trading volume is detrended by dividing the trading volume by its
# sample mean to make the volume measure comparable across markets). All contracts are also assigned to either high-open interest (top 50% of
# changes in open interest) or low-open interest groups (bottom 50% of changes in open interest) based on lagged changes in open interest between
# the period from t-1 to t and period from t-2 to t-1. The investor goes long (short) on futures from the high-volume, low-open interest group
# with the lowest (greatest) returns in the previous week. The weight of each contract is proportional to the difference between the return
# of the contract over the past one week and the equal-weighted average of returns on the N (number of contracts in a group) contracts during that period.
from collections import deque
import numpy as np
class ShortTermReversal(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
symbols: dict = {
"CME_S1": Futures.Grains.Soybeans,
"CME_W1": Futures.Grains.Wheat,
"CME_BO1": Futures.Grains.SoybeanOil,
"CME_C1": Futures.Grains.Corn,
"CME_LC1": Futures.Meats.LiveCattle,
"CME_FC1": Futures.Meats.FeederCattle,
"CME_KW2": Futures.Grains.Wheat,
"ICE_CC1": Futures.Softs.Cocoa,
"ICE_SB1": Futures.Softs.Sugar11CME,
"CME_GC1": Futures.Metals.Gold,
"CME_SI1": Futures.Metals.Silver,
"CME_PL1": Futures.Metals.Platinum,
"CME_RB1": Futures.Energies.Gasoline,
"ICE_WT1": Futures.Energies.CrudeOilWTI,
"ICE_O1": Futures.Energies.HeatingOil,
"CME_BP1": Futures.Currencies.GBP,
"CME_EC1": Futures.Currencies.EUR,
"CME_JY1": Futures.Currencies.JPY,
"CME_SF1": Futures.Currencies.CHF,
"CME_ES1": Futures.Indices.SP500EMini,
"CME_TY1": Futures.Financials.Y10TreasuryNote,
"CME_FV1": Futures.Financials.Y5TreasuryNote,
}
self.period: int = 14
self.SetWarmUp(self.period, Resolution.Daily)
self.futures_info: dict = {}
self.min_expiration_days: int = 2
self.max_expiration_days: int = 360
# daily close, volume and open interest data
self.data: dict = {}
for qp_symbol, qc_future in symbols.items():
# QP futures
data: Security = self.AddData(
QuantpediaFutures, qp_symbol, Resolution.Daily
)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[data.Symbol] = deque(maxlen=self.period)
# QC futures
future: Future = self.AddFuture(
qc_future,
Resolution.Daily,
dataNormalizationMode=DataNormalizationMode.Raw,
)
future.SetFilter(
timedelta(days=self.min_expiration_days),
timedelta(days=self.max_expiration_days),
)
self.futures_info[future.Symbol.Value] = FuturesInfo(data.Symbol)
self.recent_month: int = -1
def find_and_update_contracts(self, futures_chain, symbol) -> None:
near_contract: FuturesContract = None
if symbol in futures_chain:
contracts: list = [
contract
for contract in futures_chain[symbol]
if contract.Expiry.date() > self.Time.date()
]
if len(contracts) >= 1:
contracts: list = sorted(
contracts, key=lambda x: x.Expiry, reverse=False
)
near_contract = contracts[0]
self.futures_info[symbol].update_contracts(near_contract)
def OnData(self, data):
if data.FutureChains.Count > 0:
for symbol, futures_info in self.futures_info.items():
# check if near contract is expired or is not initialized
if not futures_info.is_initialized() or (
futures_info.is_initialized()
and futures_info.near_contract.Expiry.date() == self.Time.date()
):
self.find_and_update_contracts(data.FutureChains, symbol)
rebalance_flag: bool = False
ret_volume_oi_data: dict[Symbol, tuple] = {}
# roll return calculation
for symbol, futures_info in self.futures_info.items():
# futures data is present in the algorithm
if (
futures_info.quantpedia_future in data
and data[futures_info.quantpedia_future]
):
if futures_info.is_initialized():
near_c: FuturesContract = futures_info.near_contract
if self.Securities.ContainsKey(near_c.Symbol):
if futures_info.is_initialized():
# store daily data
price: float = data[futures_info.quantpedia_future].Value
vol: int = self.Securities[near_c.Symbol].Volume
oi: int = self.Securities[near_c.Symbol].OpenInterest
if price != 0 and vol != 0 and oi != 0:
self.data[futures_info.quantpedia_future].append(
(price, vol, oi)
)
# new month rebalance
if self.Time.month != self.recent_month and not self.IsWarmingUp:
self.recent_month = self.Time.month
rebalance_flag = True
if rebalance_flag:
if (
len(self.data[futures_info.quantpedia_future])
== self.data[futures_info.quantpedia_future].maxlen
):
# performance
prices: list[float] = [
x[0] for x in self.data[futures_info.quantpedia_future]
]
half: list[float] = int(len(prices) / 2)
prices: list[float] = prices[-half:]
ret: float = prices[-1] / prices[0] - 1
# volume change
volumes: list[int] = [
x[1] for x in self.data[futures_info.quantpedia_future]
]
volumes_t1: list[int] = volumes[-half:]
t1_vol_mean: float = np.mean(volumes_t1)
t1_vol_total: float = sum(volumes_t1) / t1_vol_mean
volumes_t2: list[int] = volumes[:half]
t2_vol_mean: float = np.mean(volumes_t2)
t2_vol_total: float = sum(volumes_t2) / t2_vol_mean
volume_weekly_diff: float = t1_vol_total - t2_vol_total
# open interest change
interests: list[int] = [
x[2] for x in self.data[futures_info.quantpedia_future]
]
t1_oi: list[int] = interests[-half:]
t1_oi_total: float = sum(t1_oi)
t2_oi: list[int] = interests[:half]
t2_oi_total: float = sum(t2_oi)
oi_weekly_diff: float = t1_oi_total - t2_oi_total
# store weekly diff data
ret_volume_oi_data[futures_info.quantpedia_future] = (
ret,
volume_weekly_diff,
oi_weekly_diff,
)
if rebalance_flag:
weight: dict[Symbol, float] = {}
if len(ret_volume_oi_data) > 4:
volume_sorted: list = sorted(
ret_volume_oi_data.items(), key=lambda x: x[1][1], reverse=True
)
half: int = int(len(volume_sorted) / 2)
high_volume: list = [x for x in volume_sorted[:half]]
open_interest_sorted: list = sorted(
ret_volume_oi_data.items(), key=lambda x: x[1][2], reverse=True
)
half = int(len(open_interest_sorted) / 2)
low_oi: list = [x for x in open_interest_sorted[-half:]]
filtered: list = [x for x in high_volume if x in low_oi]
filtered_by_return: list = sorted(
filtered, key=lambda x: x[0], reverse=True
)
half = int(len(filtered_by_return) / 2)
long: list[Symbol] = filtered_by_return[-half:]
short: list[Symbol] = filtered_by_return[:half]
if len(long + short) >= 2:
# return weighting
diff: dict[Symbol, float] = {}
avg_ret: float = np.average([x[1][0] for x in long + short])
for symbol, ret_volume_oi in long + short:
diff[symbol] = ret_volume_oi[0] - avg_ret
total_diff: float = sum([abs(x[1]) for x in diff.items()])
long_symbols: list[Symbol] = [x[0] for x in long]
if total_diff != 0:
for symbol, data in long + short:
if symbol in long_symbols:
weight[symbol] = diff[symbol] / total_diff
else:
weight[symbol] = -diff[symbol] / total_diff
# trade execution
invested: list[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in weight:
self.Liquidate(symbol)
for symbol, w in weight.items():
self.SetHoldings(symbol, w)
class FuturesInfo:
def __init__(self, quantpedia_future: Symbol) -> None:
self.quantpedia_future: Symbol = quantpedia_future
self.near_contract: FuturesContract = None
def update_contracts(self, near_contract: FuturesContract) -> None:
self.near_contract = near_contract
def is_initialized(self) -> bool:
return self.near_contract is not None
# Custom fee model.
class CustomFeeModel:
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
================================================
FILE: static/strategies/skewness-effect-in-commodities.py
================================================
# https://quantpedia.com/strategies/skewness-effect-in-commodities/
#
# The investment universe consists of 27 futures contracts on commodities. Each month, investor calculates skewness (3rd moment of returns)
# from daily returns from data going 12 months into the past for all futures. Commodities are then sorted into quintiles and investor goes
# long quintile containing the commodities with the 20% lowest total skewness and short quintile containing the commodities with the 20% highest
# total skewness (over a ranking period of 12 months). The resultant portfolio is equally weighted and rebalanced each month.
import numpy as np
from AlgorithmImports import *
from scipy.stats import skew
class SkewnessEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = [
"CME_S1", # Soybean Futures, Continuous Contract
"CME_W1", # Wheat Futures, Continuous Contract
"CME_SM1", # Soybean Meal Futures, Continuous Contract
"CME_BO1", # Soybean Oil Futures, Continuous Contract
"CME_C1", # Corn Futures, Continuous Contract
"CME_O1", # Oats Futures, Continuous Contract
"CME_LC1", # Live Cattle Futures, Continuous Contract
"CME_FC1", # Feeder Cattle Futures, Continuous Contract
"CME_LN1", # Lean Hog Futures, Continuous Contract
"CME_GC1", # Gold Futures, Continuous Contract
"CME_SI1", # Silver Futures, Continuous Contract
"CME_PL1", # Platinum Futures, Continuous Contract
"CME_CL1", # Crude Oil Futures, Continuous Contract
"CME_HG1", # Copper Futures, Continuous Contract
"CME_LB1", # Random Length Lumber Futures, Continuous Contract
# "CME_NG1", # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
"CME_PA1", # Palladium Futures, Continuous Contract
"CME_RR1", # Rough Rice Futures, Continuous Contract
"CME_DA1", # Class III Milk Futures
"ICE_RS1", # Canola Futures, Continuous Contract
"ICE_GO1", # Gas Oil Futures, Continuous Contract
"CME_RB2", # Gasoline Futures, Continuous Contract
"CME_KW2", # Wheat Kansas, Continuous Contract
"ICE_WT1", # WTI Crude Futures, Continuous Contract
"ICE_CC1", # Cocoa Futures, Continuous Contract
"ICE_CT1", # Cotton No. 2 Futures, Continuous Contract
"ICE_KC1", # Coffee C Futures, Continuous Contract
"ICE_O1", # Heating Oil Futures, Continuous Contract
"ICE_OJ1", # Orange Juice Futures, Continuous Contract
"ICE_SB1", # Sugar No. 11 Futures, Continuous Contract
]
self.period = 12 * 21
self.quantile = 5
self.SetWarmup(self.period)
self.data = {}
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[symbol] = RollingWindow[float](self.period)
self.Schedule.On(
self.DateRules.MonthStart(self.symbols[0]),
self.TimeRules.At(0, 0),
self.Rebalance,
)
def OnData(self, data):
for symbol in self.symbols:
symbol_obj = self.Symbol(symbol)
if symbol_obj in data.Keys:
price = data[symbol_obj].Value
if price != 0:
self.data[symbol].Add(price)
def Rebalance(self):
if self.IsWarmingUp:
return
# Skewness calculation
skewness_data = {}
for symbol in self.symbols:
if self.data[symbol].IsReady:
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
< 5
):
prices = np.array([x for x in self.data[symbol]])
returns = (prices[:-1] / prices[1:]) - 1
if len(returns) == self.period - 1:
# NOTE: Manual skewness calculation example
# avg = np.average(returns)
# std = np.std(returns)
# skewness = (sum(np.power((x - avg), 3) for x in returns)) / ((self.return_history[symbol].maxlen-1) * np.power(std, 3))
skewness_data[symbol] = skew(returns)
long = []
short = []
if len(skewness_data) >= self.quantile:
# Skewness sorting
sorted_by_skewness = sorted(
skewness_data.items(), key=lambda x: x[1], reverse=True
)
quintile = int(len(sorted_by_skewness) / self.quantile)
long = [x[0] for x in sorted_by_skewness[-quintile:]]
short = [x[0] for x in sorted_by_skewness[:quintile]]
# Trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/small-capitalization-stocks-premium-anomaly.py
================================================
# https://quantpedia.com/strategies/small-capitalization-stocks-premium-anomaly/
#
# The investment universe contains all NYSE, AMEX, and NASDAQ stocks. Decile portfolios are formed based on the market capitalization
# of stocks. To capture “size” effect, SMB portfolio goes long small stocks (lowest decile) and short big stocks (highest decile).
#
# QC implementation changes:
# - Instead of all listed stock, we select top 3000 stocks by market cap from QC stock universe.
from AlgorithmImports import *
class ValueBooktoMarketFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.coarse_count = 3000
self.long = []
self.short = []
self.month = 12
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
return selected
def FineSelectionFunction(self, fine):
sorted_by_market_cap = sorted([x for x in fine if x.MarketCap != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))], \
key = lambda x:x.MarketCap, reverse=True)
top_by_market_cap = [x for x in sorted_by_market_cap[:self.coarse_count]]
quintile = int(len(top_by_market_cap) / 5)
self.long = [i.Symbol for i in top_by_market_cap[-quintile:]]
self.short = [i.Symbol for i in top_by_market_cap[:quintile]]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
long_count = len(self.long)
short_count = len(self.short)
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
# Leveraged portfolio - 100% long, 100% short.
for symbol in self.long:
self.SetHoldings(symbol, 1 / long_count)
for symbol in self.short:
self.SetHoldings(symbol, -1 / short_count)
self.long.clear()
self.short.clear()
def Selection(self):
if self.month == 12:
self.selection_flag = True
self.month += 1
if self.month > 12:
self.month = 1
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/soccer-clubs-stocks-arbitrage.py
================================================
# https://quantpedia.com/strategies/soccer-clubs-stocks-arbitrage/
#
# The investment universe consists of liquid soccer clubs’ stocks that are publicly traded.
# The investor then sells short stocks of clubs that play UEFA Championship matches (or other important matches)
# at the end of the business day before the match. Stocks are held for one day,
# and the portfolio of stocks is equally weighted (if there are multiple clubs with matches that day).
#
# QC Implementation:
#region imports
from AlgorithmImports import *
#endregion
class SoccerClubsStocksArbitrage(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.tickers = [
'FCPP', # Futebol Clube Do Porto
'SPSO', # Sporting Clube De Portugal
'SLBEN', # Benfica
'LAZI', # Lazio
'ASR', # AS Rome
'AJAX', # AJAX
'JUVE', # Juventus
'MANU', # Manchester United
'BVB', # Dortmund
'CCP', # Celtic
# 'BOLA' # Bali Bintang Sejahtera Tbk PT
]
self.match_dates = {}
for ticker in self.tickers:
security = self.AddData(QuantpediaSoccer, ticker, Resolution.Daily)
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
csv_string_file = self.Download('data.quantpedia.com/backtesting_data/equity/soccer/soccer_matches.csv')
lines = csv_string_file.split('\r\n')
for line in lines:
line_split = line.split(';')
date = datetime.strptime(line_split[0], "%d.%m.%Y").date()
self.match_dates[date] = []
for i in range(1, len(line_split)):
ticker = line_split[i]
self.match_dates[date].append(ticker)
def OnData(self, data):
self.Liquidate()
short = []
# Looking for todays date, because only daily closes are traded.
today = (self.Time - timedelta(days=1)).date()
if today in self.match_dates:
for ticker in self.tickers:
if ticker in self.match_dates[today] and ticker in data:
short.append(ticker)
for ticker in short:
self.SetHoldings(ticker, -1 / len(short))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaSoccer(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/equity/soccer/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaSoccer()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['price'] = float(split[1])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/synthetic-lending-rates-predict-subsequent-market-return.py
================================================
# https://quantpedia.com/strategies/synthetic-lending-rates-predict-subsequent-market-return/
#
# The investment universe consists of SPY ETF. Synthetic shorting costs data are obtained from Borrow Intensity Indicators by the CBOE (and includes 4877 stocks/ETFs).
# The paper utilizes the constant maturities of 45 days. Intraday SPY data are obtained from FirstRate Data. The aggregate (mean) borrow intensity is calculated as equally weighted borrow intensity of each stock/ETF in the sample at day t.
# The shorting costs data are estimated at a timestamp of 15:57. Calculate the change in the aggregate intensity at day t as the difference of aggregate borrowing intensity at day t and t-1.
# Buy the SPY ETF at 15:59 if the difference is positive and short the SPY if the difference is negative. The positions are held for one day and are closed at 15:58 at next day.
#
# QC Implementation changes:
# - Signal calculation and trade opening is done each day at 15:59.
#region imports
from AlgorithmImports import *
#endregion
class SyntheticLendingRatesPredictSubsequentMarketReturn(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1)
self.SetCash(100000)
self.spy_symbol:Symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
self.lending_data_symbol:Symbol = self.AddData(
QuantpediaLendingRates,
'lending_rate',
Resolution.Minute).Symbol
self.last_lending_mean = None
def OnData(self, data: Slice):
curr_time:datetime.datetime = self.Time
# liquidate on 15:58
if curr_time.hour == 15 and curr_time.minute == 58:
self.Liquidate(self.spy_symbol)
# lending rate data came in
if self.lending_data_symbol in data and data[self.lending_data_symbol]:
curr_lending_mean:float = data[self.lending_data_symbol].Value
if self.last_lending_mean:
# calculate daily change in lending rate
diff:float = curr_lending_mean - self.last_lending_mean
if diff > 0:
self.SetHoldings(self.spy_symbol, 1)
else:
self.SetHoldings(self.spy_symbol, -1)
self.last_lending_mean = curr_lending_mean
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaLendingRates(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/options/lending_rates_day_close_matur_45_days.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data:QuantpediaLendingRates = QuantpediaLendingRates()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split:list = line.split(';')
datetime_str:str = split[0] + ', 15:59'
data.Time = datetime.strptime(datetime_str, "%Y-%m-%d, %H:%M")
valid_values:list = list(filter(lambda value: value != '', split[1:]))
valid_values:list = list(map(lambda str_value: float(str_value), valid_values))
data.Value = np.mean(valid_values)
return data
================================================
FILE: static/strategies/term-structure-effect-in-commodities.py
================================================
# https://quantpedia.com/strategies/term-structure-effect-in-commodities/
#
# This simple strategy buys each month the 20% of commodities with the highest roll-returns and shorts the 20% of commodities with the lowest
# roll-returns and holds the long-short positions for one month. The contracts in each quintile are equally-weighted.
# The investment universe is all commodity futures contracts.
#
# QC implementation:
import numpy as np
from datetime import time
from AlgorithmImports import *
class TermStructure(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2009, 1, 1)
self.SetCash(100000)
symbols = {
'CME_S1': Futures.Grains.Soybeans,
'CME_W1' : Futures.Grains.Wheat,
'CME_SM1' : Futures.Grains.SoybeanMeal,
'CME_C1' : Futures.Grains.Corn,
'CME_O1' : Futures.Grains.Oats,
'CME_LC1' : Futures.Meats.LiveCattle,
'CME_FC1' : Futures.Meats.FeederCattle,
'CME_LN1' : Futures.Meats.LeanHogs,
'CME_GC1' : Futures.Metals.Gold,
'CME_SI1' : Futures.Metals.Silver,
'CME_PL1' : Futures.Metals.Platinum,
'CME_HG1' : Futures.Metals.Copper,
'CME_LB1' : Futures.Forestry.RandomLengthLumber,
'CME_NG1' : Futures.Energies.NaturalGas,
'CME_PA1' : Futures.Metals.Palladium,
'CME_DA1' : Futures.Dairy.ClassIIIMilk,
'CME_RB1' : Futures.Energies.Gasoline,
'ICE_WT1' : Futures.Energies.CrudeOilWTI,
'ICE_CC1' : Futures.Softs.Cocoa,
'ICE_O1' : Futures.Energies.HeatingOil,
'ICE_SB1' : Futures.Softs.Sugar11CME,
}
self.futures_info:dict = {}
self.quantile:int = 5
self.min_expiration_days:int = 2
self.max_expiration_days:int = 360
self.price_data:dict[Symbol, RollingWindow] = {}
self.period:int = 60
self.SetWarmup(self.period, Resolution.Daily)
for qp_symbol, qc_future in symbols.items():
# QP futures
data:Security = self.AddData(QuantpediaFutures, qp_symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.price_data[data.Symbol] = RollingWindow[float](self.period)
# QC futures
future:Future = self.AddFuture(qc_future, Resolution.Daily, dataNormalizationMode=DataNormalizationMode.Raw)
future.SetFilter(timedelta(days=self.min_expiration_days), timedelta(days=self.max_expiration_days))
self.futures_info[future.Symbol.Value] = FuturesInfo(data.Symbol)
self.recent_month:int = -1
def find_and_update_contracts(self, futures_chain, symbol) -> None:
near_contract:FuturesContract = None
dist_contract:FuturesContract = None
if symbol in futures_chain:
contracts:list = [contract for contract in futures_chain[symbol] if contract.Expiry.date() > self.Time.date()]
if len(contracts) >= 2:
contracts:list = sorted(contracts, key=lambda x: x.Expiry, reverse=False)
near_contract = contracts[0]
dist_contract = contracts[1]
self.futures_info[symbol].update_contracts(near_contract, dist_contract)
def OnData(self, data):
if data.FutureChains.Count > 0:
for symbol, futures_info in self.futures_info.items():
# check if near contract is expired or is not initialized
if not futures_info.is_initialized() or \
(futures_info.is_initialized() and futures_info.near_contract.Expiry.date() == self.Time.date()):
self.find_and_update_contracts(data.FutureChains, symbol)
roll_return:dict[Symbol, float] = {}
rebalance_flag:bool = False
# roll return calculation
for symbol, futures_info in self.futures_info.items():
# futures data is present in the algorithm
if futures_info.quantpedia_future in data and data[futures_info.quantpedia_future]:
# store daily data
self.price_data[futures_info.quantpedia_future].Add(data[futures_info.quantpedia_future].Value)
if not self.price_data[futures_info.quantpedia_future].IsReady:
continue
# new month rebalance
if self.Time.month != self.recent_month and not self.IsWarmingUp:
self.recent_month = self.Time.month
rebalance_flag = True
if rebalance_flag:
if futures_info.is_initialized():
near_c:FuturesContract = futures_info.near_contract
dist_c:FuturesContract = futures_info.distant_contract
if self.Securities.ContainsKey(near_c.Symbol) and self.Securities.ContainsKey(dist_c.Symbol):
raw_price1:float = self.Securities[near_c.Symbol].Close * self.Securities[symbol].SymbolProperties.PriceMagnifier
raw_price2:float = self.Securities[dist_c.Symbol].Close * self.Securities[symbol].SymbolProperties.PriceMagnifier
if raw_price1 != 0 and raw_price2 != 0:
roll_return[futures_info.quantpedia_future] = raw_price1 / raw_price2 - 1
if rebalance_flag:
weights:dict[Symbol, float] = {}
long:list[Symbol] = []
short:list[Symbol] = []
if len(roll_return) >= self.quantile:
# roll return sorting
sorted_by_roll:list = sorted(roll_return.items(), key = lambda x: x[1], reverse=True)
quantile:int = int(len(sorted_by_roll) / self.quantile)
long = [x[0] for x in sorted_by_roll[:quantile]]
short = [x[0] for x in sorted_by_roll[-quantile:]]
# trade execution
invested:list[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))
class FuturesInfo():
def __init__(self, quantpedia_future:Symbol) -> None:
self.quantpedia_future:Symbol = quantpedia_future
self.near_contract:FuturesContract = None
self.distant_contract:FuturesContract = None
def update_contracts(self, near_contract:FuturesContract, distant_contract:FuturesContract) -> None:
self.near_contract = near_contract
self.distant_contract = distant_contract
def is_initialized(self) -> bool:
return self.near_contract is not None and self.distant_contract is not None
# Custom fee model.
class CustomFeeModel():
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
return data
================================================
FILE: static/strategies/time-series-momentum-effect.py
================================================
# https://quantpedia.com/strategies/time-series-momentum-effect/
#
# The investment universe consists of 24 commodity futures, 12 cross-currency pairs (with 9 underlying currencies), 9 developed equity indices, and 13 developed
# government bond futures.
# Every month, the investor considers whether the excess return of each asset over the past 12 months is positive or negative and goes long on the contract if it is
# positive and short if negative. The position size is set to be inversely proportional to the instrument’s volatility. A univariate GARCH model is used to estimated
# ex-ante volatility in the source paper. However, other simple models could probably be easily used with good results (for example, the easiest one would be using
# historical volatility instead of estimated volatility). The portfolio is rebalanced monthly.
#
# QC implementation changes:
# - instead of GARCH model volatility, we have used simple historical volatility.
from math import sqrt
from AlgorithmImports import *
import numpy as np
import pandas as pd
class TimeSeriesMomentum(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(10000000)
self.symbols = [
"CME_S1", # Soybean Futures, Continuous Contract
"CME_W1", # Wheat Futures, Continuous Contract
"CME_SM1", # Soybean Meal Futures, Continuous Contract
"CME_BO1", # Soybean Oil Futures, Continuous Contract
"CME_C1", # Corn Futures, Continuous Contract
"CME_O1", # Oats Futures, Continuous Contract
"CME_LC1", # Live Cattle Futures, Continuous Contract
"CME_FC1", # Feeder Cattle Futures, Continuous Contract
"CME_LN1", # Lean Hog Futures, Continuous Contract
"CME_GC1", # Gold Futures, Continuous Contract
"CME_SI1", # Silver Futures, Continuous Contract
"CME_PL1", # Platinum Futures, Continuous Contract
"CME_CL1", # Crude Oil Futures, Continuous Contract
"CME_HG1", # Copper Futures, Continuous Contract
"CME_LB1", # Random Length Lumber Futures, Continuous Contract
"CME_NG1", # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
"CME_PA1", # Palladium Futures, Continuous Contract
"CME_RR1", # Rough Rice Futures, Continuous Contract
"CME_DA1", # Class III Milk Futures
"CME_RB1", # Gasoline Futures, Continuous Contract
"CME_KW1", # Wheat Kansas, Continuous Contract
"ICE_CC1", # Cocoa Futures, Continuous Contract
"ICE_CT1", # Cotton No. 2 Futures, Continuous Contract
"ICE_KC1", # Coffee C Futures, Continuous Contract
"ICE_O1", # Heating Oil Futures, Continuous Contract
"ICE_OJ1", # Orange Juice Futures, Continuous Contract
"ICE_SB1", # Sugar No. 11 Futures, Continuous Contract
"ICE_RS1", # Canola Futures, Continuous Contract
"ICE_GO1", # Gas Oil Futures, Continuous Contract
"ICE_WT1", # WTI Crude Futures, Continuous Contract
"CME_AD1", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1", # British Pound Futures, Continuous Contract #1
"CME_CD1", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1", # Euro FX Futures, Continuous Contract #1
"CME_JY1", # Japanese Yen Futures, Continuous Contract #1
"CME_MP1", # Mexican Peso Futures, Continuous Contract #1
"CME_NE1", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1", # Swiss Franc Futures, Continuous Contract #1
"ICE_DX1", # US Dollar Index Futures, Continuous Contract #1
"CME_NQ1", # E-mini NASDAQ 100 Futures, Continuous Contract #1
"EUREX_FDAX1", # DAX Futures, Continuous Contract #1
"CME_ES1", # E-mini S&P 500 Futures, Continuous Contract #1
"EUREX_FSMI1", # SMI Futures, Continuous Contract #1
"EUREX_FSTX1", # STOXX Europe 50 Index Futures, Continuous Contract #1
"LIFFE_FCE1", # CAC40 Index Futures, Continuous Contract #1
"LIFFE_Z1", # FTSE 100 Index Futures, Continuous Contract #1
"SGX_NK1", # SGX Nikkei 225 Index Futures, Continuous Contract #1
"CME_MD1", # E-mini S&P MidCap 400 Futures
"CME_TY1", # 10 Yr Note Futures, Continuous Contract #1
"CME_FV1", # 5 Yr Note Futures, Continuous Contract #1
"CME_TU1", # 2 Yr Note Futures, Continuous Contract #1
"ASX_XT1", # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 # 'Settlement price' instead of 'settle' on quandl.
"ASX_YT1", # 3 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 # 'Settlement price' instead of 'settle' on quandl.
"EUREX_FGBL1", # Euro-Bund (10Y) Futures, Continuous Contract #1
"EUREX_FBTP1", # Long-Term Euro-BTP Futures, Continuous Contract #1
"EUREX_FGBM1", # Euro-Bobl Futures, Continuous Contract #1
"EUREX_FGBS1", # Euro-Schatz Futures, Continuous Contract #1
"SGX_JB1", # SGX 10-Year Mini Japanese Government Bond Futures
"LIFFE_R1" # Long Gilt Futures, Continuous Contract #1
"MX_CGB1", # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 # 'Settlement price' instead of 'settle' on quandl.
]
self.period = 12 * 21
self.SetWarmUp(self.period, Resolution.Daily)
self.targeted_volatility = 0.10
self.vol_target_period = 60
self.leverage_cap = 4
# Daily rolled data.
self.data = {}
for symbol in self.symbols:
data = None
# Back adjusted and spliced data import.
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(20)
self.data[symbol] = RollingWindow[float](self.period)
self.recent_month = -1
def OnData(self, data):
# Store daily data.
for symbol in self.symbols:
if symbol in data and data[symbol]:
price = data[symbol].Value
self.data[symbol].Add(price)
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
# Performance and volatility data.
performance_volatility = {}
daily_returns = {}
for symbol in self.symbols:
if self.data[symbol].IsReady:
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
< 5
):
back_adjusted_prices = np.array([x for x in self.data[symbol]])
performance = back_adjusted_prices[0] / back_adjusted_prices[-1] - 1
daily_rets = (
back_adjusted_prices[:-1] / back_adjusted_prices[1:] - 1
)
back_adjusted_prices = back_adjusted_prices[
: self.vol_target_period
]
daily_rets = (
back_adjusted_prices[:-1] / back_adjusted_prices[1:] - 1
)
volatility_3M = np.std(daily_rets) * sqrt(252)
daily_returns[symbol] = daily_rets[::-1][: self.vol_target_period]
performance_volatility[symbol] = (performance, volatility_3M)
if len(performance_volatility) == 0:
return
# Performance sorting.
long = [x[0] for x in performance_volatility.items() if x[1][0] > 0]
short = [x[0] for x in performance_volatility.items() if x[1][0] < 0]
weight_by_symbol = {}
# Volatility weighting long and short leg separately.
ls_leverage = [] # long and short leverage
for sym_i, symbols in enumerate([long, short]):
total_volatility = sum([1 / performance_volatility[x][1] for x in symbols])
# Inverse volatility weighting.
weights = np.array(
[(1 / performance_volatility[x][1]) / total_volatility for x in symbols]
)
weights_sum = sum(weights)
weights = weights / weights_sum
df = pd.DataFrame()
i = 0
for symbol in symbols:
df[str(symbol)] = [x for x in daily_returns[symbol]]
weight_by_symbol[symbol] = weights[i] if sym_i == 0 else -weights[i]
i += 1
# volatility targeting
portfolio_vol = np.sqrt(
np.dot(weights.T, np.dot(df.cov() * 252, weights.T))
)
leverage = self.targeted_volatility / portfolio_vol
leverage = min(self.leverage_cap, leverage) # cap max leverage
ls_leverage.append(leverage)
# Trade execution.
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol, w in weight_by_symbol.items():
if w >= 0:
self.SetHoldings(symbol, w * ls_leverage[0])
# self.SetHoldings(symbol, w)
else:
self.SetHoldings(symbol, w * ls_leverage[1])
# self.SetHoldings(symbol, w)
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/trading-wti-brent-spread.py
================================================
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/strategies/trading-wti-brent-spread/
#
# A 20-day moving average of WTI/Brent spread is calculated each day. If the current spread value is above SMA 20 then we enter a short position
# in the spread on close (betting that the spread will decrease to the fair value represented by SMA 20). The trade is closed at the close of the
# trading day when the spread crosses below fair value. If the current spread value is below SMA 20 then we enter a long position betting that
# the spread will increase and the trade is closed at the close of the trading day when the spread crosses above fair value.
class WTIBRENTSpread(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = [
"ICE_WT1", # WTI Crude Futures, Continuous Contract
"ICE_B1" # Brent Crude Oil Futures, Continuous Contract
]
self.spread = RollingWindow[float](20)
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetLeverage(5)
data.SetFeeModel(CustomFeeModel())
def OnData(self, data):
symbol1 = self.Symbol(self.symbols[0])
symbol2 = self.Symbol(self.symbols[1])
if symbol1 in data.Keys and symbol2 in data.Keys and data[symbol1] and data[symbol2]:
price1 = data[symbol1].Price
price2 = data[symbol2].Price
if price1 != 0 and price2 != 0:
spread = price1 - price2
self.spread.Add(spread)
# MA calculation.
if self.spread.IsReady:
if (self.Time.date() - self.Securities[symbol1].GetLastData().Time.date()).days < 5 and (self.Time.date() - self.Securities[symbol2].GetLastData().Time.date()).days < 5:
spreads = [x for x in self.spread]
spread_ma20 = sum(spreads) / len(spreads)
current_spread = spreads[0]
if current_spread > spread_ma20:
self.SetHoldings(symbol1, -1)
self.SetHoldings(symbol2, 1)
elif current_spread < spread_ma20:
self.SetHoldings(symbol1, 1)
self.SetHoldings(symbol2, -1)
else:
self.Liquidate()
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/trend-following-effect-in-stocks.py
================================================
# https://quantpedia.com/strategies/trend-following-effect-in-stocks/
#
# The investment universe consists of US-listed companies. A minimum stock price filter is used to avoid penny stocks, and a minimum
# daily liquidity filter is used to avoid stocks that are not liquid enough. The entry signal occurs if today’s close is greater than
# or equal to the highest close during the stock’s entire history. A 10-period average true range trailing stop is used as an exit
# signal. The investor holds all stocks which satisfy entry criterion and are not stopped out. The portfolio is equally weighted and
# rebalanced daily. Transaction costs of 0.5% round-turn are deducted from each trade to account for estimated commission and slippage.
#
# QC implementation:
# - Universe consists of top 100 liquid US stocks.
import numpy as np
from AlgorithmImports import *
class TrendFollowingStocks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
self.course_count = 100
self.long = []
self.max_close = {}
self.atr = {}
self.sl_order = {}
self.sl_price = {}
self.selection = []
self.period = 10*12*21
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
symbol = security.Symbol
if symbol not in self.atr:
self.atr[symbol] = self.ATR(symbol, 10, Resolution.Daily)
if symbol not in self.max_close:
hist = self.History([self.Symbol(symbol)], self.period, Resolution.Daily)
if 'close' in hist.columns:
closes = hist['close']
self.max_close[symbol] = max(closes)
def CoarseSelectionFunction(self, coarse):
if self.IsWarmingUp: return
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
key=lambda x: x.DollarVolume, reverse=True)
self.selection = [x.Symbol for x in selected[:self.course_count]]
return self.selection
def OnData(self, data):
if self.IsWarmingUp:
return
for symbol in self.selection:
if symbol in data.Bars:
price = data[symbol].Value
if symbol not in self.max_close: continue
if price >= self.max_close[symbol]:
self.max_close[symbol] = price
self.long.append(symbol)
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
count = len(self.long) + len(stocks_invested)
if count == 0: return
# Update stoploss orders
for symbol in stocks_invested:
if not self.Securities[symbol].IsTradable:
self.Liquidate(symbol)
if self.atr[symbol].Current.Value == 0: continue
# Move SL
if symbol not in self.sl_price: continue
self.SetHoldings(symbol, 1 / count)
new_sl = self.Securities[symbol].Price - self.atr[symbol].Current.Value
if new_sl > self.sl_price[symbol]:
update_order_fields = UpdateOrderFields()
update_order_fields.StopPrice = new_sl # Update SL price
quantity = self.CalculateOrderQuantity(symbol, (1 / count))
update_order_fields.Quantity = quantity # Update SL quantity
self.sl_price[symbol] = new_sl
self.sl_order[symbol].Update(update_order_fields)
# self.Log('SL MOVED on ' + str(symbol) + ' to: ' + str(new_sl))
# Open new trades
for symbol in self.long:
if not self.Portfolio[symbol].Invested and self.atr[symbol].Current.Value != 0:
price = data[symbol].Value
if self.Securities[symbol].IsTradable:
unit_size = self.CalculateOrderQuantity(symbol, (1 / count))
self.MarketOrder(symbol, unit_size)
sl_price = price - self.atr[symbol].Current.Value
self.sl_price[symbol] = sl_price
if unit_size != 0:
self.sl_order[symbol] = self.StopMarketOrder(symbol, -unit_size, sl_price, 'SL')
# self.Log('SL SET on ' + str(symbol) + ' to: ' + str(sl_price))
self.long.clear()
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/turn-of-the-month-in-equity-indexes.py
================================================
# https://quantpedia.com/strategies/turn-of-the-month-in-equity-indexes/
#
# Buy SPY ETF 1 day (some papers say 4 days) before the end of the month and sell the 3rd trading day of the new month at the close.
from AlgorithmImports import *
class TurnoftheMonthinEquityIndexes(QCAlgorithm):
def Initialize(self):
self.SetStartDate(1998, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.sell_flag = False
self.days = 0
self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Rebalance)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Purchase)
def Purchase(self):
self.SetHoldings(self.symbol, 1)
def Rebalance(self):
self.sell_flag = True
def OnData(self, data):
if self.sell_flag:
self.days += 1
if self.days == 3:
self.Liquidate(self.symbol)
self.sell_flag = False
self.days = 0
================================================
FILE: static/strategies/value-and-momentum-factors-across-asset-classes.py
================================================
"""
data_tools.py
"""
# Bond yields
class QuandlAAAYield(PythonQuandl):
def __init__(self):
self.ValueColumnName = "BAMLC0A1CAAAEY"
class QuandlHighYield(PythonQuandl):
def __init__(self):
self.ValueColumnName = "BAMLH0A0HYM2EY"
# Quantpedia bond yield data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBondYield(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/bond_yield/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaBondYield()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(",")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data["yield"] = float(split[1])
data.Value = float(split[1])
return data
# Country PE data
# NOTE: IMPORTANT: Data order must be ascending (date-wise)
from dateutil.relativedelta import relativedelta
class CountryPE(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/economic/country_pe.csv",
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = CountryPE()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%Y") + relativedelta(years=1)
self.symbols = [
"Argentina",
"Australia",
"Austria",
"Belgium",
"Brazil",
"Canada",
"Chile",
"China",
"Egypt",
"France",
"Germany",
"Hong Kong",
"India",
"Indonesia",
"Ireland",
"Israel",
"Italy",
"Japan",
"Malaysia",
"Mexico",
"Netherlands",
"New Zealand",
"Norway",
"Philippines",
"Poland",
"Russia",
"Saudi Arabia",
"Singapore",
"South Africa",
"South Korea",
"Spain",
"Sweden",
"Switzerland",
"Taiwan",
"Thailand",
"Turkey",
"United Kingdom",
"United States",
]
index = 1
for symbol in self.symbols:
data[symbol] = float(split[index])
index += 1
data.Value = float(split[1])
return data
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# Quantpedia PE ratio data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaPERatio(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/economic/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaPERatio()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data["pe_ratio"] = float(split[1])
data.Value = float(split[1])
return data
# Quantpedia bond yield data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaBondYield(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/bond_yield/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaBondYield()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(",")
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data["yield"] = float(split[1])
data.Value = float(split[1])
return data
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource(
"data.quantpedia.com/backtesting_data/futures/{0}.csv".format(
config.Symbol.Value
),
SubscriptionTransportMedium.RemoteFile,
FileFormat.Csv,
)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit():
return None
split = line.split(";")
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data["back_adjusted"] = float(split[1])
data["spliced"] = float(split[2])
data.Value = float(split[1])
return data
"""
main.py
"""
# https://quantpedia.com/strategies/value-and-momentum-factors-across-asset-classes/
#
# Create an investment universe containing investable asset classes (could be US large-cap, mid-cap stocks, US REITS, UK, Japan, Emerging market stocks, US treasuries, US Investment grade bonds,
# US high yield bonds, Germany bonds, Japan bonds, US cash) and find a good tracking vehicle for each asset class (best vehicles are ETFs or index funds). Momentum ranking is done on price series.
# Valuation ranking is done on adjusted yield measure for each asset class. E/P (Earning/Price) measure is used for stocks, and YTM (Yield-to-maturity) is used for bonds. US, Japan, and Germany
# treasury yield are adjusted by -1%, US investment-grade bonds are adjusted by -2%, US High yield bonds are adjusted by -6%, emerging markets equities are adjusted by -1%, and US REITs are
# adjusted by -2% to get unbiased structural yields for each asset class. Rank each asset class by 12-month momentum, 1-month momentum, and by valuation and weight all three strategies (25% weight
# to 12m momentum, 25% weight to 1-month momentum, 50% weight to value strategy). Go long top quartile portfolio and go short bottom quartile portfolio.
#
# QC implementation changes:
# - Country PB data ends in 2019. Last known value is used for further years calculations for the sake of backtest.
import data_tools
class ValueandMomentumFactorsacrossAssetClasses(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2013, 1, 1)
self.SetCash(100000)
# investable asset, yield symbol, yield data access function, yield adjustment, reverse flag(PE -> EP)
self.assets = [
(
"SPY",
"MULTPL/SP500_EARNINGS_YIELD_MONTH",
data_tools.QuandlValue,
0,
True,
), # US large-cap
(
"MDY",
"MID_CAP_PE",
data_tools.QuantpediaPERatio,
0,
True,
), # US mid-cap stocks
(
"IYR",
"REITS_DIVIDEND_YIELD",
data_tools.QuantpediaPERatio,
-2,
False,
), # US REITS - same csv data format as PERatio files
("EWU", "United Kingdom", None, 0, True), # UK
("EWJ", "Japan", None, 0, True), # Japan
(
"EEM",
"EMERGING_MARKET_PE",
data_tools.QuantpediaPERatio,
-1,
True,
), # Emerging market stocks
(
"LQD",
"ML/AAAEY",
data_tools.QuandlAAAYield,
-2,
False,
), # US Investment grade bonds
(
"HYG",
"ML/USTRI",
data_tools.QuandlHighYield,
-6,
False,
), # US high yield bonds
(
"CME_TY1",
"US10YT",
data_tools.QuantpediaBondYield,
-1,
False,
), # US bonds
(
"EUREX_FGBL1",
"DE10YT",
data_tools.QuantpediaBondYield,
-1,
False,
), # Germany bonds
(
"SGX_JB1",
"JP10YT",
data_tools.QuantpediaBondYield,
-1,
False,
), # Japan bonds
(
"BIL",
"OECD/KEI_IRSTCI01_USA_ST_M",
data_tools.QuandlValue,
0,
False,
), # US cash
]
# country pe data
self.country_pe_data = self.AddData(data_tools.CountryPE, "CountryData").Symbol
self.data = {}
self.period = 12 * 21
self.SetWarmUp(self.period)
for symbol, yield_symbol, yield_access, _, _ in self.assets:
# investable asset
if yield_access == data_tools.QuantpediaBondYield:
data = self.AddData(
data_tools.QuantpediaFutures, symbol, Resolution.Daily
)
else:
data = self.AddEquity(symbol, Resolution.Daily)
# yield
if yield_access != None:
self.AddData(yield_access, yield_symbol, Resolution.Daily)
self.data[symbol] = RollingWindow[float](self.period)
data.SetFeeModel(CustomFeeModel(self))
data.SetLeverage(5)
self.recent_month = -1
def OnData(self, data):
if self.IsWarmingUp:
return
# store investable asset price data
for symbol, yield_symbol, _, _, _ in self.assets:
symbol_obj = self.Symbol(symbol)
if symbol_obj in data and data[symbol_obj]:
self.data[symbol].Add(data[symbol_obj].Value)
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
performance_1M = {}
performance_12M = {}
valuation = {}
# performance and valuation calculation
if (
self.Securities[self.country_pe_data].GetLastData()
and (
self.Time.date()
- self.Securities[self.country_pe_data].GetLastData().Time.date()
).days
<= 365
):
for (
symbol,
yield_symbol,
yield_access,
bond_adjustment,
reverse_flag,
) in self.assets:
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
< 3
):
if self.data[symbol].IsReady:
closes = [x for x in self.data[symbol]]
performance_1M[symbol] = closes[0] / closes[21] - 1
performance_12M[symbol] = (
closes[0] / closes[len(closes) - 1] - 1
)
if yield_access == None:
country_pb_data = self.Securities[
"CountryData"
].GetLastData()
if country_pb_data:
pe = country_pb_data[yield_symbol]
yield_value = pe
else:
yield_value = self.Securities[
self.Symbol(yield_symbol)
].Price
# reverse if needed, EP->PE
if reverse_flag:
yield_value = 1 / yield_value
if yield_value != 0:
valuation[symbol] = yield_value + bond_adjustment
long = []
short = []
if len(valuation) != 0:
# sort assets by metrics
sorted_by_p1 = sorted(performance_1M.items(), key=lambda x: x[1])
sorted_by_p12 = sorted(performance_12M.items(), key=lambda x: x[1])
sorted_by_value = sorted(valuation.items(), key=lambda x: x[1])
# rank assets
score = {}
for i, (symbol, _) in enumerate(sorted_by_p1):
score[symbol] = i * 0.25
for i, (symbol, _) in enumerate(sorted_by_p12):
score[symbol] += i * 0.25
for i, (symbol, _) in enumerate(sorted_by_value):
score[symbol] += i * 0.5
# sort by rank
sorted_by_rank = sorted(score, key=lambda x: score[x], reverse=True)
quartile = int(len(sorted_by_rank) / 4)
long = sorted_by_rank[:quartile]
short = sorted_by_rank[-quartile:]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
long_count = len(long)
short_count = len(short)
for symbol in long:
self.SetHoldings(symbol, 1 / long_count)
for symbol in short:
self.SetHoldings(symbol, -1 / short_count)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/value-book-to-market-factor.py
================================================
# https://quantpedia.com/strategies/value-book-to-market-factor/
#
# The investment universe contains all NYSE, AMEX, and NASDAQ stocks. To represent “value” investing, HML portfolio goes long high book-to-price stocks and short,
# low book-to-price stocks. In this strategy, we show the results for regular HML which is simply the average of the portfolio returns of HML small (which goes long
# cheap and short expensive only among small stocks) and HML large (which goes long cheap and short expensive only among large caps). The portfolio is equal-weighted
# and rebalanced monthly.
#
# QC implementation changes:
# - Instead of all listed stock, we select top 3000 stocks by market cap from QC stock universe.
from AlgorithmImports import *
class Value(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.coarse_count = 3000
self.long = []
self.short = []
self.month = 12
self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(5)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
return Universe.Unchanged
selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa']
return selected
def FineSelectionFunction(self, fine):
sorted_by_market_cap = sorted([x for x in fine if x.ValuationRatios.PBRatio != 0 and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))],
key = lambda x:x.MarketCap, reverse=True)
top_by_market_cap = [x for x in sorted_by_market_cap[:self.coarse_count]]
sorted_by_pb = sorted(top_by_market_cap, key = lambda x:(x.ValuationRatios.PBRatio), reverse=False)
quintile = int(len(sorted_by_pb) / 5)
self.long = [i.Symbol for i in sorted_by_pb[:quintile]]
self.short = [i.Symbol for i in sorted_by_pb[-quintile:]]
return self.long + self.short
def OnData(self, data):
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution.
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
if symbol not in self.long + self.short:
self.Liquidate(symbol)
# Leveraged portfolio - 100% long, 100% short.
for symbol in self.long:
self.SetHoldings(symbol, 1 / len(self.long))
for symbol in self.short:
self.SetHoldings(symbol, -1 / len(self.short))
self.long.clear()
self.short.clear()
def Selection(self):
if self.month == 12:
self.selection_flag = True
self.month += 1
if self.month > 12:
self.month = 1
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/value-factor-effect-within-countries.py
================================================
# https://quantpedia.com/strategies/value-factor-effect-within-countries/
#
# The investment universe consists of 32 countries with easily accessible equity markets (via ETFs, for example). At the end of every year,
# the investor calculates Shiller’s “CAPE” Cyclically Adjusted PE) ratio, for each country in his investment universe. CAPE is the ratio of
# the real price of the equity market (adjusted for inflation) to the 10-year average of the country’s equity index (again adjusted for inflation).
# The whole methodology is explained well on Shiller’s home page (http://www.econ.yale.edu/~shiller/data.htm) or
# http://turnkeyanalyst.com/2011/10/the-shiller-pe-ratio/). The investor then invests in the cheapest 33% of countries from his sample if those
# countries have a CAPE below 15. The portfolio is equally weighted (the investor holds 0% cash instead of countries with a CAPE higher than 15)
# and rebalanced yearly.
#region imports
from AlgorithmImports import *
#endregion
class ValueFactorCAPEEffectwithinCountries(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1)
self.SetCash(100000)
self.symbols = {
"Australia" : "EWA", # iShares MSCI Australia Index ETF
"Brazil" : "EWZ", # iShares MSCI Brazil Index ETF
"Canada" : "EWC", # iShares MSCI Canada Index ETF
"Switzerland" : "EWL", # iShares MSCI Switzerland Index ETF
"China" : "FXI", # iShares China Large-Cap ETF
"France" : "EWQ", # iShares MSCI France Index ETF
"Germany" : "EWG", # iShares MSCI Germany ETF
"Hong Kong" : "EWH", # iShares MSCI Hong Kong Index ETF
"Italy" : "EWI", # iShares MSCI Italy Index ETF
"Japan" : "EWJ", # iShares MSCI Japan Index ETF
"Korea" : "EWY", # iShares MSCI South Korea ETF
"Mexico" : "EWW", # iShares MSCI Mexico Inv. Mt. Idx
"Netherlands" : "EWN", # iShares MSCI Netherlands Index ETF
"South Africa" : "EZA", # iShares MSCI South Africe Index ETF
"Singapore" : "EWS", # iShares MSCI Singapore Index ETF
"Spain" : "EWP", # iShares MSCI Spain Index ETF
"Sweden" : "EWD", # iShares MSCI Sweden Index ETF
"Taiwan" : "EWT", # iShares MSCI Taiwan Index ETF
"UK" : "EWU", # iShares MSCI United Kingdom Index ETF
"USA" : "SPY", # SPDR S&P 500 ETF
"Russia" : "ERUS", # iShares MSCI Russia ETF
"Israel" : "EIS", # iShares MSCI Israel ETF
"India" : "INDA", # iShares MSCI India ETF
"Poland" : "EPOL", # iShares MSCI Poland ETF
"Turkey" : "TUR" # iShares MSCI Turkey ETF
}
for country, etf_symbol in self.symbols.items():
data = self.AddEquity(etf_symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
self.quantile:int = 3
self.max_missing_days:int = 31
# CAPE data import.
self.cape_data = self.AddData(CAPE, 'CAPE', Resolution.Daily).Symbol
self.recent_month:int = -1
def OnData(self, data:Slice) -> None:
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
if self.recent_month != 12:
return
price = {}
for country, etf_symbol in self.symbols.items():
if etf_symbol in data and data[etf_symbol]:
# cape data is still coming in
if self.Securities[self.cape_data].GetLastData() and (self.Time.date() - self.Securities[self.cape_data].GetLastData().Time.date()).days <= self.max_missing_days:
country_cape = self.Securities['CAPE'].GetLastData().GetProperty(country)
if country_cape < 15:
price[etf_symbol] = data[etf_symbol].Value
long = []
# Cape and price sorting.
if len(price) >= self.quantile:
sorted_by_price = sorted(price.items(), key = lambda x: x[1], reverse = True)
tercile = int(len(sorted_by_price) / self.quantile)
long = [x[0] for x in sorted_by_price[-tercile:]]
# Trade execution.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long:
self.Liquidate(symbol)
for symbol in long:
if self.Securities[etf_symbol].Price != 0 and self.Securities[etf_symbol].IsTradable:
self.SetHoldings(symbol, 1 / len(long))
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: https://indices.barclays/IM/21/en/indices/static/historic-cape.app
class CAPE(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/cape_by_country.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = CAPE()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
data['Australia'] = float(split[1])
data['Brazil'] = float(split[2])
data['Canada'] = float(split[3])
data['Switzerland'] = float(split[4])
data['China'] = float(split[5])
data['France'] = float(split[6])
data['Germany'] = float(split[7])
data['Hong Kong'] = float(split[8])
data['India'] = float(split[9])
data['Israel'] = float(split[10])
data['Italy'] = float(split[11])
data['Japan'] = float(split[12])
data['Korea'] = float(split[13])
data['Mexico'] = float(split[14])
data['Netherlands'] = float(split[15])
data['Poland'] = float(split[16])
data['Russia'] = float(split[17])
data['South Africa'] = float(split[18])
data['Singapore'] = float(split[19])
data['Spain'] = float(split[20])
data['Sweden'] = float(split[21])
data['Taiwan'] = float(split[22])
data['Turkey'] = float(split[23])
data['UK'] = float(split[24])
data['USA'] = float(split[25])
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
================================================
FILE: static/strategies/volatility-risk-premium-effect.py
================================================
# https://quantpedia.com/strategies/volatility-risk-premium-effect/
#
# Each month, at-the-money straddle, with one month until maturity, is sold at the bid price with a 5% option premium, and an offsetting 15%
# out-of-the-money puts are bought (at the ask price) as insurance against a market crash. The remaining cash and received option premium are
# invested in the index. The strategy is rebalanced monthly.
from AlgorithmImports import *
class VolatilityRiskPremiumEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
data = self.AddEquity("SPY", Resolution.Minute)
data.SetLeverage(5)
self.symbol = data.Symbol
option = self.AddOption("SPY", Resolution.Minute)
option.SetFilter(-20, 20, 25, 35)
self.last_day = -1
def OnData(self, slice):
# Check once a day.
if self.Time.day == self.last_day:
return
self.last_day = self.Time.day
for i in slice.OptionChains:
chains = i.Value
if not self.Portfolio.Invested:
# divide option chains into call and put options
calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
# if lists are empty return
if not calls or not puts:
return
underlying_price = self.Securities[self.symbol].Price
expiries = [i.Expiry for i in puts]
# determine expiration date nearly one month
expiry = min(
expiries, key=lambda x: abs((x.date() - self.Time.date()).days - 30)
)
strikes = [i.Strike for i in puts]
# determine at-the-money strike
strike = min(strikes, key=lambda x: abs(x - underlying_price))
# determine 15% out-of-the-money strike
otm_strike = min(
strikes, key=lambda x: abs(x - float(0.85) * underlying_price)
)
atm_call = [
i for i in calls if i.Expiry == expiry and i.Strike == strike
]
atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
otm_put = [
i for i in puts if i.Expiry == expiry and i.Strike == otm_strike
]
if atm_call and atm_put and otm_put:
options_q = int(
self.Portfolio.MarginRemaining / (underlying_price * 100)
)
# Set max leverage.
self.Securities[atm_call[0].Symbol].MarginModel = BuyingPowerModel(
5
)
self.Securities[atm_put[0].Symbol].MarginModel = BuyingPowerModel(5)
self.Securities[otm_put[0].Symbol].MarginModel = BuyingPowerModel(5)
# sell at-the-money straddle
self.Sell(atm_call[0].Symbol, options_q)
self.Sell(atm_put[0].Symbol, options_q)
# buy 15% out-of-the-money put
self.Buy(otm_put[0].Symbol, options_q)
# buy index.
self.SetHoldings(self.symbol, 1)
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
if len(invested) == 1:
self.Liquidate(self.symbol)