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: > > [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](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 | ![GitHub stars](https://badgen.net/github/stars/vnpy/vnpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [zipline](https://github.com/quantopian/zipline) | Zipline is a Pythonic algorithmic trading library. It is an event-driven system for backtesting. | ![GitHub stars](https://badgen.net/github/stars/quantopian/zipline) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [backtrader](https://github.com/mementum/backtrader) | Event driven Python Backtesting library for trading strategies | ![GitHub stars](https://badgen.net/github/stars/mementum/backtrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [QUANTAXIS](https://github.com/QUANTAXIS/QUANTAXIS) | QUANTAXIS 支持任务调度 分布式部署的 股票/期货/期权/港股/虚拟货币 数据/回测/模拟/交易/可视化/多账户 纯本地量化解决方案 | ![GitHub stars](https://badgen.net/github/stars/QUANTAXIS/QUANTAXIS) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [QuantConnect](https://github.com/QuantConnect/Lean) | Lean Algorithmic Trading Engine by QuantConnect (Python, C#) | ![GitHub stars](https://badgen.net/github/stars/QuantConnect/Lean) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Rqalpha](https://github.com/ricequant/rqalpha) | A extendable, replaceable Python algorithmic backtest && trading framework supporting multiple securities | ![GitHub stars](https://badgen.net/github/stars/ricequant/rqalpha) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [finmarketpy](https://github.com/cuemacro/finmarketpy) | Python library for backtesting trading strategies & analyzing financial markets (formerly pythalesians) | ![GitHub stars](https://badgen.net/github/stars/cuemacro/finmarketpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/kernc/backtesting.py) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [zvt](https://github.com/zvtvz/zvt) | Modular quant framework | ![GitHub stars](https://badgen.net/github/stars/zvtvz/zvt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [WonderTrader](https://github.com/wondertrader/wondertrader) | WonderTrader——量化研发交易一站式框架 | ![GitHub stars](https://badgen.net/github/stars/wondertrader/wondertrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) | A high-performance algorithmic trading platform and event-driven backtester | ![GitHub stars](https://badgen.net/github/stars/nautechsystems/nautilus_trader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PandoraTrader](https://github.com/pegasusTrader/PandoraTrader) | High-frequency quantitative trading platform based on c++ development, supporting multiple trading APIs and cross-platform | ![GitHub stars](https://badgen.net/github/stars/pegasusTrader/PandoraTrader) | ![made-with-c++](https://img.shields.io/badge/Made%20with-c++-1f425f.svg) | | [HFTBacktest](https://github.com/nkaz001/hftbacktest) | Highly precise backtest on HFT data in Python+Numba | ![GitHub stars](https://badgen.net/github/stars/nkaz001/hftbacktest) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/AsyncAlgoTrading/aat) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/sreenivasdoosa/sdoosa-algo-trade-python) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [lumibot](https://github.com/Lumiwealth/lumibot) | A very simple yet useful backtesting and sample based live trading framework (a bit slow to run...) | ![GitHub stars](https://badgen.net/github/stars/Lumiwealth/lumibot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [quanttrader](https://github.com/letianzj/quanttrader) | Backtest and live trading in Python. Event based. Similar to backtesting.py. | ![GitHub stars](https://badgen.net/github/stars/letianzj/quanttrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [gobacktest](https://github.com/gobacktest/gobacktest) | A Go implementation of event-driven backtesting framework | ![GitHub stars](https://badgen.net/github/stars/gobacktest/gobacktest) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [FlashFunk](https://github.com/HFQR/FlashFunk) | High Performance Runtime in Rust | ![GitHub stars](https://badgen.net/github/stars/HFQR/FlashFunk) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ### 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. | ![GitHub stars](https://badgen.net/github/stars/polakowo/vectorbt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [pysystemtrade](https://github.com/robcarver17/pysystemtrade) | Systematic Trading in python from book Systematic Trading by Rob Carver | ![GitHub stars](https://badgen.net/github/stars/robcarver17/pysystemtrade) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [bt](https://github.com/pmorissette/bt) | Flexible backtesting for Python based on Algo and Strategy Tree | ![GitHub stars](https://badgen.net/github/stars/pmorissette/bt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 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. | ![GitHub stars](https://badgen.net/github/stars/freqtrade/freqtrade) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Jesse](https://github.com/jesse-ai/jesse) | Jesse is an advanced crypto trading framework which aims to simplify researching and defining trading strategies. | ![GitHub stars](https://badgen.net/github/stars/jesse-ai/jesse) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [OctoBot](https://github.com/Drakkar-Software/OctoBot) | Cryptocurrency trading bot for TA, arbitrage and social trading with an advanced web interface | ![GitHub stars](https://badgen.net/github/stars/Drakkar-Software/OctoBot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Kelp](https://github.com/stellar/kelp) | Kelp is a free and open-source trading bot for the Stellar DEX and 100+ centralized exchanges | ![GitHub stars](https://badgen.net/github/stars/stellar/kelp) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [openlimits](https://github.com/nash-io/openlimits) | A Rust high performance cryptocurrency trading API with support for multiple exchanges and language wrappers. | ![GitHub stars](https://badgen.net/github/stars/nash-io/openlimits) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [bTrader](https://github.com/gabriel-milan/btrader) | Triangle arbitrage trading bot for Binance | ![GitHub stars](https://badgen.net/github/stars/gabriel-milan/btrader) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [crypto-crawler-rs](https://github.com/crypto-crawler/crypto-crawler-rs) | Crawl orderbook and trade messages from crypto exchanges | ![GitHub stars](https://badgen.net/github/stars/crypto-crawler/crypto-crawler-rs) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [Hummingbot](https://github.com/CoinAlpha/hummingbot) | A client for crypto market making | ![GitHub stars](https://badgen.net/github/stars/CoinAlpha/hummingbot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [cryptotrader-core](https://github.com/monomadic/cryptotrader-core) | Simple to use Crypto Exchange REST API client in rust. | ![GitHub stars](https://badgen.net/github/stars/monomadic/cryptotrader-core) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ## 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 | ![GitHub stars](https://badgen.net/github/stars/butor/blackbird) | ![made-with-c++](https://img.shields.io/badge/Made%20with-c++-1f425f.svg) | | [bitcoin-arbitrage](https://github.com/maxme/bitcoin-arbitrage) | Bitcoin arbitrage - opportunity detector | ![GitHub stars](https://badgen.net/github/stars/maxme/bitcoin-arbitrage) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ThetaGang](https://github.com/brndnmtthws/thetagang) | ThetaGang is an IBKR bot for collecting money | ![GitHub stars](https://badgen.net/github/stars/brndnmtthws/thetagang) | ![made-with-typescript](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [czsc](https://github.com/waditu/czsc) | 缠中说禅技术分析工具;缠论;股票;期货;Quant;量化交易 | ![GitHub stars](https://badgen.net/github/stars/waditu/czsc) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [R2 Bitcoin Arbitrager](https://github.com/bitrinjani/r2) | R2 Bitcoin Arbitrager is an automatic arbitrage trading system powered by Node.js + TypeScript | ![GitHub stars](https://badgen.net/github/stars/bitrinjani/r2) | ![made-with-typescript](https://img.shields.io/badge/Made%20with-TypeScript-1f425f.svg) | | [analyzingalpha](https://github.com/leosmigel/analyzingalpha) | Implementation of simple strategies | ![GitHub stars](https://badgen.net/github/stars/leosmigel/analyzingalpha) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyTrendFollow](https://github.com/chrism2671/PyTrendFollow) | PyTrendFollow - systematic futures trading using trend following | ![GitHub stars](https://badgen.net/github/stars/chrism2671/PyTrendFollow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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 | ![GitHub stars](https://badgen.net/github/stars/mrjbq7/ta-lib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/iamjinlei/go-tart) | ![made-with-go](https://img.shields.io/badge/Made%20with-go-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/twopirllc/pandas-ta) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [finta](https://github.com/peerchemist/finta) | Common financial technical indicators implemented in Pandas | ![GitHub stars](https://badgen.net/github/stars/peerchemist/finta) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ta-rust](https://github.com/greyblake/ta-rs) | Technical analysis library for Rust language | ![GitHub stars](https://badgen.net/github/stars/greyblake/ta-rs) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ### Metrics computation *Librairies of financial metrics.* | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [quantstats](https://github.com/ranaroussi/quantstats) | Portfolio analytics for quants, written in Python | ![GitHub stars](https://badgen.net/github/stars/ranaroussi/quantstats) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ffn](https://github.com/pmorissette/ffn) | A financial function library for Python | ![GitHub stars](https://badgen.net/github/stars/pmorissette/ffn) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 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 | ![GitHub stars](https://badgen.net/github/stars/robertmartin8/PyPortfolioOpt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib) | Portfolio Optimization and Quantitative Strategic Asset Allocation in Python | ![GitHub stars](https://badgen.net/github/stars/dcajasn/Riskfolio-Lib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/ssantoshp/Empyrial) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/jankrepl/deepdow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [spectre](https://github.com/Heerozh/spectre) | Portfolio Optimization and Quantitative Strategic Asset Allocation in Python | ![GitHub stars](https://badgen.net/github/stars/Heerozh/spectre) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 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 | ![GitHub stars](https://badgen.net/github/stars/google/tf-quant-finance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/domokane/FinancePy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyQL](https://github.com/enthought/pyql) | Python wrapper of the famous pricing library QuantLib | ![GitHub stars](https://badgen.net/github/stars/enthought/pyql) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### Risk | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [pyfolio](https://github.com/quantopian/pyfolio) | Portfolio and risk analytics in Python | ![GitHub stars](https://badgen.net/github/stars/quantopian/pyfolio) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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 | ![GitHub stars](https://badgen.net/github/stars/ccxt/ccxt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Ib_insync](https://github.com/erdewit/ib_insync) | Python sync/async framework for Interactive Brokers. | ![GitHub stars](https://badgen.net/github/stars/erdewit/ib_insync) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/hugues31/coinnect) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [PENDAX](https://github.com/CompendiumFi/PENDAX-SDK) | Javascript SDK for Trading, Data, and Websockets for FTX, FTXUS, OKX, Bybit, & More. | ![GitHub stars](https://badgen.net/github/stars/CompendiumFi/PENDAX-SDK) | ![made-with-javascript](https://img.shields.io/badge/Made%20with-Javascript-1f425f.svg) | ## Data Sources ### General | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [OpenBB Terminal](https://github.com/OpenBB-finance/OpenBBTerminal) | Investment Research for Everyone, Anywhere. | ![GitHub stars](https://badgen.net/github/stars/OpenBB-finance/OpenBBTerminal) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [TuShare](https://github.com/waditu/tushare) | TuShare is a utility for crawling historical data of China stocks | ![GitHub stars](https://badgen.net/github/stars/waditu/tushare) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [yfinance](https://github.com/ranaroussi/yfinance) | yfinance offers a threaded and Pythonic way to download market data from Yahoo!Ⓡ finance. | ![GitHub stars](https://badgen.net/github/stars/ranaroussi/yfinance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [AkShare](https://github.com/akfamily/akshare) | AKShare is an elegant and simple financial data interface library for Python, built for human beings! | ![GitHub stars](https://badgen.net/github/stars/akfamily/akshare) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [pandas-datareader](https://github.com/pydata/pandas-datareader) | Up to date remote data access for pandas, works for multiple versions of pandas. | ![GitHub stars](https://badgen.net/github/stars/pydata/pandas-datareader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Quandl](https://github.com/quandl/quandl-python) | Get millions of financial and economic dataset from hundreds of publishers via a single free API. | ![GitHub stars](https://badgen.net/github/stars/quandl/quandl-python) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/cuemacro/findatapy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Investpy](https://github.com/alvarobartt/investpy) | Financial Data Extraction from Investing.com with Python | ![GitHub stars](https://badgen.net/github/stars/alvarobartt/investpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/JerBouma/FundamentalAnalysis) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Wallstreet](https://github.com/mcdallas/wallstreet) | Wallstreet: Real time Stock and Option tools | ![GitHub stars](https://badgen.net/github/stars/mcdallas/wallstreet) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### Cryptocurrencies | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [Cryptofeed](https://github.com/bmoscon/cryptofeed) | Cryptocurrency Exchange Websocket Data Feed Handler with Asyncio | ![GitHub stars](https://badgen.net/github/stars/bmoscon/cryptofeed) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Gekko-Datasets](https://github.com/xFFFFF/Gekko-Datasets) | Gekko trading bot dataset dumps. Download and use history files in SQLite format. | ![GitHub stars](https://badgen.net/github/stars/xFFFFF/Gekko-Datasets) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [CryptoInscriber](https://github.com/Optixal/CryptoInscriber) | A live crypto currency historical trade data blotter. Download live historical trade data from any crypto exchange. | ![GitHub stars](https://badgen.net/github/stars/Optixal/CryptoInscriber) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Crypto Lake](https://github.com/crypto-lake/lake-api) | High frequency order book & trade data for crypto | ![GitHub stars](https://badgen.net/github/stars/crypto-lake/lake-api) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## Data Science | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [TensorFlow](https://github.com/tensorflow/tensorflow) | Fundamental algorithms for scientific computing in Python | ![GitHub stars](https://badgen.net/github/stars/tensorflow/tensorflow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Pytorch](https://github.com/pytorch/pytorch) | Tensors and Dynamic neural networks in Python with strong GPU acceleration | ![GitHub stars](https://badgen.net/github/stars/pytorch/pytorch) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Keras](https://github.com/keras-team/keras) | The most user friendly Deep Learning for humans in Python | ![GitHub stars](https://badgen.net/github/stars/keras-team/keras) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Scikit-learn](https://github.com/scikit-learn/scikit-learn) | Machine learning in Python | ![GitHub stars](https://badgen.net/github/stars/scikit-learn/scikit-learn) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/pandas-dev/pandas) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Numpy](https://github.com/numpy/numpy) | The fundamental package for scientific computing with Python | ![GitHub stars](https://badgen.net/github/stars/numpy/numpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Scipy](https://github.com/scipy/scipy) | Fundamental algorithms for scientific computing in Python | ![GitHub stars](https://badgen.net/github/stars/scipy/scipy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyMC](https://github.com/pymc-devs/pymc) | Probabilistic Programming in Python: Bayesian Modeling and Probabilistic Machine Learning with Aesara | ![GitHub stars](https://badgen.net/github/stars/pymc-devs/pymc) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Cvxpy](https://github.com/cvxpy/cvxpy) | A Python-embedded modeling language for convex optimization problems. | ![GitHub stars](https://badgen.net/github/stars/cvxpy/cvxpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## Databases | Repository | Description | Stars | Made with | |------------|-------------|-------|-----------| | [Marketstore](https://github.com/alpacahq/marketstore) | DataFrame Server for Financial Timeseries Data | ![GitHub stars](https://badgen.net/github/stars/alpacahq/marketstore) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [Tectonicdb](https://github.com/0b01/tectonicdb) | Tectonicdb is a fast, highly compressed standalone database and streaming protocol for order book ticks. | ![GitHub stars](https://badgen.net/github/stars/0b01/tectonicdb) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [ArcticDB (Man Group)](https://github.com/man-group/arcticdb) | High performance datastore for time series and tick data | ![GitHub stars](https://badgen.net/github/stars/man-group/ArcticDB) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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. | ![GitHub stars](https://badgen.net/github/stars/ray-project/ray) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Dask](https://github.com/dask/dask) | Parallel computing with task scheduling in Python with a Pandas like API | ![GitHub stars](https://badgen.net/github/stars/dask/dask) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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 | ![GitHub stars](https://badgen.net/github/stars/janestreet/incremental) | ![made-with-ocaml](https://img.shields.io/badge/Made%20with-Ocaml-1f425f.svg) | | [Man MDF](https://github.com/man-group/mdf) | Data-flow programming toolkit for Python | ![GitHub stars](https://badgen.net/github/stars/man-group/mdf) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [GraphKit](https://github.com/yahoo/graphkit) | A lightweight Python module for creating and running ordered graphs of computations. | ![GitHub stars](https://badgen.net/github/stars/yahoo/graphkit) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Tributary](https://github.com/timkpaine/tributary) | Streaming reactive and dataflow graphs in Python | ![GitHub stars](https://badgen.net/github/stars/timkpaine/tributary) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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. | ![GitHub stars](https://badgen.net/github/stars/microsoft/qlib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/AI4Finance-Foundation/FinRL) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/hudson-and-thames/mlfinlab) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [TradingGym](https://github.com/Yvictor/TradingGym) | Trading and Backtesting environment for training reinforcement learning agent or simple rule base algo. | ![GitHub stars](https://badgen.net/github/stars/Yvictor/TradingGym) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Stock Trading Bot using Deep Q-Learning](https://github.com/pskrunner14/trading-bot) | Stock Trading Bot using Deep Q-Learning | ![GitHub stars](https://badgen.net/github/stars/pskrunner14/trading-bot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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. | ![GitHub stars](https://badgen.net/github/stars/facebook/prophet) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [statsmodels](https://github.com/statsmodels/statsmodels) | Python module that allows users to explore data, estimate statistical models, and perform statistical tests. | ![GitHub stars](https://badgen.net/github/stars/statsmodels/statsmodels) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [tsfresh](https://github.com/blue-yonder/tsfresh) | Automatic extraction of relevant features from time series. | ![GitHub stars](https://badgen.net/github/stars/blue-yonder/tsfresh) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [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. | ![GitHub stars](https://badgen.net/github/stars/alkaline-ml/pmdarima) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 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. | ![GitHub stars](https://badgen.net/github/stars/man-group/dtale) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [mplfinance](https://github.com/matplotlib/mplfinance) | Financial Markets Data Visualization using Matplotlib | ![GitHub stars](https://badgen.net/github/stars/matplotlib/mplfinance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [btplotting](https://github.com/happydasch/btplotting) | btplotting provides plotting for backtests, optimization results and live data from backtrader. | ![GitHub stars](https://badgen.net/github/stars/happydasch/btplotting) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | # 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) | ![](https://badgen.net/badge/reviews/14%20161/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/12%20278/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/6%20969/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/2%20537/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/1%20229/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [Introduction To Algo Trading: How Retail Traders Can Successfully Compete With Professional Traders - Kevin J Davey](https://amzn.to/39Tf7JC) | ![](https://badgen.net/badge/reviews/131/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [Algorithmic Trading and DMA: An introduction to direct access trading strategies - Barry Johnson](https://amzn.to/3xYb0UN) | ![](https://badgen.net/badge/reviews/69/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | ## Biography | Title | Reviews | Rating | |----------|---------|--------| | [My Life as a Quant: Reflections on Physics and Finance - Emanuel Derman](https://amzn.to/3A8KudR) | ![](https://badgen.net/badge/reviews/192/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [How I Became a Quant: Insights from 25 of Wall Street’s Elite: - Barry Schachter](https://amzn.to/3Alf8kz) | ![](https://badgen.net/badge/reviews/27/blue) | ![](https://badgen.net/badge/rating/3.7/blue) | ## Coding | Title | Reviews | Rating | |----------|---------|--------| | [Python for Finance: Mastering Data-Driven Finance - Yves Hilpisch](https://amzn.to/3NhkTlP) | ![](https://badgen.net/badge/reviews/249/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [Trading Evolved: Anyone can Build Killer Trading Strategies in Python - Andreas F. Clenow](https://amzn.to/3A0jcGB) | ![](https://badgen.net/badge/reviews/173/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Python for Algorithmic Trading: From Idea to Cloud Deployment - Yves Hilpisch](https://amzn.to/3bpkd0C) | ![](https://badgen.net/badge/reviews/90/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [Algorithmic Trading with Python: Quantitative Methods and Strategy Development - Chris Conlan](https://amzn.to/3u3cxYo) | ![](https://badgen.net/badge/reviews/48/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [Learn Algorithmic Trading: Build and deploy algorithmic trading systems and strategies using Python and advanced data analysis - Sebastien Donadio](https://amzn.to/3NqNghA) | ![](https://badgen.net/badge/reviews/46/blue) | ![](https://badgen.net/badge/rating/4.1/blue) | ## Crypto | Title | Reviews | Rating | |----------|---------|--------| | [The Bitcoin Standard: The Decentralized Alternative to Central Banking - Saifedean Ammous](https://amzn.to/3QMJgec) | ![](https://badgen.net/badge/reviews/5%20136/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Bitcoin Billionaires: A True Story of Genius, Betrayal, and Redemption - Ben Mezrich](https://amzn.to/39SkdWt) | ![](https://badgen.net/badge/reviews/1%20787/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [Mastering Bitcoin: Programming the Open Blockchain - Andreas M. Antonopoulos](https://amzn.to/3NniZ3p) | ![](https://badgen.net/badge/reviews/955/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Why Buy Bitcoin: Investing Today in the Money of Tomorrow - Andy Edstrom](https://amzn.to/3OMcKqZ) | ![](https://badgen.net/badge/reviews/192/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | ## 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) | ![](https://badgen.net/badge/reviews/38%20087/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [How I Invest My Money: Finance experts reveal how they save, spend, and invest - Joshua Brown, Brian Portnoy](https://amzn.to/3A4rsoU) | ![](https://badgen.net/badge/reviews/892/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Naked Forex: High-Probability Techniques for Trading Without Indicators - Alex Nekritin](https://amzn.to/3NkrAUj) | ![](https://badgen.net/badge/reviews/720/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/441/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Option Volatility and Pricing: Advanced Trading Strategies and Techniques, 2nd Edition - Sheldon Natenberg](https://amzn.to/3btOxXL) | ![](https://badgen.net/badge/reviews/388/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/305/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/242/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/163/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/123/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/105/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Algorithmic Trading: Winning Strategies and Their Rationale - Ernest P. Chan](https://amzn.to/3xWi8kd) | ![](https://badgen.net/badge/reviews/100/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/98/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/67/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/61/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/42/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [Machine Trading: Deploying Computer Algorithms to Conquer the Markets - Ernest P. Chan](https://amzn.to/3OIBe4o) | ![](https://badgen.net/badge/reviews/53/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/51/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk - Richard Grinold, Ronald Kahn](https://amzn.to/3xMKaic) | ![](https://badgen.net/badge/reviews/46/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/37/blue) | ![](https://badgen.net/badge/rating/3.8/blue) | | [Advances in Active Portfolio Management: New Developments in Quantitative Investing - Richard Grinold, Ronald Kahn](https://amzn.to/3xUTK2z) | ![](https://badgen.net/badge/reviews/19/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Professional Automated Trading: Theory and Practice - Eugene A. Durenard](https://amzn.to/3yhfOpw) | ![](https://badgen.net/badge/reviews/15/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Algorithmic Trading and Quantitative Strategies (Chapman and Hall/CRC Financial Mathematics Series) - Raja Velu, Maxence Hardy, Daniel Nehren](https://amzn.to/3xUTQXZ) | ![](https://badgen.net/badge/reviews/11/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/2/blue) | ![](https://badgen.net/badge/rating/3/blue) | ## 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) | ![](https://badgen.net/badge/reviews/76/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/52/blue) | ![](https://badgen.net/badge/rating/4.1/blue) | | [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) | ![](https://badgen.net/badge/reviews/38/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/11/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [Market Microstructure in Practice - Charles-Albert Lehalle, Sophie Laruelle](https://www.amazon.fr/Market-Microstructure-Practice-Sophie-Laruelle/dp/9813231122) | ![](https://badgen.net/badge/reviews/8/blue) | ![](https://badgen.net/badge/rating/3.9/blue) | | [The Financial Mathematics of Market Liquidity - Olivier Gueant](https://www.amazon.com/Financial-Mathematics-Market-Liquidity-Execution/dp/1498725473) | ![](https://badgen.net/badge/reviews/6/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/1/blue) | ![](https://badgen.net/badge/rating/3/blue) | ## 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) | ![](https://badgen.net/badge/reviews/532/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/446/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/229/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/96/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/76/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/38/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/15/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | # Videos | Title | Likes | |--------------------------------------------------------------------|-------| | [Krish Naik - Machine learning tutorials and their Application in Stock Prediction](https://www.youtube.com/watch?v=H6du_pfuznE) | ![](https://badgen.net/badge/likes/6.3k/blue) | | [QuantInsti Youtube - webinars about Machine Learning for trading](https://www.youtube.com/user/quantinsti/search?query=machine+learning) | ![](https://badgen.net/badge/likes/6.1k/blue) | | [Siraj Raval - Videos about stock market prediction using Deep Learning](https://www.youtube.com/channel/UCWN3xxRkmTPmbKwht9FuE5A/search?query=trading) | ![](https://badgen.net/badge/likes/1.7k/blue) | | [Quantopian - Webinars about Machine Learning for trading](https://www.youtube.com/channel/UC606MUq45P3zFLa4VGKbxsg/search?query=machine+learning) | ![](https://badgen.net/badge/likes/1.5k/blue) | | [Sentdex - Machine Learning for Forex and Stock analysis and algorithmic trading](https://www.youtube.com/watch?v=v_L9jR8P-54&list=PLQVvvaa0QuDe6ZBtkCNWNUbdaBo2vA4RO) | ![](https://badgen.net/badge/likes/1.5k/blue) | | [QuantNews - Machine Learning for Algorithmic Trading 3 part series](https://www.youtube.com/playlist?list=PLHJACfjILJ-91qkw5YC83S6COKGscctzz) | ![](https://badgen.net/badge/likes/806/blue) | | [Sentdex - Python programming for Finance (a few videos including Machine Learning)](https://www.youtube.com/watch?v=Z-5wNWgRJpk&index=9&list=PLQVvvaa0QuDcOdF96TBtRtuQksErCEBYZ) | ![](https://badgen.net/badge/likes/735/blue) | | [Chat with Traders EP042 - Machine learning for algorithmic trading with Bert Mouler](https://www.youtube.com/watch?v=i8FNO8r7PaE) | ![](https://badgen.net/badge/likes/687/blue) | | [Tucker Balch - Applying Deep Reinforcement Learning to Trading](https://www.youtube.com/watch?v=Pka0DC_P17k) | ![](https://badgen.net/badge/likes/487/blue) | | [Ernie Chan - Machine Learning for Quantitative Trading Webinar](https://www.youtube.com/watch?v=72aEDjwGMr8&t=1023s) | ![](https://badgen.net/badge/likes/436/blue) | | [Chat with Traders EP147 - Detective work leading to viable trading strategies with Tom Starke](https://www.youtube.com/watch?v=JjXw9Mda7eY) | ![](https://badgen.net/badge/likes/407/blue) | | [Chat with Traders EP142 - Algo trader using automation to bypass human flaws with Bert Mouler](https://www.youtube.com/watch?v=ofL66mh6Tw0) | ![](https://badgen.net/badge/likes/316/blue) | | [Master Thesis presentation, Uni of Essex - Analyzing the Limit Order Book, A Deep Learning Approach](https://www.youtube.com/watch?v=qxSh2VFmRGw) | ![](https://badgen.net/badge/likes/264/blue) | | [Howard Bandy - Machine Learning Trading System Development Webinar](https://www.youtube.com/watch?v=v729evhMpYk&t=1s) | ![](https://badgen.net/badge/likes/253/blue) | | [Chat With Traders EP131 - Trading strategies, powered by machine learning with Morgan Slade](https://www.youtube.com/watch?v=EbWbeYu8zwg) | ![](https://badgen.net/badge/likes/229/blue) | | [Chat with Traders Quantopian 5 - Good Uses of Machine Learning in Finance with Max Margenot](https://www.youtube.com/watch?v=Zj5sXWv9SDM) | ![](https://badgen.net/badge/likes/198/blue) | | [Hitoshi Harada, CTO at Alpaca - Deep Learning in Finance Talk](https://www.youtube.com/watch?v=FoQKCeDuPiY) | ![](https://badgen.net/badge/likes/147/blue) | | [Better System Trader EP028 - David Aronson shares research into indicators that identify Bull and Bear markets.](https://www.youtube.com/watch?v=Q4rV0Y9NokI) | ![](https://badgen.net/badge/likes/97/blue) | | [Prediction Machines - Deep Learning with Python in Finance Talk](https://www.youtube.com/watch?v=xvm-M-R2fZY) | ![](https://badgen.net/badge/likes/87/blue) | | [Better System Trader EP064 - Cryptocurrencies and Machine Learning with Bert Mouler](https://www.youtube.com/watch?v=YgRTd4nLJoU) | ![](https://badgen.net/badge/likes/35/blue) | | [Better System Trader EP023 - Portfolio manager Michael Himmel talks AI and machine learning in trading](https://www.youtube.com/watch?v=9tZjeyhfG0g) | ![](https://badgen.net/badge/likes/29/blue) | | [Better System Trader EP082 - Machine Learning With Kris Longmore](https://www.youtube.com/watch?v=0syNgsd635M) | ![](https://badgen.net/badge/likes/18/blue) | # 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上分享来帮助。 > > [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](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月正式发布,已经一步步成长为一个全功能的量化交易平台。 | ![GitHub stars](https://badgen.net/github/stars/vnpy/vnpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [zipline](https://github.com/quantopian/zipline) | Zipline是一个Pythonic算法交易库。它是一个事件驱动的系统,用于回溯测试。 | ![GitHub stars](https://badgen.net/github/stars/quantopian/zipline) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [backtrader](https://github.com/mementum/backtrader) | 事件驱动的Python交易策略回测库 | ![GitHub stars](https://badgen.net/github/stars/mementum/backtrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [QUANTAXIS](https://github.com/QUANTAXIS/QUANTAXIS) | QUANTAXIS 支持任务调度 分布式部署的 股票/期货/期权/港股/虚拟货币 数据/回测/模拟/交易/可视化/多账户 纯本地量化解决方案 | ![GitHub stars](https://badgen.net/github/stars/QUANTAXIS/QUANTAXIS) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [QuantConnect](https://github.com/QuantConnect/Lean) | QuantConnect的精益算法交易引擎(Python,C#)。 | ![GitHub stars](https://badgen.net/github/stars/QuantConnect/Lean) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Rqalpha](https://github.com/ricequant/rqalpha) | 一个可扩展、可替换的Python算法回测和交易框架,支持多种证券 | ![GitHub stars](https://badgen.net/github/stars/ricequant/rqalpha) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [finmarketpy](https://github.com/cuemacro/finmarketpy) | 用于回测交易策略和分析金融市场的Python库(前身为pythalesians)。 | ![GitHub stars](https://badgen.net/github/stars/cuemacro/finmarketpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [backtesting.py](https://github.com/kernc/backtesting.py) | Backtesting.py是一个Python框架,用于根据历史(过去)数据推断交易策略的可行性。Backtesting.py在Backtrader的基础上进行了改进,并以各种方式超越了其他可获得的替代方案,Backtesting.py是轻量级的、快速的、用户友好的、直观的、互动的、智能的,并希望是面向未来的。 | ![GitHub stars](https://badgen.net/github/stars/kernc/backtesting.py) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [zvt](https://github.com/zvtvz/zvt) | 模块化的量化框架 | ![GitHub stars](https://badgen.net/github/stars/zvtvz/zvt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [WonderTrader](https://github.com/wondertrader/wondertrader) | WonderTrader——量化研发交易一站式框架 | ![GitHub stars](https://badgen.net/github/stars/wondertrader/wondertrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) | 一个高性能的算法交易平台和事件驱动的回测器 | ![GitHub stars](https://badgen.net/github/stars/nautechsystems/nautilus_trader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PandoraTrader](https://github.com/pegasusTrader/PandoraTrader) | 基于c++开发,支持多种交易API,跨平台的高频量化交易平台 | ![GitHub stars](https://badgen.net/github/stars/pegasusTrader/PandoraTrader) | ![made-with-c++](https://img.shields.io/badge/Made%20with-c++-1f425f.svg) | [HFTBacktest](https://github.com/nkaz001/hftbacktest) | Python+Numba 对高频交易数据进行高精度回测 | ![GitHub stars](https://badgen.net/github/stars/nkaz001/hftbacktest) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [aat](https://github.com/AsyncAlgoTrading/aat) | 一个异步的、事件驱动的框架,用于用python编写算法交易策略,并可选择用C++进行加速。它的设计是模块化和可扩展的,支持各种工具和策略,在多个交易所之间进行实时交易。 | ![GitHub stars](https://badgen.net/github/stars/AsyncAlgoTrading/aat) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [sdoosa-algo-trade-python](https://github.com/sreenivasdoosa/sdoosa-algo-trade-python) | 这个项目主要是为那些有兴趣学习使用python解释器编写自己的交易算法的algo交易新手准备的。 | ![GitHub stars](https://badgen.net/github/stars/sreenivasdoosa/sdoosa-algo-trade-python) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [lumibot](https://github.com/Lumiwealth/lumibot) | 一个非常简单而有用的回溯测试和基于样本的实时交易框架(运行速度有点慢......)。 | ![GitHub stars](https://badgen.net/github/stars/Lumiwealth/lumibot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [quanttrader](https://github.com/letianzj/quanttrader) | 在Python中进行回测和实时交易。基于事件。类似于backtesting.py。 | ![GitHub stars](https://badgen.net/github/stars/letianzj/quanttrader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [gobacktest](https://github.com/gobacktest/gobacktest) | 事件驱动的回溯测试框架的Go实现 | ![GitHub stars](https://badgen.net/github/stars/gobacktest/gobacktest) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [FlashFunk](https://github.com/HFQR/FlashFunk) | Rust中的高性能运行时 | ![GitHub stars](https://badgen.net/github/stars/HFQR/FlashFunk) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ### 一般 - 基于矢量的框架 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [vectorbt](https://github.com/polakowo/vectorbt) | vectorbt采取了一种新颖的回测方法:它完全在pandas和NumPy对象上运行,并由Numba加速,以速度和规模分析任何数据。这允许在几秒钟内对成千上万的策略进行测试。 | ![GitHub stars](https://badgen.net/github/stars/polakowo/vectorbt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [pysystemtrade](https://github.com/robcarver17/pysystemtrade) | 罗布-卡弗的《系统交易》一书中的python系统交易 | ![GitHub stars](https://badgen.net/github/stars/robcarver17/pysystemtrade) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [bt](https://github.com/pmorissette/bt) | 基于Algo和策略树的Python的灵活回测 | ![GitHub stars](https://badgen.net/github/stars/pmorissette/bt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 加密货币 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Freqtrade](https://github.com/freqtrade/freqtrade) | Freqtrade是一个用Python编写的免费和开源的加密货币交易机器人。它被设计为支持所有主要交易所,并通过Telegram进行控制。它包含回测、绘图和资金管理工具,以及通过机器学习进行策略优化。 | ![GitHub stars](https://badgen.net/github/stars/freqtrade/freqtrade) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Jesse](https://github.com/jesse-ai/jesse) | Jesse是一个先进的加密货币交易框架,旨在简化研究和定义交易策略。 | ![GitHub stars](https://badgen.net/github/stars/jesse-ai/jesse) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [OctoBot](https://github.com/Drakkar-Software/OctoBot) | 用于TA、套利和社会交易的加密货币交易机器人,具有先进的网络界面 | ![GitHub stars](https://badgen.net/github/stars/Drakkar-Software/OctoBot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Kelp](https://github.com/stellar/kelp) | Kelp是一个免费和开源的交易机器人,适用于Stellar DEX和100多个集中式交易所 | ![GitHub stars](https://badgen.net/github/stars/stellar/kelp) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [openlimits](https://github.com/nash-io/openlimits) | 一个Rust高性能的加密货币交易API,支持多个交易所和语言封装器。 | ![GitHub stars](https://badgen.net/github/stars/nash-io/openlimits) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [bTrader](https://github.com/gabriel-milan/btrader) | Binance的三角套利交易机器人 | ![GitHub stars](https://badgen.net/github/stars/gabriel-milan/btrader) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [crypto-crawler-rs](https://github.com/crypto-crawler/crypto-crawler-rs) | 抓取加密货币交易所的订单簿和交易信息 | ![GitHub stars](https://badgen.net/github/stars/crypto-crawler/crypto-crawler-rs) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [Hummingbot](https://github.com/CoinAlpha/hummingbot) | 一个用于加密货币做市的客户 | ![GitHub stars](https://badgen.net/github/stars/CoinAlpha/hummingbot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [cryptotrader-core](https://github.com/monomadic/cryptotrader-core) | 简单的使用Rust中的加密货币交易所REST API客户端。 | ![GitHub stars](https://badgen.net/github/stars/monomadic/cryptotrader-core) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ## 交易机器人 *交易机器人和阿尔法模型。其中一些是旧的,没有维护。* | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Blackbird](https://github.com/butor/blackbird) | 黑鸟比特币套利:市场中立的多/空策略 | ![GitHub stars](https://badgen.net/github/stars/butor/blackbird) | ![made-with-c++](https://img.shields.io/badge/Made%20with-c++-1f425f.svg) | | [bitcoin-arbitrage](https://github.com/maxme/bitcoin-arbitrage) | 比特币套利 - 机会检测器 | ![GitHub stars](https://badgen.net/github/stars/maxme/bitcoin-arbitrage) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ThetaGang](https://github.com/brndnmtthws/thetagang) | ThetaGang是一个用于收集资金的IBKR机器人 | ![GitHub stars](https://badgen.net/github/stars/brndnmtthws/thetagang) | ![made-with-typescript](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [czsc](https://github.com/waditu/czsc) | 缠中说禅技术分析工具;缠论;股票;期货;Quant;量化交易 | ![GitHub stars](https://badgen.net/github/stars/waditu/czsc) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [R2 Bitcoin Arbitrager](https://github.com/bitrinjani/r2) | R2 Bitcoin Arbitrager是一个由Node.js + TypeScript驱动的自动套利交易系统。 | ![GitHub stars](https://badgen.net/github/stars/bitrinjani/r2) | ![made-with-typescript](https://img.shields.io/badge/Made%20with-TypeScript-1f425f.svg) | | [analyzingalpha](https://github.com/leosmigel/analyzingalpha) | 实施简单的战略 | ![GitHub stars](https://badgen.net/github/stars/leosmigel/analyzingalpha) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyTrendFollow](https://github.com/chrism2671/PyTrendFollow) | PyTrendFollow - 使用趋势跟踪的系统性期货交易 | ![GitHub stars](https://badgen.net/github/stars/chrism2671/PyTrendFollow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 分析 ### 指标 *预测未来价格走势的指标库。* | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [ta-lib](https://github.com/mrjbq7/ta-lib) | 对金融市场数据进行技术分析 | ![GitHub stars](https://badgen.net/github/stars/mrjbq7/ta-lib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [go-tart](https://github.com/iamjinlei/go-tart) | 用Go实现的[ta-lib]((https://github.com/mrjbq7/ta-lib),支持增量更新 | ![GitHub stars](https://badgen.net/github/stars/iamjinlei/go-tart) | ![made-with-go](https://img.shields.io/badge/Made%20with-go-1f425f.svg) | | [pandas-ta](https://github.com/twopirllc/pandas-ta) | 潘达斯技术分析(Pandas TA)是一个易于使用的库,它利用潘达斯软件包的130多个指标和实用功能以及60多个TA Lib蜡烛图。 | ![GitHub stars](https://badgen.net/github/stars/twopirllc/pandas-ta) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [finta](https://github.com/peerchemist/finta) | 在Pandas中实施的共同财务技术指标 | ![GitHub stars](https://badgen.net/github/stars/peerchemist/finta) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ta-rust](https://github.com/greyblake/ta-rs) | Rust语言的技术分析库 | ![GitHub stars](https://badgen.net/github/stars/greyblake/ta-rs) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ### 度量衡计算 *财务衡量标准。* | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [quantstats](https://github.com/ranaroussi/quantstats) | 用Python编写的面向量化投资人的投资组合分析方法 | ![GitHub stars](https://badgen.net/github/stars/ranaroussi/quantstats) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [ffn](https://github.com/pmorissette/ffn) | 一个用于Python的金融函数库 | ![GitHub stars](https://badgen.net/github/stars/pmorissette/ffn) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 优化 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [PyPortfolioOpt](https://github.com/robertmartin8/PyPortfolioOpt) | 在python中进行金融投资组合优化,包括经典的有效边界、Black-Litterman、分级风险平价等。 | ![GitHub stars](https://badgen.net/github/stars/robertmartin8/PyPortfolioOpt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib) | Python中的投资组合优化和定量战略资产配置 | ![GitHub stars](https://badgen.net/github/stars/dcajasn/Riskfolio-Lib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [empyrial](https://github.com/ssantoshp/Empyrial) | Empyrial是一个基于Python的开源量化投资库,专门为金融机构和零售投资者服务,于2021年3月正式发布。 | ![GitHub stars](https://badgen.net/github/stars/ssantoshp/Empyrial) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Deepdow](https://github.com/jankrepl/deepdow) | 连接组合优化和深度学习的Python包。它的目标是促进研究在一次前进过程中进行权重分配的网络。 | ![GitHub stars](https://badgen.net/github/stars/jankrepl/deepdow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [spectre](https://github.com/Heerozh/spectre) | Python中的投资组合优化和定量战略资产配置 | ![GitHub stars](https://badgen.net/github/stars/Heerozh/spectre) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 定价 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [tf-quant-finance](https://github.com/google/tf-quant-finance) | 谷歌为量化金融提供的高性能TensorFlow库 | ![GitHub stars](https://badgen.net/github/stars/google/tf-quant-finance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [FinancePy](https://github.com/domokane/FinancePy) | 一个Python金融库,专注于金融衍生品的定价和风险管理,包括固定收益、股票、外汇和信用衍生品。 | ![GitHub stars](https://badgen.net/github/stars/domokane/FinancePy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyQL](https://github.com/enthought/pyql) | 著名定价库QuantLib的Python封装器 | ![GitHub stars](https://badgen.net/github/stars/enthought/pyql) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 风险 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [pyfolio](https://github.com/quantopian/pyfolio) | Python中的投资组合和风险分析 | ![GitHub stars](https://badgen.net/github/stars/quantopian/pyfolio) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 经纪人API | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [ccxt](https://github.com/ccxt/ccxt) | 一个JavaScript / Python / PHP加密货币交易API,支持100多个比特币/altcoin交易所 | ![GitHub stars](https://badgen.net/github/stars/ccxt/ccxt) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Ib_insync](https://github.com/erdewit/ib_insync) | 用于交互式经纪人的Python同步/async框架。 | ![GitHub stars](https://badgen.net/github/stars/erdewit/ib_insync) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Coinnect](https://github.com/hugues31/coinnect) | Coinnect是一个Rust库,旨在通过REST API提供对主要加密货币交易所的完整访问。 | ![GitHub stars](https://badgen.net/github/stars/hugues31/coinnect) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | ## 数据来源 ### 一般 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [OpenBB Terminal](https://github.com/OpenBB-finance/OpenBBTerminal) | 为每个人、在任何地方进行投资研究。 | ![GitHub stars](https://badgen.net/github/stars/OpenBB-finance/OpenBBTerminal) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [TuShare](https://github.com/waditu/tushare) | TuShare是一个用于抓取中国股票历史数据的工具。 | ![GitHub stars](https://badgen.net/github/stars/waditu/tushare) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [yfinance](https://github.com/ranaroussi/yfinance) | yfinance提供了一个线程和Pythonic方式,从雅虎金融下载市场数据。 | ![GitHub stars](https://badgen.net/github/stars/ranaroussi/yfinance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [AkShare](https://github.com/akfamily/akshare) | AKShare是一个优雅而简单的Python金融数据接口库,它是为人类而建的!它是为人类服务的。 | ![GitHub stars](https://badgen.net/github/stars/akfamily/akshare) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [pandas-datareader](https://github.com/pydata/pandas-datareader) | 为pandas提供最新的远程数据访问,适用于多个版本的pandas。 | ![GitHub stars](https://badgen.net/github/stars/pydata/pandas-datareader) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Quandl](https://github.com/quandl/quandl-python) | 通过一个免费的API,从数百个出版商那里获得数以百万计的金融和经济数据集。 | ![GitHub stars](https://badgen.net/github/stars/quandl/quandl-python) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [findatapy](https://github.com/cuemacro/findatapy) | findatapy创建了一个易于使用的Python API,使用统一的高级接口从许多来源下载市场数据,包括Quandl、彭博、雅虎、谷歌等。 | ![GitHub stars](https://badgen.net/github/stars/cuemacro/findatapy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Investpy](https://github.com/alvarobartt/investpy) | 用Python从Investing.com提取金融数据 | ![GitHub stars](https://badgen.net/github/stars/alvarobartt/investpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Fundamental Analysis Data](https://github.com/JerBouma/FundamentalAnalysis) | 完整的基本面分析软件包能够收集20年的公司简介、财务报表、比率和20,000多家公司的股票数据。 | ![GitHub stars](https://badgen.net/github/stars/JerBouma/FundamentalAnalysis) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Wallstreet](https://github.com/mcdallas/wallstreet) | 华尔街。实时股票和期权工具 | ![GitHub stars](https://badgen.net/github/stars/mcdallas/wallstreet) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ### 加密货币 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Cryptofeed](https://github.com/bmoscon/cryptofeed) | 使用Asyncio的加密货币交易所Websocket数据源处理程序 | ![GitHub stars](https://badgen.net/github/stars/bmoscon/cryptofeed) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Gekko-Datasets](https://github.com/xFFFFF/Gekko-Datasets) | Gekko交易机器人数据集转储。下载和使用SQLite格式的历史文件。 | ![GitHub stars](https://badgen.net/github/stars/xFFFFF/Gekko-Datasets) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [CryptoInscriber](https://github.com/Optixal/CryptoInscriber) | 一个实时的加密货币历史交易数据图谱。从任何加密货币交易所下载实时历史交易数据。 | ![GitHub stars](https://badgen.net/github/stars/Optixal/CryptoInscriber) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | [Crypto Lake](https://github.com/crypto-lake/lake-api) | 加密货币的高频订单簿和交易数据 | ![GitHub stars](https://badgen.net/github/stars/crypto-lake/lake-api) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 数据科学 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [TensorFlow](https://github.com/tensorflow/tensorflow) | Python中科学计算的基本算法 | ![GitHub stars](https://badgen.net/github/stars/tensorflow/tensorflow) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Pytorch](https://github.com/pytorch/pytorch) | Python中的张量和动态神经网络具有强大的GPU加速功能 | ![GitHub stars](https://badgen.net/github/stars/pytorch/pytorch) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Keras](https://github.com/keras-team/keras) | 最具用户友好性的Python中的人类深度学习 | ![GitHub stars](https://badgen.net/github/stars/keras-team/keras) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Scikit-learn](https://github.com/scikit-learn/scikit-learn) | Python中的机器学习 | ![GitHub stars](https://badgen.net/github/stars/scikit-learn/scikit-learn) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Pandas](https://github.com/pandas-dev/pandas) | 灵活而强大的Python数据分析/操作库,提供类似于R data.frame对象的标记数据结构、统计函数以及更多。 | ![GitHub stars](https://badgen.net/github/stars/pandas-dev/pandas) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Numpy](https://github.com/numpy/numpy) | 用Python进行科学计算的基本包 | ![GitHub stars](https://badgen.net/github/stars/numpy/numpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Scipy](https://github.com/scipy/scipy) | Python中科学计算的基本算法 | ![GitHub stars](https://badgen.net/github/stars/scipy/scipy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [PyMC](https://github.com/pymc-devs/pymc) | Python中的概率编程。用Aesara进行贝叶斯建模和概率机器学习 | ![GitHub stars](https://badgen.net/github/stars/pymc-devs/pymc) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Cvxpy](https://github.com/cvxpy/cvxpy) | 一种用于凸优化问题的Python嵌入式建模语言。 | ![GitHub stars](https://badgen.net/github/stars/cvxpy/cvxpy) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 数据库 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Marketstore](https://github.com/alpacahq/marketstore) | 金融时序数据的DataFrame服务器 | ![GitHub stars](https://badgen.net/github/stars/alpacahq/marketstore) | ![made-with-go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg) | | [Tectonicdb](https://github.com/0b01/tectonicdb) | Tectonicdb是一个快速、高度压缩的独立数据库和流媒体协议,用于订单簿上的点子。 | ![GitHub stars](https://badgen.net/github/stars/0b01/tectonicdb) | ![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg) | | [ArcticDB (Man Group)](https://github.com/man-group/arcticdb) | 用于时间序列和tick数据的高性能数据存储 | ![GitHub stars](https://badgen.net/github/stars/man-group/ArcticDB) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 图形计算 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Ray](https://github.com/ray-project/ray) | 一个开源框架,为构建分布式应用提供了一个简单、通用的API。 | ![GitHub stars](https://badgen.net/github/stars/ray-project/ray) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Dask](https://github.com/dask/dask) | 在Python中使用类似Pandas的API进行任务调度的并行计算 | ![GitHub stars](https://badgen.net/github/stars/dask/dask) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Incremental (JaneStreet)](https://github.com/janestreet/incremental) | Incremental是一个库,它为你提供了一种建立复杂计算的方法,可以根据输入的变化进行有效的更新,其灵感来自Umut Acar等人关于自我调整计算的工作。Incremental在许多应用中都很有用 | ![GitHub stars](https://badgen.net/github/stars/janestreet/incremental) | ![made-with-ocaml](https://img.shields.io/badge/Made%20with-Ocaml-1f425f.svg) | | [Man MDF](https://github.com/man-group/mdf) | 用于Python的数据流编程工具包 | ![GitHub stars](https://badgen.net/github/stars/man-group/mdf) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [GraphKit](https://github.com/yahoo/graphkit) | 一个轻量级的Python模块,用于创建和运行计算的有序图。 | ![GitHub stars](https://badgen.net/github/stars/yahoo/graphkit) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Tributary](https://github.com/timkpaine/tributary) | 在Python中流化反应式和数据流图 | ![GitHub stars](https://badgen.net/github/stars/timkpaine/tributary) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 机器学习 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [QLib (Microsoft)](https://github.com/microsoft/qlib) | Qlib是一个以人工智能为导向的量化投资平台,旨在实现人工智能技术在量化投资中的潜力,授权研究,并创造价值。通过Qlib,你可以轻松尝试你的想法,创造更好的量化投资策略。越来越多的SOTA量化研究作品/论文在Qlib中发布。 | ![GitHub stars](https://badgen.net/github/stars/microsoft/qlib) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [FinRL](https://github.com/AI4Finance-Foundation/FinRL) | FinRL是第一个开源框架,展示了在量化金融中应用深度强化学习的巨大潜力。 | ![GitHub stars](https://badgen.net/github/stars/AI4Finance-Foundation/FinRL) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [MlFinLab (Hudson & Thames)](https://github.com/hudson-and-thames/mlfinlab) | MlFinLab通过提供可重复的、可解释的和易于使用的工具,帮助那些希望利用机器学习的力量的投资组合经理和交易者。 | ![GitHub stars](https://badgen.net/github/stars/hudson-and-thames/mlfinlab) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [TradingGym](https://github.com/Yvictor/TradingGym) | 交易和回测环境,用于训练强化学习代理或简单的规则基础算法。 | ![GitHub stars](https://badgen.net/github/stars/Yvictor/TradingGym) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [Stock Trading Bot using Deep Q-Learning](https://github.com/pskrunner14/trading-bot) | 使用深度Q-学习的股票交易机器人 | ![GitHub stars](https://badgen.net/github/stars/pskrunner14/trading-bot) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 时间序列分析 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [Facebook Prophet](https://github.com/facebook/prophet) | 对具有线性或非线性增长的多季节性的时间序列数据产生高质量的预测的工具。 | ![GitHub stars](https://badgen.net/github/stars/facebook/prophet) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [statsmodels](https://github.com/statsmodels/statsmodels) | Python模块,允许用户探索数据,估计统计模型,并进行统计测试。 | ![GitHub stars](https://badgen.net/github/stars/statsmodels/statsmodels) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [tsfresh](https://github.com/blue-yonder/tsfresh) | 从时间序列中自动提取相关特征。 | ![GitHub stars](https://badgen.net/github/stars/blue-yonder/tsfresh) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [pmdarima](https://github.com/alkaline-ml/pmdarima) | 一个统计库,旨在填补Python时间序列分析能力的空白,包括相当于R的auto.arima函数。 | ![GitHub stars](https://badgen.net/github/stars/alkaline-ml/pmdarima) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | ## 视觉化 | 存储库 | 描述 | 明星 | 使用方法 | |------------|-------------|-------|-----------| | [D-Tale (Man Group)](https://github.com/man-group/dtale) | D-Tale是Flask后端和React前端的结合,为你带来查看和分析Pandas数据结构的简单方法。 | ![GitHub stars](https://badgen.net/github/stars/man-group/dtale) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [mplfinance](https://github.com/matplotlib/mplfinance) | 使用Matplotlib实现金融市场数据可视化 | ![GitHub stars](https://badgen.net/github/stars/matplotlib/mplfinance) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | | [btplotting](https://github.com/happydasch/btplotting) | btplotting为回测、优化结果和backtrader的实时数据提供绘图。 | ![GitHub stars](https://badgen.net/github/stars/happydasch/btplotting) | ![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg) | # 战略 *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) | ![](https://badgen.net/badge/reviews/14%20161/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/12%20278/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/6%20969/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/2%20537/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/1%20229/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [Introduction To Algo Trading: How Retail Traders Can Successfully Compete With Professional Traders - Kevin J Davey](https://amzn.to/39Tf7JC) | ![](https://badgen.net/badge/reviews/131/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [Algorithmic Trading and DMA: An introduction to direct access trading strategies - Barry Johnson](https://amzn.to/3xYb0UN) | ![](https://badgen.net/badge/reviews/69/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | ## 传记 | 标题 | 评论 | 评价 | |----------|---------|--------| | [My Life as a Quant: Reflections on Physics and Finance - Emanuel Derman](https://amzn.to/3A8KudR) | ![](https://badgen.net/badge/reviews/192/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [How I Became a Quant: Insights from 25 of Wall Street’s Elite: - Barry Schachter](https://amzn.to/3Alf8kz) | ![](https://badgen.net/badge/reviews/27/blue) | ![](https://badgen.net/badge/rating/3.7/blue) | ## 编码 | 标题 | 评论 | 评价 | |----------|---------|--------| | [Python for Finance: Mastering Data-Driven Finance - Yves Hilpisch](https://amzn.to/3NhkTlP) | ![](https://badgen.net/badge/reviews/249/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [Trading Evolved: Anyone can Build Killer Trading Strategies in Python - Andreas F. Clenow](https://amzn.to/3A0jcGB) | ![](https://badgen.net/badge/reviews/173/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Python for Algorithmic Trading: From Idea to Cloud Deployment - Yves Hilpisch](https://amzn.to/3bpkd0C) | ![](https://badgen.net/badge/reviews/90/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [Algorithmic Trading with Python: Quantitative Methods and Strategy Development - Chris Conlan](https://amzn.to/3u3cxYo) | ![](https://badgen.net/badge/reviews/48/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [Learn Algorithmic Trading: Build and deploy algorithmic trading systems and strategies using Python and advanced data analysis - Sebastien Donadio](https://amzn.to/3NqNghA) | ![](https://badgen.net/badge/reviews/46/blue) | ![](https://badgen.net/badge/rating/4.1/blue) | ## 隐蔽性 | 标题 | 评论 | 评价 | |----------|---------|--------| | [The Bitcoin Standard: The Decentralized Alternative to Central Banking - Saifedean Ammous](https://amzn.to/3QMJgec) | ![](https://badgen.net/badge/reviews/5%20136/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Bitcoin Billionaires: A True Story of Genius, Betrayal, and Redemption - Ben Mezrich](https://amzn.to/39SkdWt) | ![](https://badgen.net/badge/reviews/1%20787/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [Mastering Bitcoin: Programming the Open Blockchain - Andreas M. Antonopoulos](https://amzn.to/3NniZ3p) | ![](https://badgen.net/badge/reviews/955/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Why Buy Bitcoin: Investing Today in the Money of Tomorrow - Andy Edstrom](https://amzn.to/3OMcKqZ) | ![](https://badgen.net/badge/reviews/192/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | ## 一般 | 标题 | 评论 | 评价 | |----------|---------|--------| | [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) | ![](https://badgen.net/badge/reviews/38%20087/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [How I Invest My Money: Finance experts reveal how they save, spend, and invest - Joshua Brown, Brian Portnoy](https://amzn.to/3A4rsoU) | ![](https://badgen.net/badge/reviews/892/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Naked Forex: High-Probability Techniques for Trading Without Indicators - Alex Nekritin](https://amzn.to/3NkrAUj) | ![](https://badgen.net/badge/reviews/720/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/441/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Option Volatility and Pricing: Advanced Trading Strategies and Techniques, 2nd Edition - Sheldon Natenberg](https://amzn.to/3btOxXL) | ![](https://badgen.net/badge/reviews/388/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/305/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [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) | ![](https://badgen.net/badge/reviews/242/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/163/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/123/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/105/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Algorithmic Trading: Winning Strategies and Their Rationale - Ernest P. Chan](https://amzn.to/3xWi8kd) | ![](https://badgen.net/badge/reviews/100/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/98/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/67/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/61/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/42/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [Machine Trading: Deploying Computer Algorithms to Conquer the Markets - Ernest P. Chan](https://amzn.to/3OIBe4o) | ![](https://badgen.net/badge/reviews/53/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/51/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk - Richard Grinold, Ronald Kahn](https://amzn.to/3xMKaic) | ![](https://badgen.net/badge/reviews/46/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/37/blue) | ![](https://badgen.net/badge/rating/3.8/blue) | | [Advances in Active Portfolio Management: New Developments in Quantitative Investing - Richard Grinold, Ronald Kahn](https://amzn.to/3xUTK2z) | ![](https://badgen.net/badge/reviews/19/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | | [Professional Automated Trading: Theory and Practice - Eugene A. Durenard](https://amzn.to/3yhfOpw) | ![](https://badgen.net/badge/reviews/15/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [Algorithmic Trading and Quantitative Strategies (Chapman and Hall/CRC Financial Mathematics Series) - Raja Velu, Maxence Hardy, Daniel Nehren](https://amzn.to/3xUTQXZ) | ![](https://badgen.net/badge/reviews/11/blue) | ![](https://badgen.net/badge/rating/4.2/blue) | | [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) | ![](https://badgen.net/badge/reviews/2/blue) | ![](https://badgen.net/badge/rating/3/blue) | ## 高频交易 | 标题 | 评论 | 评价 | |----------|---------|--------| | [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) | ![](https://badgen.net/badge/reviews/76/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/52/blue) | ![](https://badgen.net/badge/rating/4.1/blue) | | [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) | ![](https://badgen.net/badge/reviews/38/blue) | ![](https://badgen.net/badge/rating/4/blue) | | [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) | ![](https://badgen.net/badge/reviews/11/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [Market Microstructure in Practice - Charles-Albert Lehalle, Sophie Laruelle](https://www.amazon.fr/Market-Microstructure-Practice-Sophie-Laruelle/dp/9813231122) | ![](https://badgen.net/badge/reviews/8/blue) | ![](https://badgen.net/badge/rating/3.9/blue) | | [The Financial Mathematics of Market Liquidity - Olivier Gueant](https://www.amazon.com/Financial-Mathematics-Market-Liquidity-Execution/dp/1498725473) | ![](https://badgen.net/badge/reviews/6/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/1/blue) | ![](https://badgen.net/badge/rating/3/blue) | ## 机器学习 | 标题 | 评论 | 评价 | |----------|---------|--------| | [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) | ![](https://badgen.net/badge/reviews/532/blue) | ![](https://badgen.net/badge/rating/4.5/blue) | | [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) | ![](https://badgen.net/badge/reviews/446/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/229/blue) | ![](https://badgen.net/badge/rating/4.4/blue) | | [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) | ![](https://badgen.net/badge/reviews/96/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/76/blue) | ![](https://badgen.net/badge/rating/4.6/blue) | | [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) | ![](https://badgen.net/badge/reviews/38/blue) | ![](https://badgen.net/badge/rating/4.3/blue) | | [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) | ![](https://badgen.net/badge/reviews/15/blue) | ![](https://badgen.net/badge/rating/4.7/blue) | # 视频 | 标题 | 喜欢 | |--------------------------------------------------------------------|-------| | [Krish Naik - Machine learning tutorials and their Application in Stock Prediction](https://www.youtube.com/watch?v=H6du_pfuznE) | ![](https://badgen.net/badge/likes/6.3k/blue) | | [QuantInsti Youtube - webinars about Machine Learning for trading](https://www.youtube.com/user/quantinsti/search?query=machine+learning) | ![](https://badgen.net/badge/likes/6.1k/blue) | | [Siraj Raval - Videos about stock market prediction using Deep Learning](https://www.youtube.com/channel/UCWN3xxRkmTPmbKwht9FuE5A/search?query=trading) | ![](https://badgen.net/badge/likes/1.7k/blue) | | [Quantopian - Webinars about Machine Learning for trading](https://www.youtube.com/channel/UC606MUq45P3zFLa4VGKbxsg/search?query=machine+learning) | ![](https://badgen.net/badge/likes/1.5k/blue) | | [Sentdex - Machine Learning for Forex and Stock analysis and algorithmic trading](https://www.youtube.com/watch?v=v_L9jR8P-54&list=PLQVvvaa0QuDe6ZBtkCNWNUbdaBo2vA4RO) | ![](https://badgen.net/badge/likes/1.5k/blue) | | [QuantNews - Machine Learning for Algorithmic Trading 3 part series](https://www.youtube.com/playlist?list=PLHJACfjILJ-91qkw5YC83S6COKGscctzz) | ![](https://badgen.net/badge/likes/806/blue) | | [Sentdex - Python programming for Finance (a few videos including Machine Learning)](https://www.youtube.com/watch?v=Z-5wNWgRJpk&index=9&list=PLQVvvaa0QuDcOdF96TBtRtuQksErCEBYZ) | ![](https://badgen.net/badge/likes/735/blue) | | [Chat with Traders EP042 - Machine learning for algorithmic trading with Bert Mouler](https://www.youtube.com/watch?v=i8FNO8r7PaE) | ![](https://badgen.net/badge/likes/687/blue) | | [Tucker Balch - Applying Deep Reinforcement Learning to Trading](https://www.youtube.com/watch?v=Pka0DC_P17k) | ![](https://badgen.net/badge/likes/487/blue) | | [Ernie Chan - Machine Learning for Quantitative Trading Webinar](https://www.youtube.com/watch?v=72aEDjwGMr8&t=1023s) | ![](https://badgen.net/badge/likes/436/blue) | | [Chat with Traders EP147 - Detective work leading to viable trading strategies with Tom Starke](https://www.youtube.com/watch?v=JjXw9Mda7eY) | ![](https://badgen.net/badge/likes/407/blue) | | [Chat with Traders EP142 - Algo trader using automation to bypass human flaws with Bert Mouler](https://www.youtube.com/watch?v=ofL66mh6Tw0) | ![](https://badgen.net/badge/likes/316/blue) | | [Master Thesis presentation, Uni of Essex - Analyzing the Limit Order Book, A Deep Learning Approach](https://www.youtube.com/watch?v=qxSh2VFmRGw) | ![](https://badgen.net/badge/likes/264/blue) | | [Howard Bandy - Machine Learning Trading System Development Webinar](https://www.youtube.com/watch?v=v729evhMpYk&t=1s) | ![](https://badgen.net/badge/likes/253/blue) | | [Chat With Traders EP131 - Trading strategies, powered by machine learning with Morgan Slade](https://www.youtube.com/watch?v=EbWbeYu8zwg) | ![](https://badgen.net/badge/likes/229/blue) | | [Chat with Traders Quantopian 5 - Good Uses of Machine Learning in Finance with Max Margenot](https://www.youtube.com/watch?v=Zj5sXWv9SDM) | ![](https://badgen.net/badge/likes/198/blue) | | [Hitoshi Harada, CTO at Alpaca - Deep Learning in Finance Talk](https://www.youtube.com/watch?v=FoQKCeDuPiY) | ![](https://badgen.net/badge/likes/147/blue) | | [Better System Trader EP028 - David Aronson shares research into indicators that identify Bull and Bear markets.](https://www.youtube.com/watch?v=Q4rV0Y9NokI) | ![](https://badgen.net/badge/likes/97/blue) | | [Prediction Machines - Deep Learning with Python in Finance Talk](https://www.youtube.com/watch?v=xvm-M-R2fZY) | ![](https://badgen.net/badge/likes/87/blue) | | [Better System Trader EP064 - Cryptocurrencies and Machine Learning with Bert Mouler](https://www.youtube.com/watch?v=YgRTd4nLJoU) | ![](https://badgen.net/badge/likes/35/blue) | | [Better System Trader EP023 - Portfolio manager Michael Himmel talks AI and machine learning in trading](https://www.youtube.com/watch?v=9tZjeyhfG0g) | ![](https://badgen.net/badge/likes/29/blue) | | [Better System Trader EP082 - Machine Learning With Kris Longmore](https://www.youtube.com/watch?v=0syNgsd635M) | ![](https://badgen.net/badge/likes/18/blue) | # 博客 | 标题 | |--------------------------------------------------------------------| | [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)