├── .gitattributes ├── .gitignore ├── AlphaNotes.md ├── README.md ├── __init__.py ├── api ├── __init__.py ├── iex.py ├── robinhood.py └── yahoo.py ├── backtest ├── __init__.py ├── algos │ ├── BaseStrategy.py │ ├── BuyAndHold.py │ ├── CrossOver.py │ ├── EqualVolatility.py │ ├── LeveragedEtfPair.py │ ├── MeanReversion.py │ ├── NCAV.py │ ├── PairSwitching.py │ ├── WeightedHold.py │ └── __init__.py ├── run.py └── util │ ├── __init__.py │ ├── analyzers.py │ ├── commission.py │ ├── observers.py │ └── universe.py ├── data ├── __init__.py ├── info │ ├── __init__.py │ ├── columns.csv │ ├── exclude.csv │ ├── industries.csv │ ├── info.p │ ├── info.py │ └── test_info.py ├── quotes.csv ├── russell │ ├── __init__.py │ ├── russell.py │ ├── russell_1000.csv │ ├── russell_2000.csv │ ├── small_cap.csv │ └── small_cap_filtered.csv └── spy │ ├── __init__.py │ ├── sp100.csv │ ├── sp500-tech.csv │ ├── spy.py │ ├── tickers.csv │ └── tickers.py ├── jupyter ├── alpha.ipynb ├── spy_investigation.ipynb ├── spy_test.ipynb └── spy_volatility.ipynb ├── test ├── __init__.py └── test.py └── tools ├── __init__.py ├── download_info.py ├── download_prices.py ├── fin_calc.py ├── hurst.py ├── log ├── __init__.py └── log.py ├── markowitz.py ├── plot.py ├── stats.py ├── std.py ├── update_prices.py ├── validate_data.py └── vix_term.py /.gitattributes: -------------------------------------------------------------------------------- 1 | jupyter/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.local.json 2 | __pycache__ 3 | data/price/ 4 | data/info/*.p 5 | data/info/us*.csv 6 | data/info/ncav.csv 7 | data/info/*.xlsx 8 | tools/log/log 9 | .ipynb_checkpoints 10 | .vscode 11 | .pylintrc 12 | fast_arrow_auth.json -------------------------------------------------------------------------------- /AlphaNotes.md: -------------------------------------------------------------------------------- 1 | # Alpha Notes 2 | 3 | ### [Alpha Heuristic for Leverage / Scaling (Quantopian)](https://www.quantopian.com/posts/enhancing-short-term-mean-reversion-strategies-1) 4 | 5 | #### Guy Fleury Mar 17, 2017 6 | 7 | > @Anthony, you start with the usual: A(t) = A(0)∙(1 + r)^t where r is viewed as the average market return which you can have just by buying SPY, DIA, or QQQ, and holding on for the duration. 8 | > 9 | > You want to design a trading strategy that is scalable: A(t) = k∙A(0)∙(1 + r)^t. Note that buying k SPY and holding is scalable 100%. It is also why I design my trading strategies to be scalable. Otherwise, how could they ever reach the level of managing a large portfolio if they were not scalable. 10 | > 11 | > You want to trade, then you have to outperform the average. Bring some alpha to the mix. This will result in: A(t) = k∙A(0)∙(1 + r + α)^t where α represent the premium or contribution your trading skills bring to the problem. 12 | > 13 | > Evidently, your skills needs to have a positive impact (α > 0). Nonetheless, you still want the strategy to be scalable. As previously observed, this particular trading strategy is not that scalable down. If you go small, it will simply destroy the account. It is only partially scalable up. But, still, it can generate some alpha. And, positive alpha translate to outperforming averages, generating more money which was the whole purpose of the game. 14 | > 15 | > IB charges less than 3% for leverage, but leverage is only for the sum exceeding your equity. It is only when leverage exceeds one; L > 1.00 that you will be charged interests on the excess. So you can get an approximation using: A(t) = k∙A(0)∙(1 + r + α – 0.03)^t. Using leverage is the same as increasing the bet size and therefore it will have an impact on the overall score, giving: A(t) = (1 + L)∙k∙A(0)∙(1 + r + α - 0.03)^t. 16 | > 17 | > The strategy was not using 100% leverage, but 60%. It was designed as a 130/30 hedge fund after all. This would have for impact to reduce the leveraging estimate to: 18 | > A(t) = (1 + L)∙k∙A(0)∙(1 + r + α – 0.03\*0.60)^t. 19 | > 20 | > With the modifications to Blue's program (two numbers) I got: r + α = 21.87%, L = 0.60, A(0) = \$1M, and t = 14.36 years. 21 | > 22 | > My simulation did put k = 5 since I wanted to see the extent of the strategy's scalability. On that front, the trading strategy did not respond that well. It is understandable, it does not make any compensation for return degradation over time. But, that is a flaw that can be corrected, and doing so will improve performance even more. 23 | 24 | #### Guy Fleury Mar 18, 2017 25 | 26 | > @Anthony, the formula used is very basic. It considers the end points, not the path to get there. Nonetheless, the end points give the same numbers, an as if. 27 | > 28 | > A(t) represents the ongoing net liquidating value of a portfolio (sum of its assets). You could also express the same thing using: 29 | > A(t) = A(0) + Ʃ ƒ H(t)∙dP which reads the same. A(t) is the sum of the initial capital to which is added the end results of all trades. 30 | > 31 | > A(0) is the initial portfolio value (usually the available initial trading capital). 32 | > 33 | > r is the compounded annual growth rate (CAGR). Here, r is expressed as the average market return. It can also serve as the most expected portfolio outcome for someone playing for the long term, say more than 10 years. 34 | > 35 | > t is for the time in years. In this case t was for 14.36 years. 36 | > 37 | > k is just a scaling factor. You put 2 times more in initial capital (2∙A(0)), you should expect 2 times more in total profits. This, if the strategy is scalable. 38 | > 39 | > The alpha α is used in the same way as Jensen presented it in the late 60's. It is for the skills brought to the game. A positive alpha meant that what you did in managing a portfolio was beneficial since it was adding to the CAGR. 40 | > 41 | > What Jensen observed in his seminal paper was that the average portfolio manager had a negative alpha. Meaning that what they did, on average, was detrimental to their overall long term performance since the majority of them ended up not beating the averages. Your trading script needs to generate a positive alpha (α > 0), otherwise your portfolio might fall in the above cited category. 42 | > 43 | > L is for the leverage. There are no leveraging fees for L < 1.00 since it is equivalent to be not fully invested. In Blue's program, if you wanted a leverage of 2.00, you could change one, or two numbers to do the job. As was said, leveraging has the same impact as increasing the bet size. You pay for the ability of doing so. 44 | > 45 | > What you want your program to do is have your α > lc, its leveraging charge (lc). I don't find leveraging a trading strategy interesting unless it has an α > 2∙lc, meaning the added alpha is at least twice the leveraging charges. Otherwise you might be trading for the benefit of the broker, not your own. And as you know, the result could be even worse if the trading strategy is not well behaved or well controlled. 46 | > 47 | > So, with the presented equation, one can estimate the outcome of different scenarios even before performing them knowing what will be the impact on total return. If you set k = 5, meaning 5 times more initial capital, you should expect 5 times more profits. If you don't get it, then your program is showing weaknesses in the scalability department. This should lead to finding ways to compensate for the performance degradation. 48 | 49 | #### Guy Fleury Mar 25, 2017 50 | 51 | > @Grant, I think we simply have a different viewpoint. 52 | > 53 | > For me, an automated trading strategy needs to be upward scalable. If not, its value is considerably reduced. 54 | > 55 | > If scaling up for whatever trading methods used gets to produce over the long haul less than market average, then a strategy becomes literally worthless since the alternative of buying an index fund would have generated more for a lot less work. 56 | > 57 | > No one will put 1,000 $10k strategies to work ($10M). Especially if you have 10 times or 100 times more capital to invest. You need scalable strategies that will not break down under AUM pressure. You also need machines that can handle the data and execute the trades. You need not only a doable, but also a feasible scenario that can handle A(t) for the entire trading/investing interval. This trading interval can not be just for a few years, otherwise you are just taking a timing bet where the period you activate your account becomes the most important. 58 | > 59 | > Also, as you must have seen numerous times here, having a $10k strategy is by no means a guaranteed viable scenario at $100k, $1M, or $10M for that matter. The problems encountered at each level are quite different, and this just based on the initial capital put to work. 60 | > 61 | > I've given this formula elsewhere: `A(t) = (1+L)∙k∙A(0)∙(1 + r + α - lc% - fc%)^t`, and what I see as dreamland is expecting some positive alpha for a 50/50 long short portfolio with a near zero beta. The examples that have been presented on Q failed that acid test: `α > 0`. And it usually gets worse when you scale them up: k∙A(0), increase the trading interval, account for frictional costs fc%, and leveraging fees lc%. 62 | > 63 | > There are 3 ways to improve the Sharpe ratio. One is to really increase your portfolio average return (generate some alpha). Another is to reduce volatility. And the other is to do both at the same time. However, anytime you want to reduce volatility, it entails on average, lower risks but also lower returns. 64 | > 65 | > So, for someone wishing some Q allocation or even just a viable trading strategy, they should understand the implications of the formula: `A(t) = (1+v∙L)∙k∙A(0)∙(1 + r + α – v∙lc% – v∙fc%)^t` where r, the market average, will be reduced by a negative alpha α. And where leveraging fees and frictional costs will be v times the leveraging boost. Evidently, if v = 1 (no boost), k = 1 (no scaling), lc% = 0 (no leverage), you are left with: `A(t) = A(0)∙(1 + r + α – fc%)^t` since commissions will still have to be paid. Then, you better have some trading skills to outperform the average (α > fc%). 66 | > 67 | > I don't see that as a very pretty picture. The only remedy is to have a positive alpha of sufficient size to compensate for all the drawbacks. Even the one rarely discussed here which is long term return degradation. 68 | > 69 | > I am from the outside, and I would never put anybody's strategy to work without testing the above formula on a sufficiently large portfolio over an extended period of time which is what I do using Q, explore their limits, see how far they can go, where will they break down? 70 | > 71 | > It is like if you can not show me that your trading strategy is scalable upwards, what is its value? If you can show that it can generate some alpha, again what could be its value going forward? And if it is not leverageable, how can we push it further to do even more? 72 | > 73 | > For me, it does not matter which trading methods you use, my interest is only on A(t). And the score for A(t) is kept on the bottom line of the trading account. 74 | > 75 | > However you want to look at it. If you want more, you will have to do more than the other guy. 76 | > 77 | > It's like if we have the exact same car and take a long race to the finish line. Our respective driving skills will matter. But regardless, I could get a definite positive edge just by changing the tires. Then driving skills might not matter much 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aglo Trader 2 | 3 | ## Intro 4 | 5 | This is my repo for backtesting algorithmic trading strategies. 6 | 7 | Implemented with Backtrader in Python. 8 | 9 | ## Run a backtest 10 | 11 | ``` 12 | python -m backtest.run BuyAndHold -t SPY -s 2010 13 | ``` 14 | 15 | ### Syntax: 16 | 17 | ``` 18 | backtest.run -t ... 19 | ``` 20 | 21 | ### Arguments: 22 | 23 | | Arg | Flag | Possible Values | Description | 24 | | ------------ | -------------- | --------------------------- | ------------------------------------------------------------------------------------------- | 25 | | strategy | | BuyAndHold, CrossOver, etc. | Choose from the list of algorithms in the ./backtest/algos/. The arg value is the filename. | 26 | | tickers | -t, --tickers | SPY, AAPL, etc. | A list of tickers to use. | 27 | | universe | -u, --universe | sp500, faang, etc. | Find the list of uniuverses in ./backtest/utils/universe.py | 28 | | start | -s, --start | 2010, 2010-01-01 | Starting date of the backtest | 29 | | end | -e, --end | 2022, 2021-12-31 | End date for backtest | 30 | | cash | --cash | 100000 | Starting cash balance | 31 | | verbose | -v, --verbose | | Show verbose details of all trades | 32 | | plot | -p, --plot | | Show the full plot | 33 | | plot returns | --plotreturns | | Only plot the returns | 34 | | kwargs | -k, --kwargs | | Additional arguments to pass through to the strategy | 35 | 36 | ## Tools 37 | 38 | ``` 39 | python -m tools.download_prices -t SPY 40 | ``` 41 | 42 | | Tool | Description | 43 | | --------------- | ---------------------------------------------------------------------------------------------------------------------- | 44 | | download_info | Download fundamental data | 45 | | download_prices | Download price history for specified tickers. If no tickers given, defaults to download all tickers in SP500 | 46 | | update_prices | Updates newest price data and appends to the end of the downloaded file (Use this once you've already downloaded data) | 47 | | plot | Plot price for specified tickers | 48 | | validate_data | Cleans up and validates price data | 49 | | stats | Get statistical data of ticker | 50 | | etc. | You can follow this format and try out the other tools as well. They can all be imported too. | 51 | 52 | ## Current Implemented Strategies 53 | 54 | - Buy and Hold (`BuyAndHold.py`) 55 | - Simple Moving Average Cross-Over (`CrossOver.py`) 56 | - Leveraged ETF Pairs (`LeveragedEtfPair.py`) 57 | - Pair Switching (`PairSwitching.py`) 58 | - Mean reversion (`MeanReversion.py`) 59 | 60 | ### Notes: 61 | 62 | #### Pair Switching 63 | 64 | This strategy has been successful for the ETF pairs MDY and TLT. 65 | 66 | Backtest results: 67 | 68 | ##### 2003 - 2013 69 | 70 | | Method | Value | SPY | 71 | | ------------- | ------- | ------- | 72 | | Total Returns | 525.71% | 89.86% | 73 | | Max Drawdown | 16.28% | 54.83% | 74 | | CAGR | 20.15% | 6.63% | 75 | | Sharpe | 1.03988 | 0.24775 | 76 | | Sortino | 1.52483 | 0.34871 | 77 | 78 | ##### 2013 - 2018 79 | 80 | | Method | Value | SPY | 81 | | ------------- | ------- | ------- | 82 | | Total Returns | 55.83% | 100.92% | 83 | | Max Drawdown | 9.76% | 12.93% | 84 | | CAGR | 9.29% | 14.99% | 85 | | Sharpe | 0.51831 | 0.95824 | 86 | | Sortino | 0.72603 | 1.35337 | 87 | 88 | ##### 2018 - YTD (09/04/2019) 89 | 90 | | Method | Value | SPY | 91 | | ------------- | ------- | ------- | 92 | | Total Returns | 14.64% | 12.29% | 93 | | Max Drawdown | 12.05% | 19.15% | 94 | | CAGR | 8.50% | 7.19% | 95 | | Sharpe | 0.43412 | 0.30127 | 96 | | Sortino | 0.58252 | 0.40374 | 97 | 98 | #### MeanReversion 99 | 100 | This strategy has been successful for the S&P 100 stocks. 101 | 102 | ##### Possible Enhancements: 103 | 104 | [Quantopian: Enhancing short term mean reversion strategies](https://www.quantopian.com/posts/enhancing-short-term-mean-reversion-strategies-1) 105 | 106 | - Filter out large 1-day news-realted moves 107 | - (Sort by 5d standard-deviation of returns) 108 | 109 | Backtest results: 110 | 111 | ##### 2013 - 2018 (60d lookback, 5d rebalance) 112 | 113 | | Method | Value | SPY | 114 | | ------------- | ------- | ------- | 115 | | Total Returns | 133.90% | 96.88% | 116 | | Max Drawdown | 18.10% | 13.04% | 117 | | CAGR | 17.54% | 14.52% | 118 | | Sharpe | 0.97543 | 0.93255 | 119 | | Sortino | 1.43594 | 1.32703 | 120 | 121 | ##### 2018 - YTD (12/16/2019) (60d lookback, 5d rebalance) 122 | 123 | | Method | Value | OEF | 124 | | ------------- | ------- | ------- | 125 | | Total Returns | 33.29% | 22.65% | 126 | | Max Drawdown | 20.20% | 19.41% | 127 | | CAGR | 13.88% | 11.03% | 128 | | Sharpe | 0.66737 | 0.53051 | 129 | | Sortino | 0.94469 | 0.71488 | 130 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from . import finance, data, api, tools, algos, test 2 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | from . import yahoo 2 | -------------------------------------------------------------------------------- /api/iex.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import requests 4 | from urllib.parse import urlparse 5 | 6 | IEX_TOKEN = '' 7 | API_ENDPOINT = '' 8 | 9 | config_path = os.path.join(os.path.dirname(__file__), '../config.local.json') 10 | with open(config_path) as config_file: 11 | config = json.load(config_file) 12 | IEX_TOKEN = config['iex-cloud-api-token-test'] 13 | # set to sandbox, use real endpoint once configured 14 | API_ENDPOINT = config['iex-api-endpoint-sandbox'] 15 | 16 | 17 | def dailyHistorical(ticker, range): 18 | print('Retrieving data for: {0}'.format(ticker)) 19 | url = API_ENDPOINT + '/stock/' + ticker + '/chart/' + range 20 | resp = requests.get(urlparse(url).geturl(), params={ 21 | 'token': IEX_TOKEN, 22 | 'chartCloseOnly': True 23 | }) 24 | if resp.status_code == 200: 25 | return resp.json() 26 | raise Exception('Response %d - ' % resp.status_code, resp.text) 27 | -------------------------------------------------------------------------------- /api/robinhood.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | import pandas as pd 5 | 6 | from fast_arrow import StockMarketdata, OptionChain, Option 7 | from fast_arrow import Client as ClientLegacy 8 | from fast_arrow_auth import Client, User 9 | 10 | 11 | class Robinhood: 12 | def __init__(self): 13 | username, password, device_token = self.setup() 14 | self.username = username 15 | self.password = password 16 | self.device_token = device_token 17 | 18 | def setup(self): 19 | config_path = os.path.join(os.path.dirname(__file__), '../config.local.json') 20 | username = '' 21 | password = '' 22 | device_token = None 23 | with open(config_path) as config_file: 24 | config = json.load(config_file) 25 | username = config['robinhood']['username'] 26 | password = config['robinhood']['password'] 27 | device_token = config['robinhood']['device-token'] 28 | return (username, password, device_token) 29 | 30 | def login(self): 31 | self.client = Client( 32 | username=self.username, 33 | password=self.password, 34 | device_token=self.device_token) 35 | self.result = self.client.authenticate() 36 | self.user = User.fetch(self.client) 37 | 38 | print("Authenticated successfully = {}".format(self.result)) 39 | print("Account Url = {}".format(self.client.account_url)) 40 | print("Account Id = {}".format(self.client.account_id)) 41 | 42 | print("Username = {}".format(self.user["username"])) 43 | print() 44 | 45 | # Set client to legacy version for further use 46 | auth_data = self.client.gen_credentials() 47 | self.client = ClientLegacy(auth_data) 48 | return self.client 49 | 50 | def get_quote(self, symbol): 51 | return StockMarketdata.quote_by_symbol(self.client, symbol) 52 | 53 | def get_option_chain(self, symbol, stock): 54 | stock_id = stock["instrument"].split("/")[-2] 55 | return OptionChain.fetch(self.client, stock_id, symbol) 56 | 57 | def get_next_3_exp_options(self, option_chain): 58 | option_chain_id = option_chain["id"] 59 | expiration_dates = option_chain['expiration_dates'] 60 | 61 | next_3_expiration_dates = expiration_dates[0:3] 62 | 63 | ops = Option.in_chain(self.client, option_chain_id, expiration_dates=next_3_expiration_dates) 64 | ops = Option.mergein_marketdata_list(client, ops) 65 | return ops 66 | 67 | 68 | if __name__ == '__main__': 69 | rh = Robinhood() 70 | client = rh.login() 71 | 72 | symbol = 'TLT' 73 | 74 | stock = rh.get_quote(symbol) 75 | 76 | print("TLT Options:") 77 | option_chain = rh.get_option_chain(symbol, stock=stock) 78 | options = rh.get_next_3_exp_options(option_chain) 79 | 80 | op_df = pd.DataFrame(options, columns=options[0].keys()) 81 | op_df = op_df[abs(pd.to_numeric(op_df['strike_price']) - pd.to_numeric(stock['last_trade_price'])) <= 2] 82 | display_columns = {'expiration_date': 'exp', 'strike_price': 'strike', 83 | 'adjusted_mark_price': 'mark', 'bid_price': 'bid', 'ask_price': 'ask', 84 | 'break_even_price': 'break_even', 'open_interest': 'open_interest', 85 | 'volume': 'volume', 'chance_of_profit_long': 'profit_%_long', 86 | 'chance_of_profit_short': 'profit_%_short', 'delta': 'delta', 87 | 'implied_volatility': 'implied_vol'} 88 | op_df = op_df.sort_values(['expiration_date', 'strike_price']).rename(columns=display_columns) 89 | op_df = op_df[display_columns.values()] 90 | print("Data:") 91 | print(op_df) 92 | -------------------------------------------------------------------------------- /api/yahoo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pandas as pd 3 | import yfinance as yf 4 | from datetime import date 5 | from tools.log import log 6 | 7 | 8 | def get_daily(ticker, start=None): 9 | if start == None: 10 | return yf.download(ticker, period='max', interval='1d') 11 | else: 12 | return yf.download(ticker, start=start, end=date.today(), interval='1d') 13 | 14 | 15 | def get_daily_async(tickers, start=None): 16 | if start == None: 17 | return yf.download(tickers, period='max', interval='1d', group_by="ticker") 18 | else: 19 | return yf.download(tickers, start=start, end=date.today(), interval='1d', group_by="ticker") 20 | 21 | def retry_ticker(ticker, retry=3): 22 | tries = 0 23 | while tries < retry: 24 | try: 25 | t = yf.Ticker(ticker) 26 | return t 27 | except e: 28 | print('ERROR:', ticker) 29 | print(e) 30 | log.log(type(e).__name__, e) 31 | time.sleep(1) 32 | tries += 1 33 | return None; 34 | 35 | 36 | def get_info(ticker): 37 | t = retry_ticker(ticker) 38 | if t is None: 39 | print('Download Issue:', ticker) 40 | return 41 | 42 | info_dict = t.info 43 | info = pd.DataFrame.from_dict(info_dict, orient='index').iloc[:, 0].rename('Info') 44 | 45 | try: 46 | balance_sheet = t.balance_sheet 47 | financials = t.financials 48 | cashflow = t.cashflow 49 | except IndexError as e: 50 | print('ERROR:', ticker) 51 | print(e) 52 | log.log(type(e).__name__, e) 53 | return info 54 | 55 | try: 56 | balance_sheet = balance_sheet.iloc[:, 0] 57 | financials = financials.iloc[:, 0] 58 | cashflow = cashflow.iloc[:, 0] 59 | except AttributeError as e: 60 | print('ERROR:', ticker) 61 | print(e) 62 | log.log(type(e).__name__, e) 63 | return info 64 | 65 | return_info = pd.concat([balance_sheet, financials, cashflow], axis=0) 66 | print(ticker) 67 | return return_info 68 | -------------------------------------------------------------------------------- /backtest/__init__.py: -------------------------------------------------------------------------------- 1 | from . import algos, util 2 | -------------------------------------------------------------------------------- /backtest/algos/BaseStrategy.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | 4 | class Strategy(bt.Strategy): 5 | """ 6 | Wrapper for `bt.Strategy` to log orders and perform other generic tasks. 7 | """ 8 | 9 | params = { 10 | 'riskfreerate': 0.035, 11 | 'cheat_on_open': False, 12 | 'verbose': False 13 | } 14 | 15 | def __init__(self, kwargs=None): 16 | bt.Strategy.__init__(self) 17 | self.order = None 18 | self.buyprice = None 19 | self.buycomm = None 20 | self.order_rejected = False 21 | self.verbose = self.params.verbose 22 | 23 | def log(self, txt, date=None): 24 | if self.verbose: 25 | date = date or self.data.datetime.date(0) 26 | print('{}, {}'.format(date.isoformat(), txt)) 27 | 28 | def notify_order(self, order): 29 | if order.status in [order.Submitted, order.Accepted]: 30 | # Buy/Sell order submitted/accepted to/by broker - Nothing to do 31 | return 32 | 33 | # Check if an order has been completed 34 | # Attention: broker could reject order if not enought cash 35 | if order.status in [order.Completed]: 36 | if order.isbuy(): 37 | self.log('BUY {}\t{:.2f}\t Cost: {:.2f}\tComm: {:.2f}'.format( 38 | order.data._name, 39 | order.executed.price, 40 | order.executed.value, 41 | order.executed.comm)) 42 | self.buyprice = order.executed.price 43 | self.buycomm = order.executed.comm 44 | if order.issell(): 45 | self.log('SELL {}\t{:.2f}\t Cost: {:.2f}\tComm: {:.2f}'.format( 46 | order.data._name, 47 | order.executed.price, 48 | order.executed.value, 49 | order.executed.comm)) 50 | 51 | elif order.status in [order.Canceled, order.Margin, order.Rejected]: 52 | status_reason = { 53 | order.Canceled: 'Canceled', 54 | order.Margin: 'Margin Called', 55 | order.Rejected: 'Rejected' 56 | } 57 | self.log('Order {}: {} {}'.format( 58 | status_reason[order.status], 59 | 'BUY' if order.isbuy() else 'SELL', 60 | order.data._name 61 | )) 62 | self.log('Cash: {:.2f}, Order: {:.2f}'.format(self.broker.get_cash(), 63 | (order.price or 0) * (order.size or 0))) 64 | self.order_rejected = True 65 | 66 | # Write down: no pending order 67 | self.order = None 68 | -------------------------------------------------------------------------------- /backtest/algos/BuyAndHold.py: -------------------------------------------------------------------------------- 1 | from . import BaseStrategy as base 2 | 3 | 4 | class BuyAndHold(base.Strategy): 5 | params = ( 6 | ('target_percent', 0.99), 7 | ) 8 | 9 | def __init__(self): 10 | base.Strategy.__init__(self) 11 | 12 | def buy_and_hold(self): 13 | for d in self.datas: 14 | split_target = self.params.target_percent / len(self.datas) 15 | self.order_target_percent(d, target=split_target) 16 | 17 | def next(self): 18 | if not self.position: 19 | self.buy_and_hold() 20 | 21 | elif self.order_rejected: 22 | self.buy_and_hold() 23 | self.order_rejected = False 24 | -------------------------------------------------------------------------------- /backtest/algos/CrossOver.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | from . import BaseStrategy as base 4 | 5 | 6 | class CrossOver(base.Strategy): 7 | params = { 8 | 'target_percent': 0.95 9 | } 10 | 11 | def __init__(self): 12 | base.Strategy.__init__(self) 13 | 14 | # Define Indicators 15 | self.sma5 = bt.indicators.MovingAverageSimple(period=5) 16 | self.sma30 = bt.indicators.MovingAverageSimple(period=30) 17 | self.buysell = bt.indicators.CrossOver(self.sma5, self.sma30, plot=True) 18 | 19 | def next(self): 20 | if self.order: 21 | # Skip if order is pending 22 | return 23 | 24 | if not self.position: 25 | if self.buysell > 0 or self.order_rejected: 26 | # Buy the up crossover 27 | self.log('BUY CREATE, {:.2f}'.format(self.data.close[0])) 28 | self.order = self.order_target_percent(target=self.params.target_percent) 29 | self.order_rejected = False 30 | else: 31 | if self.buysell < 0: 32 | # Sell the down crossover 33 | self.log('SELL CREATE, {:.2f}'.format(self.data.close[0])) 34 | self.order = self.close() 35 | -------------------------------------------------------------------------------- /backtest/algos/EqualVolatility.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | from . import BaseStrategy as base 5 | 6 | 7 | class EqualVolatility(base.Strategy): 8 | """ 9 | Given a set of equities, balance volatilities monthly. 10 | """ 11 | 12 | params = { 13 | 'rebalance_days': 21, 14 | 'target_percent': 0.95, 15 | 'lookback': 21 16 | } 17 | 18 | def __init__(self): 19 | base.Strategy.__init__(self) 20 | 21 | def rebalance(self): 22 | vols = [] 23 | for d in self.datas: 24 | returns = pd.Series(d.close.get(size=self.params.lookback)) 25 | returns = np.log(returns).diff().iloc[1:] 26 | vol = returns.std() 27 | vols.append(vol) 28 | vols = np.array(vols) 29 | 30 | order_sort = [] 31 | weights = [] 32 | for v, d in zip(vols, self.datas): 33 | weight = (1.0 / v) / sum(1.0 / vols) 34 | weights.append(weight) 35 | position = self.getposition(d) 36 | position_value = position.size * position.price 37 | order_target = self.params.target_percent * weight * self.broker.get_value() 38 | order_sort.append(order_target - position_value) 39 | 40 | for s, d, w in sorted(zip(order_sort, self.datas, weights), key=lambda pair: pair[0]): 41 | self.order_target_percent(d, self.params.target_percent * w) 42 | 43 | def next(self): 44 | if self.order: 45 | # Skip if order is pending 46 | return 47 | 48 | # Rebalance portfolio 49 | if len(self) % self.params.rebalance_days == 0: 50 | self.rebalance() 51 | 52 | # Retry order on next day if rejected 53 | elif self.order_rejected: 54 | self.rebalance() 55 | self.order_rejected = False 56 | -------------------------------------------------------------------------------- /backtest/algos/LeveragedEtfPair.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from . import BaseStrategy as base 4 | 5 | 6 | class LeveragedEtfPair(base.Strategy): 7 | """ 8 | Use as data two oposing weighted ETFs, ex: SSO & SDS 9 | 10 | :data: 2 datafeeds, the first a positive leveraged ETF, and the second negatively leveraged. 11 | """ 12 | 13 | params = { 14 | 'leverages': [1, 1], 15 | 'rebalance_days': 21, 16 | 'target_percent': -0.95 17 | } 18 | 19 | def __init__(self): 20 | base.Strategy.__init__(self) 21 | assert len(self.datas) == 2, "Exactly 2 datafeeds needed for this strategy!" 22 | 23 | self.leverages = np.abs(self.params.leverages) 24 | self.leverages = self.params.target_percent * self.leverages / sum(self.leverages) 25 | 26 | def rebalance(self): 27 | for i, d in enumerate(self.datas): 28 | self.order_target_percent(d, self.leverages[i]) 29 | 30 | def next(self): 31 | if self.order: 32 | # Skip if order is pending 33 | return 34 | 35 | # Rebalance portfolio 36 | if len(self) % self.params.rebalance_days == 0: 37 | self.rebalance() 38 | 39 | # Retry order on next day if rejected 40 | elif self.order_rejected: 41 | self.rebalance() 42 | self.order_rejected = False 43 | -------------------------------------------------------------------------------- /backtest/algos/MeanReversion.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from . import BaseStrategy as base 4 | 5 | 6 | class MeanReversion(base.Strategy): 7 | params = { 8 | 'target_percent': 0.95, 9 | 'riskfreerate': 0, 10 | 'quantile': 0.10, 11 | 'npositions': 25, 12 | 'quantile_std': 0.10, 13 | 'quantile_vol': 1.0, 14 | 'lookback': 6, 15 | 'offset': 1, 16 | 'order_frequency': 5, 17 | 'cheat_on_open': False 18 | } 19 | 20 | def __init__(self): 21 | base.Strategy.__init__(self) 22 | self.count = 1 23 | self.rank = pd.Series() 24 | self.filter = [] 25 | self.top = self.bottom = self.longs = self.shorts = self.closes = None 26 | self.order_valid = False 27 | self.values = [] 28 | 29 | def add_rank(self): 30 | 31 | # Ranking for Basic strategy 32 | # Go long all worst losers, and short best winners 33 | 34 | for i, d in enumerate(self.datas): 35 | if len(d) < self.params.lookback + self.params.offset: 36 | continue 37 | 38 | if i not in self.filter: 39 | continue 40 | 41 | # Improvement 2: 42 | # Delay lookback by 1 day 43 | 44 | prev = d.close.get(size=self.params.lookback, ago=self.params.offset)[0] 45 | pct_ret = (d.close[0] / prev) - 1 46 | self.rank.loc[i] = pct_ret 47 | 48 | if self.params.npositions > 0: 49 | self.top = list(self.rank.nlargest(self.params.npositions).index) 50 | self.bottom = list(self.rank.nsmallest(self.params.npositions).index) 51 | else: 52 | quantile_top = self.rank.quantile(1 - self.params.quantile) 53 | self.top = list(self.rank[self.rank >= quantile_top].index) 54 | 55 | quantile_bottom = self.rank.quantile(self.params.quantile) 56 | self.bottom = list(self.rank[self.rank <= quantile_bottom].index) 57 | 58 | def add_filter(self): 59 | 60 | # Filter function to filter out datas from current timestep 61 | 62 | sd = pd.Series() 63 | vol = pd.Series() 64 | for i, d in enumerate(self.datas): 65 | if len(d) < self.params.lookback + self.params.offset: 66 | continue 67 | 68 | # Improvement 1: 69 | # Add a filter to remove large 1-day returns 70 | # (Usually caused by news events) 71 | lookback = d.close.get(size=self.params.lookback, ago=self.params.offset) 72 | returns = np.diff(np.log(lookback))[1:] 73 | sd.loc[i] = np.std(returns) 74 | 75 | # Improvement 3: 76 | # Add filter to remove all high-volatility stocks 77 | lookback = d.close.get(size=min(126, len(d)), ago=self.params.offset) 78 | returns = np.diff(np.log(lookback))[1:] 79 | vol.loc[i] = np.std(lookback) 80 | 81 | quantile_std = sd.quantile(1 - self.params.quantile_std) 82 | quantile_vol = vol.quantile(1 - self.params.quantile_vol) 83 | sd = list(sd[sd <= quantile_std].index) 84 | vol = list(vol[vol <= quantile_vol].index) 85 | 86 | self.filter = list(set(sd) | set(vol)) 87 | # self.filter = sd 88 | 89 | def process(self): 90 | self.add_filter() 91 | self.add_rank() 92 | 93 | self.longs = [d for (i, d) in enumerate(self.datas) if i in self.bottom] 94 | self.shorts = [d for (i, d) in enumerate(self.datas) if i in self.top] 95 | self.closes = [d for d in self.datas if ( 96 | (d not in self.longs) and 97 | (d not in self.shorts) 98 | )] 99 | 100 | def send_orders(self): 101 | for d in self.longs: 102 | if len(d) < self.params.lookback + self.params.offset: 103 | continue 104 | 105 | split_target = 1 * self.params.target_percent / len(self.longs) 106 | self.order_target_percent(d, target=split_target) 107 | 108 | for d in self.shorts: 109 | if len(d) < self.params.lookback + self.params.offset: 110 | continue 111 | 112 | split_target = -1 * self.params.target_percent / len(self.shorts) 113 | self.order_target_percent(d, target=split_target) 114 | 115 | def set_kelly_weights(self): 116 | value = self.broker.get_value() 117 | self.values.append(value) 118 | 119 | kelly_lookback = 20 120 | 121 | if self.count > kelly_lookback: 122 | d = pd.Series(self.values[-kelly_lookback:]) 123 | r = d.pct_change() 124 | 125 | mu = np.mean(r) 126 | std = np.std(r) 127 | if std == 0.0: 128 | return 129 | 130 | f = (mu)/(std**2) 131 | if f == np.nan: 132 | return 133 | self.params.target_percent = max(0.2, min(2.0, f / 2.0)) 134 | print(self.params.target_percent) 135 | 136 | def close_positions(self): 137 | for d in self.closes: 138 | self.close(d) 139 | 140 | def next(self): 141 | self.order_valid = ( 142 | self.count > (self.params.lookback + self.params.offset) and 143 | self.count % self.params.order_frequency == 0 144 | ) 145 | if self.order_valid: 146 | self.process() 147 | self.close_positions() 148 | self.send_orders() 149 | 150 | elif self.order_rejected: 151 | self.send_orders() 152 | self.order_rejected = False 153 | 154 | self.set_kelly_weights() 155 | self.count += 1 156 | 157 | def prenext(self): 158 | self.order_valid = ( 159 | self.count > (self.params.lookback + self.params.offset) and 160 | self.count % self.params.order_frequency == 0 161 | ) 162 | if self.order_valid: 163 | self.process() 164 | self.close_positions() 165 | self.send_orders() 166 | 167 | elif self.order_rejected: 168 | self.send_orders() 169 | self.order_rejected = False 170 | 171 | self.set_kelly_weights() 172 | self.count += 1 173 | 174 | # def next_open(self): 175 | # if self.order_valid: 176 | # self.send_orders() 177 | 178 | # self.count += 1 179 | -------------------------------------------------------------------------------- /backtest/algos/NCAV.py: -------------------------------------------------------------------------------- 1 | # Import the backtrader platform 2 | import numpy as np 3 | import pandas as pd 4 | 5 | from . import BaseStrategy as base 6 | from data.info.info import all_balance 7 | 8 | 9 | class NCAV(base.Strategy): 10 | params = { 11 | 'rebalance_days': 252, 12 | 'target_percent': 0.95, 13 | 'ncav_limit': 1.5 14 | } 15 | 16 | def __init__(self): 17 | base.Strategy.__init__(self) 18 | self.info = all_balance([d._name for d in self.datas]) 19 | self.long = [] 20 | 21 | def filter(self): 22 | self.long = [] 23 | for d in self.datas: 24 | infos = self.info[self.info.Ticker == d._name] 25 | info = infos.loc[(pd.to_datetime(infos['Report Date']) > self.data.datetime.datetime()).idxmax()] 26 | 27 | ncav = (info['Total Current Assets'] - info['Total Liabilities']) / info['Shares (Basic)'] 28 | 29 | if ncav > self.params.ncav_limit: 30 | self.long.append(d) 31 | 32 | def rebalance(self): 33 | for d in self.datas: 34 | if d in self.long: 35 | # Go long equal weight if in long list 36 | split_target = self.params.target_percent / len(self.long) 37 | self.order_target_percent(d, target=split_target) 38 | else: 39 | # Exit order or order none if not long list 40 | self.order_target_percent(d, target=0.0) 41 | 42 | def next(self): 43 | if self.order: 44 | # Skip if order is pending 45 | return 46 | 47 | # Rebalance portfolio 48 | if len(self) % self.params.rebalance_days == 0: 49 | self.filter() 50 | self.rebalance() 51 | 52 | # Retry order on next day if rejected 53 | elif self.order_rejected: 54 | self.rebalance() 55 | self.order_rejected = False 56 | -------------------------------------------------------------------------------- /backtest/algos/PairSwitching.py: -------------------------------------------------------------------------------- 1 | # Import the backtrader platform 2 | import numpy as np 3 | 4 | from . import BaseStrategy as base 5 | 6 | 7 | class PairSwitching(base.Strategy): 8 | """ 9 | Use as data two oposing equities or ETFs 10 | 11 | :data: 2 datafeeds 12 | """ 13 | 14 | params = { 15 | 'rebalance_days': 21, 16 | 'target_percent': 0.95, 17 | 'lookback': 60 18 | } 19 | 20 | def __init__(self): 21 | base.Strategy.__init__(self) 22 | assert len(self.datas) == 2, "Exactly 2 datafeeds needed for this strategy!" 23 | 24 | def switch(self): 25 | prev0 = self.data0.close[-self.params.lookback] 26 | prev1 = self.data1.close[-self.params.lookback] 27 | 28 | # Calculate n-day returns 29 | return0 = np.log(self.data0.close[0]) - np.log(prev0) 30 | return1 = np.log(self.data1.close[0]) - np.log(prev1) 31 | 32 | if return0 > return1: 33 | self.order_target_percent(data=self.data1, target=0) 34 | self.order_target_percent(data=self.data0, target=self.params.target_percent) 35 | else: 36 | self.order_target_percent(data=self.data0, target=0) 37 | self.order_target_percent(data=self.data1, target=self.params.target_percent) 38 | 39 | def next(self): 40 | if self.order: 41 | # Skip if order is pending 42 | return 43 | 44 | if (len(self) - 1) % self.params.rebalance_days == 0: 45 | self.switch() 46 | 47 | # Retry order on next day if rejected 48 | elif self.order_rejected: 49 | self.switch() 50 | self.order_rejected = False 51 | -------------------------------------------------------------------------------- /backtest/algos/WeightedHold.py: -------------------------------------------------------------------------------- 1 | from . import BaseStrategy as base 2 | import numpy as np 3 | 4 | 5 | class WeightedHold(base.Strategy): 6 | params = ( 7 | ('kwargs', None), 8 | ('target_percent', 0.99), 9 | ) 10 | 11 | def __init__(self): 12 | base.Strategy.__init__(self) 13 | 14 | if self.params.kwargs: 15 | self.params.weights = [float(w) for w in self.params.kwargs] 16 | else: 17 | self.params.weights = [1 for d in self.datas] 18 | 19 | w_pos = [w for w in self.params.weights if w >= 0] 20 | w_neg = [w for w in self.params.weights if w < 0] 21 | self.weights = [(w / sum(w_pos)) if w in w_pos else (-w / sum(w_neg)) for w in self.params.weights] 22 | 23 | def buy_and_hold(self): 24 | for i, d in enumerate(self.datas): 25 | split_target = self.params.target_percent * self.weights[i] 26 | self.order_target_percent(d, target=split_target) 27 | 28 | def next(self): 29 | self.buy_and_hold() 30 | -------------------------------------------------------------------------------- /backtest/algos/__init__.py: -------------------------------------------------------------------------------- 1 | from . import BaseStrategy, BuyAndHold, CrossOver, MeanReversion, LeveragedEtfPair, WeightedHold, NCAV 2 | -------------------------------------------------------------------------------- /backtest/run.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os.path 3 | import argparse 4 | import importlib 5 | import dateutil.parser 6 | 7 | import pandas as pd 8 | import numpy as np 9 | 10 | # Import the backtrader platform 11 | import backtrader as bt 12 | from backtrader import TimeFrame 13 | from .util import commission, observers, analyzers 14 | from .util import universe as universe_util 15 | 16 | 17 | def clean_tickers(tickers, start, end): 18 | data_path = os.path.join(os.path.dirname(__file__), '../data/price/') 19 | out_tickers = [] 20 | 21 | for ticker in tickers: 22 | d = pd.read_csv(data_path + ticker + '.csv', index_col=0, parse_dates=True) 23 | if not (d.tail(1).index[0] < start or 24 | d.head(1).index[0] > end): 25 | out_tickers.append(ticker) 26 | else: 27 | print('Data out of date range:', ticker) 28 | 29 | return out_tickers 30 | 31 | 32 | def run_strategy(strategy, tickers=None, start='1900-01-01', end='2100-01-01', cash=100000.0, 33 | verbose=False, plot=False, plotreturns=False, universe=None, exclude=[], 34 | kwargs=None): 35 | start_date = dateutil.parser.isoparse(start) 36 | end_date = dateutil.parser.isoparse(end) 37 | 38 | tickers = tickers if (tickers or universe) else ['SPY'] 39 | if universe: 40 | u = universe_util.get(universe)() 41 | tickers = [a for a in u.assets if a not in exclude] 42 | 43 | tickers = clean_tickers(tickers, start_date, end_date) 44 | 45 | module_path = f'.algos.{strategy}' 46 | module = importlib.import_module(module_path, 'backtest') 47 | strategy = getattr(module, strategy) 48 | 49 | cerebro = bt.Cerebro( 50 | stdstats=not plotreturns, 51 | cheat_on_open=strategy.params.cheat_on_open 52 | ) 53 | 54 | # Add a strategy 55 | cerebro.addstrategy(strategy, verbose=verbose) 56 | 57 | # Set up data feed 58 | for ticker in tickers: 59 | datapath = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker}.csv') 60 | data = bt.feeds.YahooFinanceCSVData( 61 | dataname=datapath, 62 | fromdate=start_date, 63 | todate=end_date, 64 | reverse=False, 65 | adjclose=False, 66 | plot=not plotreturns) 67 | 68 | cerebro.adddata(data) 69 | 70 | # Set initial cash amount and commision 71 | cerebro.broker.setcash(cash) 72 | # ib_comm = commission.IBCommision() 73 | # cerebro.broker.addcommissioninfo(ib_comm) 74 | 75 | # Add obervers 76 | if plotreturns: 77 | cerebro.addobserver(observers.Value) 78 | 79 | # Add analyzers 80 | cerebro.addanalyzer(bt.analyzers.SharpeRatio, 81 | riskfreerate=strategy.params.riskfreerate, 82 | timeframe=TimeFrame.Days, 83 | annualize=True) 84 | cerebro.addanalyzer(analyzers.Sortino, 85 | riskfreerate=strategy.params.riskfreerate, 86 | timeframe=TimeFrame.Days, 87 | annualize=True) 88 | cerebro.addanalyzer(bt.analyzers.Returns) 89 | cerebro.addanalyzer(bt.analyzers.DrawDown) 90 | cerebro.addanalyzer(bt.analyzers.PositionsValue) 91 | cerebro.addanalyzer(bt.analyzers.GrossLeverage) 92 | 93 | # Run backtest 94 | results = cerebro.run(preload=False) 95 | 96 | # Print results 97 | start_value = cash 98 | end_value = cerebro.broker.getvalue() 99 | print('Starting Portfolio Value:\t{:.2f}'.format(cash)) 100 | print('Final Portfolio Value:\t\t{:.2f}'.format(end_value)) 101 | 102 | # Get analysis results 103 | drawdown = results[0].analyzers.drawdown.get_analysis()['max']['drawdown'] 104 | cagr = results[0].analyzers.returns.get_analysis()['rnorm100'] 105 | sharpe = results[0].analyzers.sharperatio.get_analysis()['sharperatio'] 106 | sortino = results[0].analyzers.sortino.get_analysis()['sortino'] 107 | positions = results[0].analyzers.positionsvalue.get_analysis() 108 | avg_positions = np.mean([sum(d != 0.0 for d in i) for i in positions.values()]) 109 | leverage = results[0].analyzers.grossleverage.get_analysis() 110 | avg_leverage = np.mean([abs(i) for i in leverage.values()]) 111 | 112 | sharpe = 'None' if sharpe is None else round(sharpe, 5) 113 | print('ROI:\t\t{:.2f}%'.format(100.0 * ((end_value / start_value) - 1.0))) 114 | analyzer_results = [] 115 | analyzer_results.append('Max Drawdown:\t{:.2f}'.format(drawdown)) 116 | analyzer_results.append('CAGR:\t\t{:.2f}'.format(cagr)) 117 | analyzer_results.append('Sharpe:\t\t{}'.format(sharpe)) 118 | analyzer_results.append('Sortino:\t{:.5f}'.format(sortino)) 119 | analyzer_results.append('Positions:\t{:.5f}'.format(avg_positions)) 120 | analyzer_results.append('Leverage:\t{:.5f}'.format(avg_leverage)) 121 | print('\n'.join(analyzer_results)) 122 | 123 | # Plot results 124 | if plot: 125 | cerebro.plot() 126 | 127 | 128 | if __name__ == '__main__': 129 | PARSER = argparse.ArgumentParser() 130 | PARSER.add_argument('strategy', nargs=1) 131 | PARSER.add_argument('-t', '--tickers', nargs='+') 132 | PARSER.add_argument('-u', '--universe', nargs=1) 133 | PARSER.add_argument('-x', '--exclude', nargs='+') 134 | PARSER.add_argument('-s', '--start', nargs=1) 135 | PARSER.add_argument('-e', '--end', nargs=1) 136 | PARSER.add_argument('--cash', nargs=1, type=int) 137 | PARSER.add_argument('-v', '--verbose', action='store_true') 138 | PARSER.add_argument('-p', '--plot', action='store_true') 139 | PARSER.add_argument('--plotreturns', action='store_true') 140 | PARSER.add_argument('-k', '--kwargs', nargs='+') 141 | ARGS = PARSER.parse_args() 142 | ARG_ITEMS = vars(ARGS) 143 | 144 | # Parse multiple tickers / kwargs 145 | TICKERS = ARG_ITEMS['tickers'] 146 | KWARGS = ARG_ITEMS['kwargs'] 147 | EXCLUDE = ARG_ITEMS['exclude'] 148 | del ARG_ITEMS['tickers'] 149 | del ARG_ITEMS['kwargs'] 150 | del ARG_ITEMS['exclude'] 151 | 152 | # Remove None values 153 | STRATEGY_ARGS = {k: (v[0] if isinstance(v, list) else v) for k, v in ARG_ITEMS.items() if v} 154 | STRATEGY_ARGS['tickers'] = TICKERS 155 | STRATEGY_ARGS['kwargs'] = KWARGS 156 | 157 | if EXCLUDE: 158 | STRATEGY_ARGS['exclude'] = [EXCLUDE] if len(EXCLUDE) == 1 else EXCLUDE 159 | 160 | run_strategy(**STRATEGY_ARGS) 161 | -------------------------------------------------------------------------------- /backtest/util/__init__.py: -------------------------------------------------------------------------------- 1 | from . import commission, observers, analyzers, universe 2 | -------------------------------------------------------------------------------- /backtest/util/analyzers.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | from backtrader import Analyzer, TimeFrame 5 | from backtrader.analyzers import TimeReturn 6 | 7 | 8 | class Sortino(Analyzer): 9 | params = ( 10 | ('timeframe', TimeFrame.Years), 11 | ('compression', 1), 12 | ('riskfreerate', 0.01), 13 | ('factor', None), 14 | ('convertrate', True), 15 | ('annualize', False), 16 | ) 17 | 18 | RATEFACTORS = { 19 | TimeFrame.Days: 252, 20 | TimeFrame.Weeks: 52, 21 | TimeFrame.Months: 12, 22 | TimeFrame.Years: 1, 23 | } 24 | 25 | def __init__(self): 26 | super(Sortino, self).__init__() 27 | self.ret = TimeReturn( 28 | timeframe=self.p.timeframe, 29 | compression=self.p.compression) 30 | self.ratio = 0.0 31 | 32 | def stop(self): 33 | returns = list(self.ret.get_analysis().values()) 34 | 35 | rate = self.p.riskfreerate 36 | 37 | factor = None 38 | if self.p.timeframe in self.RATEFACTORS: 39 | # Get the conversion factor from the default table 40 | factor = self.RATEFACTORS[self.p.timeframe] 41 | 42 | if factor is not None: 43 | # A factor was found 44 | if self.p.convertrate: 45 | # Standard: downgrade annual returns to timeframe factor 46 | rate = pow(1.0 + rate, 1.0 / factor) - 1.0 47 | else: 48 | # Else upgrade returns to yearly returns 49 | returns = [pow(1.0 + x, factor) - 1.0 for x in returns] 50 | 51 | if len(returns): 52 | # Sortino Ratio = (R - T) / TDD 53 | # R = Avg Returns 54 | # T = Target (risk-free rate) 55 | # TDD = Downside Risk 56 | ret_free_avg = np.mean(returns) - rate 57 | tdd = math.sqrt(np.mean([min(0, r - rate)**2 for r in returns])) 58 | 59 | try: 60 | ratio = ret_free_avg / tdd 61 | 62 | if factor is not None and \ 63 | self.p.convertrate and self.p.annualize: 64 | 65 | ratio = math.sqrt(factor) * ratio 66 | except (ValueError, TypeError, ZeroDivisionError): 67 | ratio = None 68 | 69 | else: 70 | # no returns 71 | ratio = None 72 | 73 | self.ratio = ratio 74 | 75 | def get_analysis(self): 76 | return dict(sortino=self.ratio) 77 | -------------------------------------------------------------------------------- /backtest/util/commission.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | 4 | class IBCommision(bt.CommInfoBase): 5 | 6 | """A :class:`IBCommision` charges the way interactive brokers does. 7 | """ 8 | 9 | params = ( 10 | #('stocklike', True), 11 | #('commtype', bt.CommInfoBase.COMM_PERC), 12 | #('percabs', True), 13 | 14 | # Float. The amount charged per share. Ex: 0.005 means $0.005 15 | ('per_share', 0.005), 16 | 17 | # Float. The minimum amount that will be charged. Ex: 1.0 means $1.00 18 | ('min_per_order', 1.0), 19 | 20 | # Float. The maximum that can be charged as a percent of the trade value. Ex: 0.005 means 0.5% 21 | ('max_per_order_abs_pct', 0.005), 22 | ) 23 | 24 | def _getcommission(self, size, price, pseudoexec): 25 | """ 26 | :param size: current position size. > 0 for long positions and < 0 for short positions (this parameter will not be 0) 27 | :param price: current position price 28 | :param pseudoexec: 29 | :return: the commission of an operation at a given price 30 | """ 31 | 32 | commission = abs(size) * self.p.per_share 33 | order_price = price * abs(size) 34 | commission_as_percentage_of_order_price = commission / order_price 35 | 36 | if commission < self.p.min_per_order: 37 | commission = self.p.min_per_order 38 | elif commission_as_percentage_of_order_price > self.p.max_per_order_abs_pct: 39 | commission = order_price * self.p.max_per_order_abs_pct 40 | return commission 41 | -------------------------------------------------------------------------------- /backtest/util/observers.py: -------------------------------------------------------------------------------- 1 | import backtrader as bt 2 | 3 | 4 | class Value(bt.Observer): 5 | alias = ('Value',) 6 | lines = ('value',) 7 | 8 | plotinfo = dict(plot=True, subplot=True) 9 | 10 | def next(self): 11 | self.lines.value[0] = self._owner.broker.getvalue() 12 | -------------------------------------------------------------------------------- /backtest/util/universe.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | 4 | 5 | class Universe: 6 | def __init__(self, assets=None): 7 | self.assets = assets 8 | 9 | def _read_csv(self, path): 10 | ticker_csv_path = os.path.join(os.path.dirname(__file__), path) 11 | tickers = pd.read_csv(ticker_csv_path, header=None)[1] 12 | return list(tickers) 13 | 14 | 15 | class SP500(Universe): 16 | def __init__(self): 17 | tickers = self._read_csv('../../data/spy/tickers.csv') 18 | exclude = self._read_csv('../../data/info/exclude.csv') 19 | tickers = [t for t in tickers if t not in exclude] 20 | super().__init__(tickers) 21 | 22 | 23 | class FAANG(Universe): 24 | def __init__(self): 25 | tickers = ['FB', 'AAPL', 'AMZN', 'NFLX', 'GOOG'] 26 | super().__init__(tickers) 27 | 28 | 29 | class SP500_TECH(Universe): 30 | def __init__(self): 31 | tickers = self._read_csv('../../data/spy/sp500-tech.csv') 32 | exclude = self._read_csv('../../data/info/exclude.csv') 33 | tickers = [t for t in tickers if t not in exclude] 34 | super().__init__(tickers) 35 | 36 | 37 | class SP100(Universe): 38 | def __init__(self): 39 | tickers = self._read_csv('../../data/spy/sp100.csv') 40 | exclude = self._read_csv('../../data/info/exclude.csv') 41 | tickers = [t for t in tickers if t not in exclude] 42 | super().__init__(tickers) 43 | 44 | 45 | def get(universe): 46 | return UNIVERSE_DICT[universe] 47 | 48 | 49 | UNIVERSE_DICT = { 50 | 'sp500': SP500, 51 | 'faang': FAANG, 52 | 'sp500_tech': SP500_TECH, 53 | 'sp100': SP100 54 | } 55 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- 1 | from . import spy, info, russell 2 | -------------------------------------------------------------------------------- /data/info/__init__.py: -------------------------------------------------------------------------------- 1 | from . import info, test_info 2 | -------------------------------------------------------------------------------- /data/info/columns.csv: -------------------------------------------------------------------------------- 1 | 0,Accounts Payable 2 | 1,Adjustments To Net Income 3 | 2,Capital Expenditure 4 | 3,Capital Surplus 5 | 4,Cash And Cash Equivalents 6 | 5,Change In Cash and Cash Equivalents 7 | 6,Changes In Accounts Receivables 8 | 7,Changes In Inventories 9 | 8,Changes In Liabilities 10 | 9,Changes In Other Operating Activities 11 | 10,Common Stock 12 | 11,Cost of Revenue 13 | 12,Deferred Long Term Asset Charges 14 | 13,Deferred Long Term Liability Charges 15 | 14,Depreciation 16 | 15,Discontinued Operations 17 | 16,Dividends Paid 18 | 17,Earnings Before Interest and Taxes 19 | 18,Effect Of Exchange Rate Changes 20 | 19,Goodwill 21 | 20,Gross Profit 22 | 21,Income Before Tax 23 | 22,Income Tax Expense 24 | 23,Intangible Assets 25 | 24,Interest Expense 26 | 25,Inventory 27 | 26,Investments 28 | 27,Long Term Debt 29 | 28,Long Term Investments 30 | 29,Minority Interest 31 | 30,Net Borrowings 32 | 31,Net Income Applicable To Common Shares 33 | 32,Net Income From Continuing Ops 34 | 33,Net Income 35 | 34,Net Receivables 36 | 35,Net Tangible Assets 37 | 36,Operating Income or Loss 38 | 37,Other Assets 39 | 38,Other Cash Flows from Financing Activities 40 | 39,Other Cash flows from Investing Activities 41 | 40,Other Current Assets 42 | 41,Other Current Liabilities 43 | 42,Other Liabilities 44 | 43,Other Stockholder Equity 45 | 44,Others 46 | 45,"Property, plant and equipment" 47 | 46,Research Development 48 | 47,Retained Earnings 49 | 48,Selling General and Administrative 50 | 49,Short Term Investments 51 | 50,Short/Current Long Term Debt 52 | 51,Total Assets 53 | 52,Total Cash Flow From Operating Activities 54 | 53,Total Cash Flows From Financing Activities 55 | 54,Total Cash Flows From Investing Activities 56 | 55,Total Current Assets 57 | 56,Total Current Liabilities 58 | 57,Total Liabilities 59 | 58,Total Operating Expenses 60 | 59,Total Other Income/Expenses Net 61 | 60,Total Revenue 62 | 61,Treasury Stock 63 | 62,ask 64 | 63,askSize 65 | 64,averageDailyVolume10Day 66 | 65,averageDailyVolume3Month 67 | 66,bid 68 | 67,bidSize 69 | 68,bookValue 70 | 69,earningsTimestamp 71 | 70,earningsTimestampEnd 72 | 71,earningsTimestampStart 73 | 72,epsForward 74 | 73,epsTrailingTwelveMonths 75 | 74,fiftyDayAverage 76 | 75,fiftyDayAverageChange 77 | 76,fiftyDayAverageChangePercent 78 | 77,fiftyTwoWeekHigh 79 | 78,fiftyTwoWeekHighChange 80 | 79,fiftyTwoWeekHighChangePercent 81 | 80,fiftyTwoWeekLow 82 | 81,fiftyTwoWeekLowChange 83 | 82,fiftyTwoWeekLowChangePercent 84 | 83,forwardPE 85 | 84,marketCap 86 | 85,priceHint 87 | 86,priceToBook 88 | 87,regularMarketChange 89 | 88,regularMarketChangePercent 90 | 89,regularMarketDayHigh 91 | 90,regularMarketDayLow 92 | 91,regularMarketOpen 93 | 92,regularMarketPreviousClose 94 | 93,regularMarketPrice 95 | 94,regularMarketTime 96 | 95,regularMarketVolume 97 | 96,sharesOutstanding 98 | 97,sourceInterval 99 | 98,Total stockholders' equity 100 | 99,trailingAnnualDividendRate 101 | 100,trailingAnnualDividendYield 102 | 101,trailingPE 103 | 102,twoHundredDayAverage 104 | 103,twoHundredDayAverageChange 105 | 104,twoHundredDayAverageChangePercent 106 | -------------------------------------------------------------------------------- /data/info/exclude.csv: -------------------------------------------------------------------------------- 1 | 11,ADM 2 | 17,AFL 3 | 18,AIG 4 | 19,AIV 5 | 20,AIZ 6 | 26,ALL 7 | 30,AMCR 8 | 39,ANTM 9 | 53,AXP 10 | 56,BAC 11 | 61,BF-B 12 | 64,BK 13 | 65,BKNG 14 | 66,BKR 15 | 71,BRK-B 16 | 75,C 17 | 78,CARR 18 | 80,CB 19 | 90,CFG 20 | 94,CI 21 | 95,CINF 22 | 98,CMA 23 | 106,COF 24 | 117,CTLT 25 | 119,CTVA 26 | 128,DFS 27 | 131,DHI 28 | 135,DISCK 29 | 137,DLR 30 | 167,EVRG 31 | 174,FANG 32 | 180,FE 33 | 184,FITB 34 | 190,FOXA 35 | 191,FRC 36 | 200,GL 37 | 204,GOOGL 38 | 206,GPN 39 | 209,GS 40 | 213,HBAN 41 | 219,HIG 42 | 230,HUM 43 | 231,HWM 44 | 245,IQV 45 | 252,J 46 | 258,JPM 47 | 260,KEY 48 | 269,KR 49 | 271,L 50 | 275,LEN 51 | 277,LHX 52 | 278,LIN 53 | 282,LNC 54 | 286,LUMN 55 | 288,LVS 56 | 289,LW 57 | 302,MET 58 | 308,MMC 59 | 311,MOS 60 | 315,MS 61 | 319,MTB 62 | 330,NLOK 63 | 338,NTRS 64 | 344,NWSA 65 | 351,OTIS 66 | 355,PBCT 67 | 356,PCAR 68 | 357,PEAK 69 | 358,PEG 70 | 361,PFG 71 | 363,PGR 72 | 370,PNC 73 | 377,PRU 74 | 385,QRVO 75 | 388,REG 76 | 390,RF 77 | 392,RJF 78 | 400,RTX 79 | 403,SCHW 80 | 406,SIVB 81 | 414,SPGI 82 | 417,STT 83 | 422,SYF 84 | 428,TDY 85 | 430,TER 86 | 431,TFC 87 | 440,TRV 88 | 443,TT 89 | 450,UAA 90 | 456,UNM 91 | 460,USB 92 | 463,VFC 93 | 464,VIAC 94 | 468,VNT 95 | 473,VTRS 96 | 481,WFC 97 | 483,WLTW 98 | 487,WRB 99 | 489,WST 100 | 500,ZBH 101 | 501,ZBRA 102 | 502,ZION 103 | -------------------------------------------------------------------------------- /data/info/industries.csv: -------------------------------------------------------------------------------- 1 | IndustryId;Sector;Industry 2 | 100001;Industrials;"Industrial Products" 3 | 100002;Industrials;"Business Services" 4 | 100003;Industrials;"Engineering & Construction" 5 | 100004;Industrials;"Waste Management" 6 | 100005;Industrials;"Industrial Distribution" 7 | 100006;Industrials;Airlines 8 | 100007;Industrials;"Consulting & Outsourcing" 9 | 100008;Industrials;"Aerospace & Defense" 10 | 100009;Industrials;"Farm & Construction Machinery" 11 | 100010;Industrials;"Transportation & Logistics" 12 | 100011;Industrials;"Employment Services" 13 | 100012;Industrials;"Truck Manufacturing" 14 | 100013;Industrials;Conglomerates 15 | 101001;Technology;"Computer Hardware" 16 | 101002;Technology;"Online Media" 17 | 101003;Technology;"Application Software" 18 | 101004;Technology;Semiconductors 19 | 101005;Technology;"Communication Equipment" 20 | 102001;"Consumer Defensive";"Retail - Defensive" 21 | 102002;"Consumer Defensive";"Consumer Packaged Goods" 22 | 102003;"Consumer Defensive";"Tobacco Products" 23 | 102004;"Consumer Defensive";"Beverages - Alcoholic" 24 | 102005;"Consumer Defensive";"Beverages - Non-Alcoholic" 25 | 102006;"Consumer Defensive";Education 26 | 103001;"Consumer Cyclical";Entertainment 27 | 103002;"Consumer Cyclical";"Retail - Apparel & Specialty" 28 | 103003;"Consumer Cyclical";Restaurants 29 | 103004;"Consumer Cyclical";"Manufacturing - Apparel & Furniture" 30 | 103005;"Consumer Cyclical";Autos 31 | 103011;"Consumer Cyclical";"Advertising & Marketing Services" 32 | 103013;"Consumer Cyclical";"Homebuilding & Construction" 33 | 103015;"Consumer Cyclical";"Travel & Leisure" 34 | 103018;"Consumer Cyclical";"Packaging & Containers" 35 | 103020;"Consumer Cyclical";"Personal Services" 36 | 103026;"Consumer Cyclical";Publishing 37 | 104001;"Financial Services";"Asset Management" 38 | 104002;"Financial Services";Banks 39 | 104003;"Financial Services";"Brokers & Exchanges" 40 | 104004;"Financial Services";"Insurance - Life" 41 | 104005;"Financial Services";Insurance 42 | 104006;"Financial Services";"Insurance - Property & Casualty" 43 | 104007;"Financial Services";"Credit Services" 44 | 104013;"Financial Services";"Insurance - Specialty" 45 | 105001;Utilities;"Utilities - Regulated" 46 | 105002;Utilities;"Utilities - Independent Power Producers" 47 | 106001;Healthcare;"Medical Diagnostics & Research" 48 | 106002;Healthcare;Biotechnology 49 | 106003;Healthcare;"Medical Instruments & Equipment" 50 | 106004;Healthcare;"Medical Devices" 51 | 106005;Healthcare;"Drug Manufacturers" 52 | 106006;Healthcare;"Health Care Plans" 53 | 106011;Healthcare;"Health Care Providers" 54 | 106014;Healthcare;"Medical Distribution" 55 | 107001;Energy;"Oil & Gas - Refining & Marketing" 56 | 107002;Energy;"Oil & Gas - E&P" 57 | 107003;Energy;"Oil & Gas - Midstream" 58 | 107004;Energy;"Oil & Gas - Services" 59 | 107005;Energy;"Oil & Gas - Integrated" 60 | 107006;Energy;"Oil & Gas - Drilling" 61 | 108001;"Business Services";"Communication Services" 62 | 108002;"Business Services";Consulting 63 | 109001;"Real Estate";REITs 64 | 109002;"Real Estate";"Real Estate Services" 65 | 110001;"Basic Materials";Chemicals 66 | 110002;"Basic Materials";"Forest Products" 67 | 110003;"Basic Materials";Agriculture 68 | 110004;"Basic Materials";"Metals & Mining" 69 | 110005;"Basic Materials";"Building Materials" 70 | 110006;"Basic Materials";Coal 71 | 110007;"Basic Materials";Steel 72 | 111001;Other;"Diversified Holdings" 73 | -------------------------------------------------------------------------------- /data/info/info.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samwindham/algo-trader/a1e335d87050f3e42151659416c7d1f170d650bc/data/info/info.p -------------------------------------------------------------------------------- /data/info/info.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import pandas as pd 4 | 5 | PATHS = { 6 | 'BALANCE': './us-balance-quarterly.csv', 7 | 'CASHFLOW': './us-cashflow-quarterly.csv', 8 | 'INCOME': './us-income-quarterly.csv' 9 | } 10 | 11 | 12 | def load(path='./info.p'): 13 | info_path = os.path.join(os.path.dirname(__file__), path) 14 | 15 | with open(info_path, 'rb') as f: 16 | info = pickle.load(f) 17 | return info 18 | 19 | 20 | def save(info, path='./info.p'): 21 | info_path = os.path.join(os.path.dirname(__file__), path) 22 | with open(info_path, 'wb') as f: 23 | pickle.dump(info, f, protocol=pickle.HIGHEST_PROTOCOL) 24 | 25 | 26 | def load_file(info_type): 27 | return pd.read_csv(os.path.join(os.path.dirname(__file__), PATHS[info_type]), sep=';') 28 | 29 | 30 | def load_info(info_type, ticker, date=None): 31 | data = load_file(info_type) 32 | t_data = data[data.Ticker == ticker] 33 | 34 | if date is None: 35 | return t_data 36 | else: 37 | row_label = (pd.to_datetime(t_data['Report Date']) > date).idxmax() 38 | return t_data.loc[row_label] 39 | 40 | 41 | def balance(ticker, date=None): 42 | return load_info('BALANCE', ticker, date) 43 | 44 | 45 | def all_balance(tickers=None): 46 | data = load_file('BALANCE') 47 | if tickers: 48 | return data[data.Ticker.isin(tickers)] 49 | return data 50 | 51 | 52 | def cashflow(ticker, date=None): 53 | return load_info('CASHFLOW', ticker, date) 54 | 55 | 56 | def income(ticker, date=None): 57 | return load_info('INCOME', ticker, date) 58 | 59 | 60 | def all_info(ticker=None): 61 | if ticker is None: 62 | info = pd.concat([load_file('BALANCE'), load_file('CASHFLOW'), load_file('INCOME')], axis=1) 63 | return info.loc[:, ~info.columns.duplicated()] 64 | else: 65 | info = pd.concat([balance(ticker), cashflow(ticker), income(ticker)], axis=1) 66 | return info.loc[:, ~info.columns.duplicated()] 67 | -------------------------------------------------------------------------------- /data/info/test_info.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datetime import datetime 3 | import pandas as pd 4 | 5 | from .info import load_info, balance, cashflow, income, all_info 6 | 7 | class TestInfo(unittest.TestCase): 8 | 9 | def test_load_info(self): 10 | df = load_info('BALANCE', 'AAPL') 11 | self.assertEqual(list(df.columns)[18], 'Total Assets') 12 | self.assertEqual(type(df), pd.DataFrame) 13 | 14 | df = load_info('CASHFLOW', 'AAPL') 15 | self.assertEqual(list(df.columns)[-1], 'Net Change in Cash') 16 | 17 | df = load_info('INCOME', 'AAPL') 18 | self.assertEqual(list(df.columns)[-2], 'Net Income') 19 | 20 | df = load_info('BALANCE', 'AAPL', datetime(2016, 1, 1)) 21 | self.assertEqual(type(df), pd.Series) 22 | 23 | def test_balance(self): 24 | df = balance('AAPL') 25 | self.assertEqual(df.iloc[0]['Report Date'], '2000-06-30') 26 | self.assertEqual(df.iloc[0]['Total Assets'], 6803000000) 27 | 28 | s = balance('AAPL', datetime(2016, 1, 1)) 29 | self.assertEqual(s['Report Date'], '2016-03-31') 30 | self.assertEqual(s['Total Assets'], 305277000000) 31 | 32 | def test_cashflow(self): 33 | df = cashflow('AAPL') 34 | self.assertEqual(df.iloc[0]['Report Date'], '2000-06-30') 35 | self.assertEqual(df.iloc[0]['Net Change in Cash'], -318000000) 36 | 37 | s = cashflow('AAPL', datetime(2016, 1, 1)) 38 | self.assertEqual(s['Report Date'], '2016-03-31') 39 | self.assertEqual(s['Net Change in Cash'], 4825000000) 40 | 41 | def test_income(self): 42 | df = income('AAPL') 43 | self.assertEqual(df.iloc[0]['Report Date'], '2000-06-30') 44 | self.assertEqual(df.iloc[0]['Net Income'], 200000000) 45 | 46 | s = income('AAPL', datetime(2016, 1, 1)) 47 | self.assertEqual(s['Report Date'], '2016-03-31') 48 | self.assertEqual(s['Net Income'], 10516000000) 49 | 50 | def test_all_info(self): 51 | df = all_info() 52 | self.assertEqual(len(df.Ticker.unique()), 2080) 53 | 54 | df = all_info('AAPL') 55 | self.assertEqual(len(df), 78) 56 | -------------------------------------------------------------------------------- /data/quotes.csv: -------------------------------------------------------------------------------- 1 | Symbol,Current Price,Date,Time,Change,Open,High,Low,Volume,Trade Date,Purchase Price,Quantity,Commission,High Limit,Low Limit,Comment 2 | PMT,22.58,2019/10/04,16:02 EDT,0.289999,22.29,22.58,22.27,1070709,20191007,,1.0,,,, 3 | JAG,6.76,2019/10/04,16:01 EDT,-0.119999886,6.86,6.96,6.57,2029729,20191007,,1.0,,,, 4 | OI,9.36,2019/10/04,16:03 EDT,0.009999275,9.35,9.4206,9.09,2209027,20191007,,1.0,,,, 5 | PUMP,8.71,2019/10/04,16:02 EDT,0.0100002,8.79,8.94,8.45,1952646,20191007,,1.0,,,, 6 | ANF,14.37,2019/10/04,16:05 EDT,-0.35000038,14.76,14.7973,14.315,1612842,20191007,,1.0,,,, 7 | HOME,9.33,2019/10/04,16:05 EDT,0.07999992,9.38,9.465,8.92,2087245,20191007,,1.0,,,, 8 | WTI,4.2,2019/10/04,16:04 EDT,0.04,4.19,4.24,4.08,1551003,20191007,,1.0,,,, 9 | GPOR,2.6,2019/10/04,16:00 EDT,0.06499982,2.49,2.66,2.4,6493909,20191007,,1.0,,,, 10 | CRZO,7.45,2019/10/04,16:00 EDT,-0.05000019,7.54,7.7228,7.21,2959712,20191007,,1.0,,,, 11 | AKS,2.22,2019/10/04,16:02 EDT,0.0599999,2.18,2.22,2.12,4788802,20191007,,1.0,,,, 12 | AR,2.9,2019/10/04,16:02 EDT,0.0500002,2.8,2.92,2.66,12959336,20191007,,1.0,,,, 13 | DNR,1.09,2019/10/04,16:03 EDT,0.0300001,1.06,1.1,1.04,8853406,20191007,,1.0,,,, 14 | CLF,7.38,2019/10/04,16:03 EDT,0.07999992,7.29,7.56,7.25,18492050,20191007,,1.0,,,, 15 | CPE,3.93,2019/10/04,16:01 EDT,-0.03999996,3.96,4.05,3.79,10835986,20191007,,1.0,,,, 16 | X,10.77,2019/10/04,16:01 EDT,-0.0199995,10.86,10.935,10.46,10902218,20191007,,1.0,,,, 17 | SWN,1.88,2019/10/04,16:02 EDT,0.0599999,1.83,1.91,1.71,19495452,20191007,,1.0,,,, 18 | -------------------------------------------------------------------------------- /data/russell/__init__.py: -------------------------------------------------------------------------------- 1 | from . import russell 2 | -------------------------------------------------------------------------------- /data/russell/russell.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import numpy as np 4 | 5 | from ..info import info as info_tool 6 | 7 | 8 | def small_cap(): 9 | # get small-cap tickers 10 | ticker_csv_path = os.path.join(os.path.dirname(__file__), './small_cap.csv') 11 | df = pd.read_csv(ticker_csv_path).set_index('Ticker') 12 | 13 | # load info 14 | info = info_tool.load('./small_cap.p') 15 | 16 | # get columns 17 | column_csv_path = os.path.join(os.path.dirname(__file__), '../info/columns.csv') 18 | info_columns = pd.read_csv(column_csv_path, header=None, index_col=0)[1].rename('info_columns') 19 | 20 | # convert info into DataFrame 21 | info_df = pd.DataFrame.from_dict(info, orient='index')[list(info_columns)] 22 | 23 | # calculate extra fundamentals 24 | info_df['turnover'] = info_df['averageDailyVolume3Month'] / info_df['sharesOutstanding'] 25 | info_df['debtToEquity'] = (pd.to_numeric(info_df['Total Liabilities']) / 26 | pd.to_numeric(info_df["Total stockholders' equity"])) 27 | info_df['returnOnEquity'] = (pd.to_numeric(info_df['Net Income']) / 28 | pd.to_numeric(info_df["Total stockholders' equity"])) 29 | info_df['priceToRevenue'] = (pd.to_numeric(info_df['marketCap']) / 30 | pd.to_numeric(info_df['Total Revenue'])) 31 | 32 | # filter low-liquid stocks 33 | df_filter = ( 34 | info_df['marketCap'] > 100000 35 | ) & ( 36 | info_df['averageDailyVolume3Month'] > info_df['averageDailyVolume3Month'].quantile(0.05) 37 | ) & ( 38 | info_df['turnover'] < info_df['turnover'].quantile(.95) 39 | ) & ( 40 | info_df['trailingAnnualDividendYield'] > 0 41 | ) & ( 42 | info_df['trailingPE'] <= 20 43 | ) & ( 44 | info_df['forwardPE'] <= 25 45 | ) & ( 46 | info_df['epsForward'] > 0 47 | ) & ( 48 | info_df['priceToBook'] <= 3 49 | ) & ( 50 | info_df['debtToEquity'] <= 2 51 | ) & ( 52 | info_df['returnOnEquity'] >= .1 53 | ) 54 | 55 | info_df = info_df[df_filter] 56 | info_df.to_csv(os.path.join(os.path.dirname(__file__), 'small_cap_filtered.csv')) 57 | 58 | return info_df 59 | 60 | 61 | if __name__ == '__main__': 62 | small_cap() 63 | -------------------------------------------------------------------------------- /data/russell/small_cap_filtered.csv: -------------------------------------------------------------------------------- 1 | ,Accounts Payable,Adjustments To Net Income,Capital Expenditure,Capital Surplus,Cash And Cash Equivalents,Change In Cash and Cash Equivalents,Changes In Accounts Receivables,Changes In Inventories,Changes In Liabilities,Changes In Other Operating Activities,Common Stock,Cost of Revenue,Deferred Long Term Asset Charges,Deferred Long Term Liability Charges,Depreciation,Discontinued Operations,Dividends Paid,Earnings Before Interest and Taxes,Effect Of Exchange Rate Changes,Goodwill,Gross Profit,Income Before Tax,Income Tax Expense,Intangible Assets,Interest Expense,Inventory,Investments,Long Term Debt,Long Term Investments,Minority Interest,Net Borrowings,Net Income Applicable To Common Shares,Net Income From Continuing Ops,Net Income,Net Receivables,Net Tangible Assets,Operating Income or Loss,Other Assets,Other Cash Flows from Financing Activities,Other Cash flows from Investing Activities,Other Current Assets,Other Current Liabilities,Other Liabilities,Other Stockholder Equity,Others,"Property, plant and equipment",Research Development,Retained Earnings,Selling General and Administrative,Short Term Investments,Short/Current Long Term Debt,Total Assets,Total Cash Flow From Operating Activities,Total Cash Flows From Financing Activities,Total Cash Flows From Investing Activities,Total Current Assets,Total Current Liabilities,Total Liabilities,Total Operating Expenses,Total Other Income/Expenses Net,Total Revenue,Treasury Stock,ask,askSize,averageDailyVolume10Day,averageDailyVolume3Month,bid,bidSize,bookValue,earningsTimestamp,earningsTimestampEnd,earningsTimestampStart,epsForward,epsTrailingTwelveMonths,fiftyDayAverage,fiftyDayAverageChange,fiftyDayAverageChangePercent,fiftyTwoWeekHigh,fiftyTwoWeekHighChange,fiftyTwoWeekHighChangePercent,fiftyTwoWeekLow,fiftyTwoWeekLowChange,fiftyTwoWeekLowChangePercent,forwardPE,marketCap,priceHint,priceToBook,regularMarketChange,regularMarketChangePercent,regularMarketDayHigh,regularMarketDayLow,regularMarketOpen,regularMarketPreviousClose,regularMarketPrice,regularMarketTime,regularMarketVolume,sharesOutstanding,sourceInterval,Total stockholders' equity,trailingAnnualDividendRate,trailingAnnualDividendYield,trailingPE,twoHundredDayAverage,twoHundredDayAverageChange,twoHundredDayAverageChangePercent,turnover,debtToEquity,returnOnEquity,priceToRevenue 2 | ADES,6235,-41467,-467,96750,18577,-6921,14098,-,-15371,-3657,23,5253,32539,,723,,-20165,2229,,,18692,45877,10423,4830,-2151,21791,46425,43640,7186,,-,35342,35454,35454,13838,63117,2229,42644,-2112,,4337,1252,940,,,45228,,12914,15740,,22318,159664,-9889,19511,-16543,59776,40719,91717,21716,43648,23945,-41740,14.0,8,176033,132981,14.01,13,5.953,1565049600,1494619200.0,1494532800.0,4.81,1.832,12.887143,1.0466566,0.08121712,14.8,-0.86620045,-0.058527056,9.01,4.9237995,0.5464816,2.8968399,258862128,2,2.3406348,-0.036200523,-0.25913045,14.13,13.9338,14.06,13.97,13.9338,1569336401,23168,18578000,15,67947,1.0,0.07158196,7.605786,12.474638,1.4591618,0.11697027,0.007157982560017225,1.3498314863055028,0.521789041458784,10810.696512841929 3 | AIT,237289,44710,-18970,172931,108219,54069,8465,-16590,-29788,-32308,-,2464416,3859,,62119,,-47266,267767,109,661991,1008323,194481,50488,368866,-40788,447555,,908850,11246,,-6238,143993,143993,143993,549822,-133823,267767,17153,-3385,-,42542,65984,102019,-99886,,124303,,1229148,698673,,49036,2331697,180601,-71539,-55102,1148138,423794,1434663,3204972,-73286,3472739,-515045,54.96,9,312000,224025,54.92,22,23.241,1565800200,1572888600.0,1572366600.0,4.74,3.68,54.102856,0.94714355,0.01750635,79.93,-24.880001,-0.31127238,49.45,5.5999985,0.11324567,11.613924,2127781504,2,2.3686588,0.16999817,0.30976343,55.3,54.68,54.77,54.88,55.05,1569336328,30086,38651800,15,897034,1.22,0.022230322,14.959239,57.479057,-2.429058,-0.04225988,0.005795978453784817,1.5993407161824413,0.1605212288497426,612.7098823147953 4 | AVX,96631,3619,-129655,362498,378456,-146790,22225,-128556,5469,-17401,-,1308907,75938,,90031,,-77696,305768,-2814,316675,482883,333243,61430,118944,,631688,-156756,,,,-1893,271813,271813,271813,262279,1948561,305768,138175,,-957,853,106835,115913,-39494,,455757,,2156584,177115,434754,,2813278,232966,-77265,-299677,1783727,313185,429098,1486022,27475,1791790,-136666,15.67,8,299016,264595,15.62,10,14.355,1564158600,1572280200.0,1571761800.0,1.05,1.6,14.460857,1.1891422,0.08223179,19.36,-3.710001,-0.19163227,13.03,2.62,0.20107444,14.904762,2646305280,2,1.0902125,0.059999466,0.38485867,15.87,15.6,15.69,15.59,15.65,1569336103,20123,169092992,15,2384180,0.46,0.029506095,9.78125,16.005651,-0.35565186,-0.022220392,0.0015647898642659301,0.179977182930819,0.11400691222978131,1476.90593205677 5 | B,143419,15138,-57273,470818,100719,-44571,-10960,-12369,-6387,-1838,634,963524,20474,,94238,,-32206,235574,-4148,955524,532365,207495,41309,636538,-16841,265990,,936357,1412,,401987,166186,166186,166186,394097,-389006,235574,38819,-11678,-1000,18803,87913,311697,-190500,,370531,,1363772,296791,,5522,2808970,237199,215564,-493186,806146,357860,1605914,1260315,-28079,1495889,-632168,53.27,8,426066,230376,53.12,8,23.988,1572021000,1572021000.0,1572021000.0,3.44,2.878,47.554,5.576,0.11725617,72.7,-19.569996,-0.2691884,42.39,10.740002,0.25336167,15.444768,2689424640,2,2.2148573,-0.54999924,-1.0245887,53.77,52.91,53.69,53.68,53.13,1569336182,25500,50619700,15,1203056,0.64,0.011922504,18.460737,52.4621,0.6679001,0.012731097,0.004551113499289802,1.3348622175526326,0.13813654559721245,1797.8771419537145 6 | BDC,352646,53404,-97847,1139395,420610,-140498,-21748,-14779,-29401,-2592,-,1547056,56018,,145644,,-43169,317472,-7272,1557653,1038312,220330,59619,511093,-61559,316418,,1463200,,441,-53487,125963,160711,160894,465939,-681600,317472,85881,-8020,,55757,101194,211611,-74907,,365970,135085,922000,486926,,,3779321,289220,-281770,-140676,1258724,716922,2391733,2267896,-97142,2585368,-674752,51.21,9,324016,398735,51.08,8,36.539,1564590600,1572888600.0,1572366600.0,5.82,4.047,47.207714,4.092285,0.08668679,72.8,-21.500004,-0.29532972,37.79,13.509998,0.35750192,8.814432,2331713280,2,1.4039792,0.29999924,0.58823377,51.605,51.095,51.02,51.0,51.3,1569336327,22164,45452500,15,1387146,0.2,0.003921569,12.676056,54.04029,-2.7402916,-0.050708305,0.008772564765414444,1.724211438449882,0.11598923256816514,901.8883501304263 7 | BKE,29008,5113,-10021,148564,168471,3385,-550,-7487,-1292,-4340,490,448524,,,26848,,-48872,120928,,,436972,126644,31036,,,125190,2115,,4767,,,95608,95608,95608,7089,393877,120928,21421,,158,,21776,43207,,,130682,,244823,316044,51546,,527302,108727,-97744,-7598,370432,90218,133425,764568,5716,885496,,19.8,8,491650,693842,19.76,8,8.305,1574447400,1574447400.0,1574447400.0,1.79,1.919,19.220572,0.55942917,0.02910575,23.42,-3.6399994,-0.15542269,14.81,4.9700003,0.33558407,11.05028,973646784,2,2.3816977,0.09000015,0.45708558,20.07,19.73,19.83,19.69,19.78,1569336320,70862,49223800,15,393877,1.0,0.0507872,10.307452,18.327318,1.4526825,0.07926323,0.014095661042016261,0.3387478832224273,0.24273567636597213,1099.5496128723337 8 | BRY,13564,-144760,-127281,914540,68680,-58,-1683,,19526,-8786,82,212022,,16000,86271,,-,204878,,,369783,190137,43035,,-35648,9473,,391786,,,21000,49160,147102,147102,57379,1006446,204878,20533,-9193,,88834,16681,149913,,31381,1442708,,116042,47253,,,1692263,103100,15911,-119069,229022,144118,685817,376927,-14741,581805,-24218,10.28,8,1080066,732846,10.26,8,11.761,1565222400,,,1.27,0.995,8.902857,1.1571436,0.12997441,18.55,-8.489999,-0.4576819,7.58,2.4800005,0.32717684,7.9212604,814591360,2,0.8553695,-0.50499916,-4.779926,10.56,10.06,10.56,10.565,10.06,1569336405,79977,80973296,15,1006446,0.45,0.042593468,10.110553,10.548478,-0.4884777,-0.046307884,0.00905046522991975,0.6814245374317152,0.14615985358379882,1400.110621256263 9 | CORR,3493.49,-11390.5,,320295.969,69287.177,53500.108,-8336.055,,323.446,156.692,11.96,7210.748,4948.203,2838.443,24855.518,,-43871.559,44030.55,,-,82020.85,41293.15,-2418.726,,-12759.01,,1113.006,146510.38,,,-3528,34163.499,43711.876,43711.876,37506.577,327736.762,44030.55,7786.646,-264.01,-,488.005,,14508.392,,,508095.907,,9147.701,13042.847,,-,624883.18,48622.74,-51939.122,56816.49,107281.759,8853.103,169871.875,45201.048,-2737.4,89231.598,,47.45,11,93300,128087,47.29,10,27.224,1564617600,1572886800.0,1572361140.0,2.33,2.612,45.813145,1.6068535,0.03507407,49.75,-2.3300018,-0.046834208,32.52,14.899998,0.4581795,20.35193,641668416,2,1.7418453,0.45999908,0.97955513,47.44,47.07,47.3,46.96,47.42,1569336067,25320,13531600,15,329455.63,3.0,0.06388416,18.15467,40.687176,6.7328224,0.16547775,0.009465769014750658,0.5156138172536314,0.132679098548111,7191.044768692811 10 | CPSI,5668,12527,-978,164793,5732,5212,-3898,-81,-1688,-10260,-,130683,,995,9265,,-5620,24882,,-,149728,18108,476,86226,-7577,1498,,124583,,,-11710,17037,17632,17632,55533,-66892,24882,20258,-409,,4143,10407,4877,,,10875,36371,-5024,77988,,6236,327746,23929,-17739,-978,69938,38503,167963,255529,-6774,280411,,24.08,10,151383,127632,23.9,8,12.087,1565136000,1572886800.0,1572447540.0,2.46,1.302,22.534286,1.3957138,0.061937343,34.0,-10.07,-0.29617646,20.72,3.210001,0.15492283,9.727642,343519936,2,1.9798131,0.34000015,1.4412893,24.08,23.56,23.61,23.59,23.93,1569335973,18074,14355200,15,159783,0.4,0.016956337,18.379416,26.640144,-2.710144,-0.10173158,0.008890994204190816,1.051194432449009,0.11034966172871957,1225.0587031179234 11 | CRAI,21938,5952,-15447,,38028,-16007,-17414,,18786,-3569,22837,289429,9330,,9942,,-5784,28691,-1002,88208,128219,28933,6461,7846,-647,,,,,,,22384,22472,22492,136689,100418,28691,45564,-299,,12527,9190,31877,-12594,,48088,,186229,89533,,,370846,36189,-35747,-15447,181140,142497,174374,388957,242,417648,-12594,44.01,8,48666,61970,43.67,8,24.891,1564677000,1572888600.0,1572453000.0,2.94,2.49,41.068287,2.841713,0.06919482,54.23,-10.32,-0.19030057,34.24,9.669998,0.28241816,14.935374,345817152,2,1.7640914,-0.13999939,-0.31781927,43.98,43.7,43.98,44.05,43.91,1569336097,19201,7875590,15,196472,0.77,0.017480137,17.634539,42.919853,0.99014664,0.023069665,0.00786861682743769,0.8875259578973085,0.11447941691436948,828.011033214573 12 | CRS,238700,35500,-180300,320400,27000,-29200,-5300,-94000,20100,-12400,279000,1935400,4200,,115400,,-38600,242500,2400,326400,444800,216000,49000,191000,-26000,787700,2900,550600,1600,,19700,165100,167000,167000,384100,1002700,242500,66400,,11400,37100,31400,701100,-351800,,1366200,,1605300,202300,,,3187800,232400,-19400,-244600,1236200,416000,1667700,2137700,-26500,2380200,-684600,52.83,11,389716,268575,52.56,10,32.022,1564677000,1572280200.0,1571761800.0,5.25,3.43,49.18543,3.3645706,0.068405844,60.38,-7.830002,-0.12967873,32.77,19.779999,0.6036008,10.009523,2504753664,2,1.6410593,-1.3699989,-2.5407994,53.99,52.55,53.75,53.92,52.55,1569336107,20130,47664200,15,1520100,0.8,0.014836796,15.320699,47.249638,5.3003616,0.11217783,0.005634732146978235,1.0970988750740083,0.10986119334254325,1052.3290748676582 13 | CXW,96642,-1808,,1807202,52802,21954,-19470,,19377,,1187,1315250,14947,3322,163001,,-204198,260246,,48169,520516,167560,8353,50049,-82153,,,1787555,,,202554,159207,159207,159207,270597,1316841,260246,353328,-7587,-6703,50126,147718,67833,,,2830589,,-393330,103769,,14121,3655660,322880,-9869,-291057,373525,366396,2240601,1575520,-92686,1835766,,17.3,8,915733,973809,17.27,8,11.609,1565049600,1573232400.0,1572883140.0,1.6,1.51,17.426857,-0.12685776,-0.00727944,24.77,-7.470001,-0.30157453,15.57,1.7299995,0.11111108,10.812499,2060360704,2,1.490223,0.25,1.4662757,17.45,17.07,17.08,17.05,17.3,1569335883,144088,119096000,15,1415059,1.74,0.10205279,11.456953,19.583334,-2.2833347,-0.11659581,0.008176672600255256,1.5833975827156324,0.11250908972700079,1122.3438629977895 14 | EBF,13728,-1033,-4824,123065,88442,-7788,1480,-3580,-2383,3111,-,277422,,,16189,,-22611,49858,,81634,123360,49934,12497,61272,-1154,35411,,-,,,,37437,37437,37437,40357,146221,49858,880,,,195,214,12335,-16704,,53134,,179003,73502,,,363085,51335,-27353,-31770,166165,31623,73958,350924,76,400782,-88075,19.15,8,187583,120301,19.09,9,11.251,1569283200,1569283200.0,1569283200.0,1.46,1.454,20.314857,-1.0814571,-0.05323479,22.0,-2.7665997,-0.12575454,17.36,1.8733997,0.10791473,13.173561,502255232,2,1.7094836,-0.1765995,-0.9098377,19.4,19.09,19.36,19.41,19.2334,1569336350,89563,26113700,15,289127,0.9,0.04636785,13.227923,20.148115,-0.9147148,-0.045399524,0.004606815579561686,0.2557976252650219,0.12948289160126863,1253.1880972698375 15 | EME,652091,19069,-43479,21103,363907,-103174,-177036,-3915,99280,-32019,601,6925178,4700,,80915,-2345,-18640,409039,-3421,990887,1205453,395028,109106,488286,-13544,42321,-3400,277626,2899,896,-16736,283531,285922,283531,1944463,261372,409039,86177,-3339,,2307,596890,333204,-87662,,134351,,2060440,796414,,13938,4088807,271011,-253042,-117722,2386207,1734398,2347366,7721592,-14011,8130631,-341599,85.74,14,376283,351154,85.66,8,33.747,1564504200,1572280200.0,1571848200.0,5.94,5.481,84.426285,1.3837128,0.016389597,89.55,-3.7400055,-0.04176444,57.29,28.519997,0.49781805,14.446127,4816927232,2,2.5427444,0.0799942,0.09330946,86.2,85.5,85.8,85.73,85.81,1569336436,48394,56134800,15,1740545,0.32,0.0037326487,15.655902,81.78333,4.0266647,0.04923576,0.006255549142421457,1.3486385011591198,0.16289782797916744,592.4419927555438 16 | ENS,292449,44029,-70372,512696,299212,-222906,5974,-46614,9944,-40381,548,2104612,40466,,63348,,-29743,273371,-43455,656399,703405,182211,21584,462316,-30868,503869,,971756,,3730,428731,160239,160627,160239,673737,163572,273371,53391,-1393,,37340,61542,247312,-150522,,409439,,1450325,430034,,,3118193,197855,346577,-723883,1536648,612933,1832176,2534646,-91160,2808017,-681282,67.49,8,377250,298607,67.29,14,30.437,1565222400,1573491600.0,1572969540.0,5.92,3.782,59.798286,7.831711,0.13096882,89.83,-22.200005,-0.24713352,53.56,14.069996,0.26269597,11.4239855,2873050880,2,2.2219665,-0.1800003,-0.26544803,68.44,67.26,67.9,67.81,67.63,1569336234,35736,42481900,15,1282287,0.7,0.010322961,17.882072,64.239204,3.3907928,0.052783854,0.007029040603174529,1.428834574475137,0.1249634442211455,1023.1600734610937 17 | GEF,403800,27800,-149100,,94200,-48100,-34000,-24800,24300,-78700,150500,3084900,7900,,126900,,-100000,385100,-7600,776000,788900,302800,73300,80600,-51000,289500,,936000,,81900,-55700,209400,229500,209400,456700,251200,385100,169600,-2600,,96500,125900,398900,-377100,,1191900,,1469800,403800,,18800,3194800,253000,-158300,-135200,976700,670200,2005100,3488700,-82300,3873800,-512500,38.13,8,197850,280767,38.02,12,23.217,1567036800,1575910800.0,1575388740.0,3.89,3.033,34.790855,3.359146,0.09655256,57.29,-19.14,-0.3340897,29.85,8.300001,0.27805698,9.807199,1991834496,2,1.6431926,-0.11999893,-0.31355873,38.39,38.01,38.44,38.27,38.15,1569336067,17631,26257900,15,1107800,1.76,0.045989025,12.578306,36.51855,1.6314507,0.04467457,0.010692667730473495,1.8099837515797075,0.18902328940241922,514.1810356755641 18 | HI,196800,81900,-27000,351400,56000,-10000,-13000,-24000,41600,5700,,1126400,,,56500,,-52100,239800,-2700,581900,643700,146500,65300,487300,-23300,172500,,344600,,13000,-120200,76600,81200,76600,338800,-338100,239800,42800,-6300,200,18100,263000,244200,-84200,,142000,,531000,373700,,,1864600,248300,-232500,-23100,610600,531700,1120500,1530300,-93300,1770100,-151300,30.09,8,789516,770951,30.03,8,12.479,1564617600,1573837200.0,1573487940.0,2.56,2.223,28.774857,1.2951431,0.045009542,53.41,-23.34,-0.43699682,26.01,4.0599995,0.15609379,11.746094,1884399616,2,2.4096482,-0.25,-0.8245383,30.51,29.94,30.43,30.32,30.07,1569336432,102995,62667100,15,731100,0.838,0.027638523,13.526765,36.845726,-6.7757263,-0.1838945,0.01230232450520289,1.5326220763233485,0.10477362877855287,1064.5724060787527 19 | HNI,221395,26758,-55648,18041,76819,53471,,,,-8493,43582,1420557,1569,1500,57679,,-51085,146198,,270788,837338,118725,25399,192502,-10027,157178,424,249355,14477,326,-29652,93377,93326,93377,255736,99643,146198,7261,,1135,400,11863,154922,-3599,,384933,33420,504909,633996,1327,679,1401844,186430,-91739,-41220,531883,434308,838585,2111697,-27473,2257895,-3599,34.98,8,299700,228387,34.91,9,12.343,1564012800,1572019200.0,1571669940.0,2.94,2.015,32.064857,2.9451408,0.09184949,44.79,-9.780003,-0.21835236,29.9,5.1099987,0.17090297,11.908162,1501043200,2,2.8364253,0.15999985,0.45911005,35.19,34.93,34.86,34.85,35.01,1569336413,104491,42874700,15,562933,1.19,0.034146346,17.374687,34.8171,0.1928978,0.005540318,0.00532684776803103,1.489671062097976,0.1658758679985007,664.7976101634487 20 | HSII,9166,6736,-5960,227147,279906,72100,-16759,,-2425,52776,-,506349,34830,,12522,,-10181,70142,-5565,122092,209674,70492,21197,2216,,,794,,,,,49295,49295,49295,142572,142848,70142,91583,-4573,,5623,82132,114522,4062,,33871,,56049,139532,,,700629,102902,-16988,-8249,450867,318951,433473,645881,350,716023,-16236,27.84,8,100866,116804,27.7,8,15.08,1564444800,1572624000.0,1572274740.0,2.55,2.748,27.131714,0.58828545,0.021682575,44.66,-16.94,-0.37931037,25.27,2.4499989,0.09695286,10.870588,530333472,2,1.8381963,-0.10000038,-0.35945502,28.23,27.14,28.23,27.82,27.72,1569336355,18849,19131800,15,267156,0.56,0.020129403,10.087337,32.720654,-5.000654,-0.15282868,0.0061052279450966454,1.6225463773974755,0.184517660093728,740.665414379147 21 | JOUT,34160,4784,-19152,75025,121877,58067,5409,-10495,8432,1396,-,302408,11748,,9358,,-4350,63021,-404,11199,241860,68106,27437,12341,-203,88864,17893,,,,,40669,40669,40669,40866,255657,63021,30768,-,,5373,16238,23955,3487,,55934,20441,202828,158398,28714,,395936,63358,-4935,48,285694,92784,116739,481247,5085,544268,841,56.91,8,41466,54115,56.35,8,32.378,1564763400,1575916200.0,1575570600.0,4.27,4.231,58.04114,-0.84114075,-0.014492147,96.69,-39.49,-0.40841866,54.12,3.0800018,0.056910604,13.395784,570478464,2,1.7666317,-0.13000107,-0.22675923,57.56,56.15,57.37,57.33,57.2,1569336133,10813,8838460,15,279197,0.56,0.009768009,13.519263,70.24029,-13.040287,-0.18565252,0.0061226729543382,0.418124120244845,0.14566417260930453,1048.1572754598837 22 | KAI,35720,6620,-16559,104731,45830,-30729,-18366,-6577,5419,-8092,-,355505,15428,1735,23568,,-9644,90644,-3195,258174,278281,79528,18482,113347,-7032,86373,,172405,148,1603,-59587,60413,61046,60413,108365,1447,90644,21162,-1851,,12193,60699,47684,-39376,,80157,10552,393578,177085,,1668,725749,62985,-74155,-16364,252761,128989,351178,543142,-11116,633786,-125487,86.79,8,44533,67409,86.3,40,35.425,1564585200,1509739200.0,1509393600.0,6.04,5.637,83.51228,2.838417,0.033988018,109.45,-23.099297,-0.21104886,76.44,9.910698,0.12965329,14.2964735,972334784,2,2.437564,-1.0292969,-1.1779548,87.67,85.94,86.95,86.96,86.3507,1569336352,7307,11260300,15,372968,0.9,0.010349586,15.318556,87.79703,-1.4463272,-0.016473534,0.005986430201682016,0.9415767572553141,0.16197904377855474,1534.168921370936 23 | KALU,121400,73800,-74100,1059300,125600,75300,-21600,-45000,29200,-22800,-,1300700,35900,,43900,,-37700,139500,,44000,285200,120000,28300,32400,-22700,215100,147800,370400,,,-700,91700,91700,91700,260300,664000,139500,76100,,,3700,24700,97400,-48800,,610200,,150200,101800,36700,,1419300,150200,-106000,31100,-,205500,678900,1446400,-19500,1585900,-469300,97.25,8,159983,118001,97.05,9,46.792,1564012800,1571673600.0,1571151540.0,7.85,5.577,93.01943,4.24057,0.045588,114.99,-17.729996,-0.15418729,83.29,13.970001,0.16772723,12.38981,1556451840,2,2.0785606,-0.73999786,-0.75509983,98.23,96.99,98.19,98.0,97.26,1569336321,12568,16003000,15,740400,2.35,0.023979591,17.439484,96.75681,0.5031891,0.0052005546,0.007373679935012185,0.916936790923825,0.12385197190707725,981.4312630052336 24 | KBR,546000,-106000,-17000,2190000,739000,300000,-178000,,52000,10000,,4457000,222000,,63000,,-44000,290000,-28000,1265000,456000,398000,88000,516000,-66000,,-,1243000,744000,20000,855000,279200,310000,281000,1141000,-63000,290000,467000,-156000,2000,30000,579000,672000,-913000,,121000,,1258000,166000,,32000,5072000,165000,654000,-491000,1959000,1419000,3334000,4623000,108000,4913000,-1730000,25.46,8,1246450,1086157,25.44,9,12.533,1564590600,1572625800.0,1572280200.0,1.97,1.335,25.376,0.083999634,0.0033102,26.85,-1.3900013,-0.051769134,13.9,11.559999,0.83165467,12.923858,3603990272,2,2.031437,0.13999939,0.5529202,25.6096,25.44,25.44,25.32,25.46,1569336360,66551,141555008,15,1718000,0.32,0.012638231,19.07116,23.28384,2.176159,0.0934622,0.00767303831454695,1.940628637951106,0.16356228172293363,733.5620337879096 25 | KMT,212908,34053,-212343,528827,182015,-374138,17323,-53387,-49378,-11082,103026,1541238,20507,,112052,,-65746,331434,-1744,300011,833996,311235,63359,160998,-32994,571576,,592474,,39532,-400889,241925,247876,241925,379855,874163,331434,69538,-4583,-381,57381,109120,227365,-373543,,934895,,1076862,488151,,,2656269,300519,-471432,-201481,1190827,461726,1281565,2043800,-20199,2375234,-373543,30.61,8,679416,826342,30.58,8,16.199,1565049600,1573232400.0,1572883140.0,3.19,2.9,30.152287,0.3977127,0.013190134,45.05,-14.5,-0.3218646,27.49,3.0599995,0.11131319,9.576802,2530401536,2,1.8859189,-0.1800003,-0.58574784,30.98,30.43,30.67,30.73,30.55,1569336330,57027,82828200,15,1335172,0.8,0.026033193,10.534482,34.432102,-3.882103,-0.11274661,0.009976578025358514,0.9598501166890857,0.18119388363446806,1065.3272629138855 26 | KRO,119900,49800,-56300,1399100,373300,51000,8700,-135500,16400,10800,-,1099700,122000,900,49700,,-78800,305200,-14400,,562200,293800,88800,,-19500,497900,,455100,84700,,-1500,205000,205000,205000,312500,839800,305200,125600,-100,13600,1400,54400,369800,-424300,-800,486400,,-136200,257800,,1500,1898100,188500,-80400,-42700,1201400,233400,1058300,1356700,-11400,1661900,-424300,12.34,8,230633,289965,12.3,8,7.495,1565208916,1573491600.0,1572969540.0,1.38,1.004,11.335143,0.97485733,0.08600309,17.14,-4.829999,-0.2817969,9.65,2.6600008,0.27564776,8.92029,1425571968,2,1.6424284,-0.16999912,-1.3621725,12.5,12.31,12.5,12.48,12.31,1569336340,70846,115806000,15,839800,0.7,0.056089744,12.260957,13.279782,-0.9697819,-0.07302694,0.002503885809025439,1.260180995475113,0.2441057394617766,857.7964787291654 27 | LBRT,80490,138964,-258835,312659,103312,86991,11978,-4610,-26498,-32771,1136,1628753,,,125110,,-,302221,,,526383,289418,40385,,-17145,60024,,106139,,-,-92847,126349,249033,135054,247961,433069,302221,28227,-25724,,,67771,49812,,,627053,,119274,99052,,385,1116501,351258,-8775,-255492,461221,219736,375687,1852915,-12803,2155136,,11.03,8,1212750,823335,11.01,13,6.893,1564531200,,,1.38,1.34,11.156286,-0.09128666,-0.008182531,23.51,-12.445001,-0.5293492,9.85,1.2149992,0.123350166,8.018116,1245078016,2,1.6052517,-0.015000343,-0.13538216,11.13,10.96,11.09,11.08,11.065,1569336420,121622,75144096,15,433069,0.2,0.018050542,8.2574625,14.072754,-3.0077543,-0.2137289,0.010956749017248142,0.8674991744964429,0.3118533074406157,577.7259606818317 28 | LTC,,-75351,,862712,2656,-449,-5682,,,1940,397,84,,17432,37555,,-90372,109550,,,166385,155076,,,-30196,,-670,645029,30615,7481,-22666,154356,155076,154981,107304,825490,109550,260371,-465,3724,9923,,,-37619,,1102751,,,19193,,,1513620,115535,-112375,-3609,119883,35620,680649,56919,48390,166469,-37619,50.59,8,226483,193767,50.52,8,19.603,1565308800,1573232400.0,1572883140.0,2.19,2.667,48.902573,1.712429,0.035017155,51.14,-0.5249977,-0.010265892,40.11,10.505001,0.26190478,23.111872,2012057728,2,2.5820026,0.36500168,0.7263715,50.6399,50.3317,50.49,50.25,50.615,1569336341,27194,39752200,15,825490,2.28,0.045373134,18.978252,46.445217,4.1697845,0.08977856,0.004874371732885224,0.8245393644986614,0.1877442488703679,12086.681171869837 29 | MATW,70044,-22371,-43200,129252,41572,-15943,-790,-2869,2516,-3284,-,1018230,1837,,76974,,-24637,135834,-2082,948894,584350,97993,-9118,443910,-37427,180451,-2776,929342,52872,363,53022,107371,107111,107371,331463,-524453,135834,60611,-,,59070,150764,273286,-164298,,252775,,1040378,416954,,31260,2375485,147574,901,-162336,616423,304143,1506771,1466746,-37841,1602580,-337613,34.79,8,229216,133231,34.66,8,26.894,1564704000,1574096400.0,1573660740.0,3.64,1.984,31.884857,3.1851425,0.09989515,50.6,-15.529999,-0.30691698,28.57,6.5,0.22751138,9.634615,1104301696,2,1.3040084,0.7750015,2.2598093,35.145,34.3,34.3,34.295,35.07,1569336071,63706,31488500,15,868351,0.8,0.023327017,17.67641,35.00949,0.060508728,0.0017283522,0.004231100242945838,1.735209609938838,0.12364930771082201,689.0774226559672 30 | MDC,50505,42975,-27166,1168442,414724,-44101,-4638,-316332,49325,-11342,566,2452184,37178,7662,21326,,-67718,283231,,-,613032,263854,53074,2133,,2132994,47380,1002967,,,4475,209521,210780,210780,52982,1567859,283231,45835,-3026,,30168,79872,,,,56034,,406992,329801,,,3001077,-7906,-56409,20214,2877334,422110,1425077,2781985,-19377,3065216,,42.76,8,578700,488134,42.73,10,26.826,1564576200,1572888600.0,1572453000.0,4.18,3.228,38.36714,4.3528595,0.11345279,44.01,-1.2899971,-0.029311456,25.14,17.580002,0.6992841,10.220097,2646700544,2,1.592485,0.3899994,0.92133087,42.81,42.5545,42.64,42.33,42.72,1569336450,86511,61954600,15,1576000,1.155,0.027285611,13.234201,33.881306,8.838696,0.26087233,0.007878898419165002,0.9042366751269035,0.13374365482233502,863.4629807491543 31 | MEI,91900,8800,-49800,150400,83200,-162900,1500,-3900,-23100,-16700,19200,726100,34300,32800,43300,,-16300,132600,-11500,233300,274200,103600,12000,264900,-8300,116700,,276900,,,238500,91600,91600,91600,234400,191500,132600,88100,-,,,19300,84200,-13600,,191900,,545200,125500,,,1231700,102000,217400,-470800,453500,180900,542000,867700,-29000,1000300,-25100,34.11,9,349683,240610,33.98,9,19.258,1567096200,1575916200.0,1575484200.0,3.69,2.55,30.797428,3.1525726,0.1023648,37.42,-3.4699974,-0.092731096,20.99,12.960001,0.61743695,9.200542,1259256448,2,1.7629038,-0.25,-0.73099416,34.7538,33.95,34.59,34.2,33.95,1569336357,19334,37091500,15,689700,0.44,0.012865497,13.313726,29.015217,4.934784,0.17007571,0.006486930968011539,0.7858489198202117,0.13281136726112802,1258.8787843646905 32 | MGRC,90844,-14339,-96949,,1508,-993,-15725,,20681,-9351,103801,265040,,,8836,,-30939,117520,-102,-,233290,104695,25289,7254,-12297,,,298564,,,-4870,79406,79406,79406,121016,536473,117520,901015,,,,,256373,-49,,126899,,467783,115770,,,1217316,142667,-39066,-104492,154340,90844,645781,380810,-12825,498330,-49,69.57,9,92283,95357,69.26,11,24.422,1564531200,1572624000.0,1572274740.0,3.78,3.54,65.83829,3.801712,0.05774318,70.88,-1.2399979,-0.017494326,45.85,23.79,0.5188659,18.42328,1688985856,2,2.8515272,0.80000305,1.1621195,69.9,69.17,69.17,68.84,69.64,1569336405,10615,24253100,15,571535,1.43,0.020772807,19.672316,61.897972,7.7420273,0.12507723,0.00393174480788023,1.129906304950703,0.13893462342638682,3389.291947103325 33 | MLI,103754,21583,-38481,276849,72616,-49425,-11342,27512,,-14193,-,2148200,,,39555,,-22705,174149,-1952,150335,359678,137772,30952,61971,-25199,329795,-1609,489597,58042,14904,29316,104459,106820,104459,273417,336050,174149,25950,-1318,1968,26790,25397,83891,-79792,,370633,,824737,145974,,7101,1369549,167892,-28269,-187096,702618,232801,806289,2333729,-36377,2507878,-554032,28.6,9,294066,247982,28.53,30,10.383,1563879600,1572021000.0,1571675400.0,1.88,1.6,27.242285,1.4377155,0.052775145,34.24,-5.5600014,-0.16238321,21.49,7.1900005,0.33457425,15.25532,1625189504,2,2.7622073,-0.22999954,-0.79557085,28.915,28.555,28.91,28.91,28.68,1569336445,25247,56666300,15,548356,0.4,0.013836043,17.925,28.982391,-0.30239105,-0.010433613,0.004376181257643432,1.4703750847989263,0.19049486100270627,648.0337177486305 34 | MOV,38650,-7818,-10635,201814,189911,-24952,-2640,-4234,6082,13739,342,309609,24503,,10965,,-18469,77297,-5801,136033,369958,61733,162,48183,-771,165311,,50280,,-,25296,61624,61571,61624,84026,312439,77297,81272,-689,,23166,13690,96362,80507,,26067,,431180,289761,332,,759701,86170,3688,-109009,468146,112683,259325,602270,-15564,679567,-136681,24.07,11,206383,217873,24.02,9,21.753,1567009800,1575657000.0,1575311400.0,2.36,2.804,23.934,0.035999298,0.001504107,44.39,-20.42,-0.46001354,18.78,5.1899986,0.27635774,10.15678,551302784,2,1.1019169,-0.060001373,-0.24969359,24.5,23.9,24.11,24.03,23.97,1569336450,32733,16411800,15,496655,0.8,0.03329172,8.548502,28.91855,-4.948551,-0.1711203,0.013275387221389488,0.5221431375904804,0.12407808237106241,811.2559674027726 35 | MTX,169100,42400,-75900,431900,208800,-3400,-3000,-14700,-11200,-65800,-,1346200,26300,,94300,,-7100,253700,-11200,812400,461400,207600,34400,214100,-48600,239200,-1600,910200,2700,31800,32700,169000,173200,169000,387300,327000,253700,78700,-2700,-900,5200,56200,409700,-233700,,1102900,22700,1769100,185000,3800,3300,3087100,203600,4200,-200000,876300,381900,1701800,1553900,-46100,1807600,-852400,53.36,8,242466,187684,53.17,8,39.669,1572555600,1572886800.0,1572447540.0,4.57,4.252,50.00086,3.2891426,0.06578172,68.86,-15.57,-0.22611095,45.26,8.030003,0.17741942,11.660831,1868486016,2,1.3433664,-0.16999817,-0.31799135,53.875,53.08,53.63,53.46,53.29,1569336433,16322,35062600,15,1353500,0.2,0.0037411149,12.532927,55.32116,-2.0311584,-0.03671576,0.00535282608819654,1.2573328407831548,0.12486147026228298,1033.6833458729807 36 | NWE,87043,-9584,-283966,1499070,7860,3282,19909,1617,-3805,-2233,539,272883,,437581,174476,,-109202,266272,,-,919126,178250,-18710,,-91988,50815,-,2102345,-,,-11556,196960,196960,196960,162373,1584796,266272,469587,-91,,56637,40876,1232725,-9934,171259,4521318,,548253,307119,,,5644376,381985,-73804,-304899,277685,347009,3701994,925737,-88022,1192009,-105480,75.48,8,371450,320782,75.39,9,39.849,1572480000,1563825600.0,1563393600.0,3.6,4.252,71.80315,3.7368546,0.052043047,75.61,-0.069999695,-0.0009257994,56.23,19.310001,0.34341103,20.983334,3810456832,2,1.8956562,0.7099991,0.94881606,75.61,74.93,75.03,74.83,75.54,1569336410,30680,50442900,15,1942382,2.25,0.030068154,17.765759,71.3129,4.227104,0.059275452,0.006359309238763037,1.9059041939227197,0.10140126916332627,3196.667837239484 37 | OXM,81612,11224,-37043,142976,8327,1984,-1560,-36518,,14026,16959,476124,,1000,42490,,-23054,95586,-231,66621,631342,88309,22018,176176,-2283,160656,,12993,,,-32816,66291,66291,66291,69037,235558,95586,22093,,,1800,14800,93697,-5095,-13976,192576,,323515,549732,,,727254,96377,-56765,-37397,269788,142209,248899,1011880,-7277,1107466,-5095,70.07,8,178633,197807,69.62,9,30.964,1568246400,1576515600.0,1575993540.0,4.58,4.154,69.832,0.07299805,0.001045338,93.52,-23.614998,-0.2525128,63.5,6.404999,0.100866124,15.263101,1189720192,2,2.2576218,-0.5149994,-0.7313255,71.135,69.62,70.81,70.42,69.905,1569336025,17958,17019100,15,478355,1.42,0.020164726,16.82836,73.82456,-3.9195633,-0.05309294,0.011622647496048558,0.5203227728360736,0.1385811792497204,1074.2724309369316 38 | PRIM,249217,8104,-110189,144048,151063,-19322,-46681,,5677,3004,-,2613741,1457,,79250,,-12343,143731,-,206159,325737,113358,25765,81198,-18746,,,308498,,2763,110241,77461,87593,77461,711225,316863,143731,6459,-15457,,25715,208221,56852,-908,,375884,,461075,182006,,62488,1594147,126815,63939,-209152,924447,621814,987164,2795747,-30373,2939478,-908,19.62,12,199866,178975,19.55,8,12.177,1565109000,1573237800.0,1572892200.0,2.14,1.655,19.684,-0.2140007,-0.01087181,25.37,-5.9000015,-0.23255819,17.8,1.6700001,0.09382024,9.09813,992621440,2,1.5989159,-0.25500107,-1.2927811,19.835,19.47,19.7,19.725,19.47,1569336374,24604,50982100,15,604220,0.24,0.0121673,11.76435,20.44058,-0.97058105,-0.04748305,0.00351054585825221,1.6337823971401144,0.12819999337989474,337.68629668260826 39 | RDN,708610,168572,-26008,2724733,95393,10758,-4599,,15419,-171184,231,228030,131643,,52167,,-2140,754210,,14092,1044976,684186,78175,113584,-61490,,-658032,894056,4155555,,74704,606011,606011,606011,143075,3378350,754210,279872,-1510,2590,72588,830757,100539,-60920,278337,37090,,1719541,,516704,218824,6314652,677786,22386,-689414,1714459,1831342,2825937,518796,-70024,1273006,-955790,23.61,9,1229533,1302871,23.6,8,18.419,1564617600,1572888600.0,1572366600.0,3.13,2.849,22.816572,0.8634281,0.037842147,24.62,-0.94000053,-0.038180362,14.78,8.900001,0.60216516,7.565495,4813693952,2,1.2856289,0.20000076,0.851792,23.685,23.47,23.48,23.48,23.68,1569336418,121899,203280992,15,3488715,0.01,0.00042589437,8.311688,22.654638,1.025362,0.04526058,0.0064092121313536286,0.8100223148064545,0.17370607802586338,3781.3599873056373 40 | RECN,21634,14098,-6896,460226,43045,-13425,-5690,,-1469,1058,631,446560,1497,800,8478,,-16158,49569,-568,190815,282439,47969,16499,14589,-2190,,-,43000,,,-20000,31470,31470,31470,135528,76992,49569,4677,-1860,,,11154,11558,-12588,,26632,,350230,224392,5981,,428370,43621,-43601,-12877,191657,91416,145974,679430,-1600,728999,-528691,17.85,9,185583,151264,17.83,9,8.94,1570060800,1570060800.0,1570060800.0,1.11,0.98,16.922,0.9379997,0.05543078,19.5,-1.6399994,-0.084102534,12.72,5.1400003,0.40408808,16.09009,568794560,2,1.997763,0.15999985,0.9039539,17.9,17.645,17.76,17.7,17.86,1569335719,9519,31847400,15,282396,0.52,0.02937853,18.22449,16.40652,1.4534798,0.08859159,0.0047496498929268955,0.5169124208558195,0.11143925551353419,780.2405215919364 41 | RGR,11675,1243,-10541,33291,38492,-24995,15051,8479,6189,2605,24123,360546,2969,,31972,,-19201,67730,,,135089,68714,17781,2464,-330,31420,-,,,,,50933,50933,50933,45031,261778,67730,18168,,,,11890,99,,,82711,,350423,67359,114326,,335532,119812,-20017,-124790,232189,71191,71290,427905,984,495635,-,41.22,10,163983,177118,41.08,8,15.776,1564617600,1572886800.0,1572361140.0,3.66,2.292,42.316,-1.2060013,-0.028499886,70.0,-28.89,-0.41271427,39.31,1.7999992,0.045789853,11.232241,718820672,2,2.6058571,-0.3899994,-0.9397576,41.7874,41.01,41.68,41.5,41.11,1569336428,26139,17485300,15,264242,0.92,0.022168675,17.9363,50.39732,-9.287319,-0.184282,0.010129537382830149,0.26979057076467783,0.19275134157325483,1450.3024846913556 42 | RMR,23316,125659,-648,99239,256848,148208,,,3142,-7036,-,,25726,,10376,,-16169,257138,-6,-,351827,276288,58862,375,,,,,7051,201899,,95477,217426,96041,28846,230528,257138,196468,-62452,,,5,41460,82,-53152,2589,,133410,146593,,,504428,228470,-79608,-648,296086,28307,69767,94689,19150,351827,82,45.29,9,116266,215623,45.1,9,17.579,1565362740,1502452800.0,1502103540.0,2.32,4.582,46.005142,-0.71014404,-0.015436188,95.12,-49.825005,-0.52381206,41.69,3.6049995,0.086471565,19.523706,1414852736,2,2.5766537,-0.46500015,-1.0161717,45.87,45.1,45.78,45.76,45.295,1569335819,5304,15236400,15,232762,1.4,0.030594407,9.885422,52.250652,-6.955654,-0.13312091,0.014151833766506524,0.29973535199044515,0.41261460203985184,4021.4444485499976 43 | SCHN,128495,-19353,-77626,36929,4723,-2564,-44941,-24280,26049,17833,26702,2010485,30333,,49672,346,-20736,145353,-701,168065,354230,141853,-17590,4358,-8983,205877,11,100491,11532,4032,-40976,156451,159443,156451,174086,493655,145353,56792,-5986,,41673,6682,73793,-37237,,415711,,639684,208877,,98,1104817,159676,-88141,-73398,448359,254677,434707,2219362,-3500,2364715,-37237,21.95,9,253033,211340,21.87,8,25.046,1561552200,1572280200.0,1571761800.0,1.85,3.698,23.500572,-1.5605717,-0.06640569,28.9,-6.959999,-0.24083042,20.94,1.0,0.04775549,11.85946,587522496,2,0.87598825,-0.42000008,-1.8783545,22.34,21.81,22.26,22.36,21.94,1569336247,22735,26578600,15,666078,0.75,0.033542037,5.932937,24.061087,-2.1210861,-0.08815421,0.007951509861317,0.652636778275217,0.23488390248589505,248.45382889692837 44 | SCS,241200,-13200,-81400,16400,261300,-20800,-66400,-24000,8500,18700,,2355300,135800,,81600,,-64300,187300,-2700,240800,1087900,163900,37900,119300,-37500,224800,,482900,56900,,197300,126000,126000,126000,401900,489700,187300,321300,-,15500,41100,40400,214500,-47300,-7500,455500,53700,880700,854400,,2700,2142400,131200,122300,-271600,948600,595200,1292600,3255900,-23400,3443200,-47300,19.15,13,1577616,676573,19.12,8,7.584,1568982600,1568982600.0,1568982600.0,1.46,1.14,16.082285,3.0527153,0.1898185,19.65,-0.5149994,-0.02620862,13.96,5.175,0.37070203,13.106164,2240938240,2,2.5230749,-0.0049991608,-0.02611892,19.36,18.9896,19.13,19.14,19.135,1569336438,420689,88820600,15,849800,0.56,0.029258098,16.785088,16.525,2.6100006,0.15794255,0.0076172982393723975,1.5210637797128737,0.14827018121911037,650.8301115241636 45 | SWM,65700,7300,-27000,71100,93800,-13100,-18300,-4900,8000,-1100,-,762800,300,,60000,-300,-53200,136700,-3800,338100,278500,105500,10700,281100,-28200,151500,,618800,51900,,-61100,93900,94800,94500,164700,-61300,136700,25900,-3600,2200,19200,1600,146300,-124500,,340300,15200,608200,126600,,3300,1466500,139100,-120900,-27500,429200,143500,908600,904600,-31200,1041300,-124500,38.37,22,191166,141051,38.29,11,18.534,1565222400,1573491600.0,1572969540.0,3.73,2.783,34.67057,3.719429,0.107279144,41.58,-3.1900024,-0.076719634,24.35,14.039999,0.5765913,10.292225,1186101248,2,2.0713284,0.48999786,1.2928703,38.47,38.09,38.17,37.9,38.39,1569336423,18374,30896100,15,557900,1.75,0.04617414,13.794466,34.494274,3.8957253,0.112938315,0.004565333488692747,1.6286072772898368,0.16938519447929737,1139.0581465475848 46 | SXI,72603,-20035,-34367,65515,93145,-16457,7647,18223,-6803,-16558,-,523519,14140,,30881,20725,-9826,82827,-1931,281503,268060,65613,18424,118660,-10760,88645,,197610,2452,,4800,67914,47189,67914,121211,64150,82827,37377,-,109412,,14694,118351,-137278,-,148024,,818282,184733,,1432,921889,73346,-38201,-49671,333873,141615,457576,708752,-17214,791579,-461460,73.69,10,83950,87925,73.14,11,37.643,1566864000,1572624000.0,1572274740.0,5.41,5.381,68.14114,5.2588577,0.07717595,109.77,-36.369995,-0.3313291,59.28,14.120003,0.23819168,13.567469,913081344,2,1.9498976,-0.4399948,-0.595876,74.4,73.4,73.81,73.84,73.4,1569335769,6407,12439800,15,464313,0.78,0.01056338,13.640588,70.334206,3.065796,0.043588974,0.007068039679094519,0.9854903911800875,0.14626771165140756,1153.49364245388 47 | UFPI,136901,3529,-95862,178540,27316,-618,-8512,-84304,-5213,1245,60884,3896286,2668,,61342,,-22072,200659,-464,224117,592894,197853,45441,48846,-8893,563165,-9660,202130,,15281,54261,145202,152412,148598,357580,800440,200659,24597,-4193,-66,32462,3245,46564,-5938,,354710,,839917,392235,14755,148,1647548,116685,4393,-121232,995278,310170,558864,4288521,-2806,4489180,-5938,39.93,8,301483,296714,39.86,8,19.023,1571934600,1571934600.0,1571934600.0,3.16,2.619,39.483143,0.51685715,0.013090578,42.8,-2.7999992,-0.065420546,24.14,15.860001,0.6570009,12.658228,2454668032,2,2.1027176,0.5400009,1.3684767,40.0,39.46,39.76,39.46,40.0,1569336450,59331,61366700,15,1073403,0.38,0.009630005,15.2730055,35.903114,4.0968857,0.11410948,0.004835097862521531,0.5206469517972281,0.13843635614955427,546.7965267598983 48 | VIVO,6260,3912,-4201,129193,59763,2691,-4447,-1142,4124,323,,83110,130,1239,7924,,-21170,44635,-298,54637,130461,30380,6531,23113,-1520,41993,,44930,1722,,-4500,23849,23849,23849,32336,97668,44635,2857,-,,,335,6856,-3377,,29995,16870,49602,68956,,5250,251377,34783,-27593,-4201,139053,24173,75959,168936,-14255,213571,-3377,9.99,14,291816,282990,9.97,11,4.419,1564531200,1510579800.0,1510147800.0,0.51,0.6,9.904,0.076999664,0.0077746026,19.84,-9.859,-0.4969254,8.96,1.0209999,0.113950886,19.57059,426303488,2,2.2586558,0.015999794,0.1605599,10.04,9.95,9.99,9.965,9.981,1569336357,70695,42711500,15,175418,0.25,0.025087807,16.634998,11.988841,-2.007841,-0.16747583,0.006625616051882982,0.4330171362117913,0.13595526114765874,1996.0738489776234 49 | VSH,218322,-158067,-229899,1436011,686032,-62000,-62433,-80182,-2277,43075,14422,2146165,,,161863,,-46509,469786,-14003,147480,888524,416776,70239,65688,-36680,479660,460705,494509,,2286,-510980,345758,346537,345758,397020,1169216,469786,140143,-16146,-2058,,184850,582913,-6791,,969001,,-61258,406931,78286,,3106198,258506,-575932,269429,1783886,644106,1721528,2564903,-53010,3034689,-6791,17.39,40,827900,1036740,17.37,14,10.315,1564504200,1572625800.0,1572280200.0,1.26,2.041,16.301144,1.0488567,0.06434252,22.94,-5.59,-0.24367917,14.36,2.9900007,0.20821732,13.769842,2521718528,2,1.6820166,-0.07999992,-0.45897835,17.75,17.35,17.48,17.43,17.35,1569336467,56810,132348000,15,1382384,0.35,0.02008032,8.500735,17.321087,0.028913498,0.0016692658,0.007833439115060296,1.2453327006099608,0.2501171888563525,830.9644012944984 50 | WDR,26253,116219,-2566,311264,231997,55570,-12855,,-18007,56444,-,720161,12321,,28278,,-81215,223248,,-,440140,238292,55480,38899,-6461,,12909,94854,,11463,-,183588,182812,183588,164427,737581,223248,29300,-2270,,69726,237549,16190,331,,63429,,1198445,191243,498913,,1344079,357015,-311788,10343,1105481,338122,449166,937053,15044,1160301,-627256,17.3,14,856300,828139,17.29,8,11.622,1572366600,1572366600.0,1572366600.0,1.61,2.06,16.512857,0.83714294,0.050696433,21.63,-4.279999,-0.19787328,15.09,2.2600002,0.14976807,10.776398,1250555008,2,1.4928584,0.17000008,0.9895232,17.405,17.23,17.25,17.18,17.35,1569336450,113116,73075296,15,883450,1.0,0.058207218,8.422331,17.132898,0.21710205,0.012671648,0.01133268074617173,0.5084226611579603,0.20780802535514178,1077.7849954451474 51 | WERN,97781,41986,-519872,107455,33930,18799,-33753,,7559,4068,-,1825767,,,230151,,-23013,214517,-374,,632147,223881,55733,,-2695,10060,,50000,,,28461,168148,168148,168148,379435,1264753,214517,139284,,17598,16614,30004,458391,-16073,104546,1487562,,1413746,82933,,75000,2083504,418159,-67612,-331374,456658,310360,818751,2243397,9364,2457914,-257253,34.3,10,478416,630028,34.22,12,14.919,1564099200,1571673600.0,1571237940.0,2.38,2.556,32.648285,1.6517143,0.050591152,36.53,-2.2299995,-0.061045706,27.27,7.029999,0.25779238,14.411764,2355236864,2,2.2990818,-0.049999237,-0.1455582,34.67,34.16,34.44,34.35,34.3,1569336468,51853,69195000,15,1264753,0.36,0.01048035,13.419405,32.498913,1.8010864,0.0554199,0.00910510875063227,0.6473603936895188,0.13294927942452003,958.225903754159 52 | WGO,81039,16768,-28668,86223,2342,-33603,-37739,-46429,-1278,20345,-,1716993,,,19177,,-,162569,,274370,299836,142640,40283,265717,-18246,195128,,291441,1959,,14532,102357,102357,102357,164585,-5642,162569,36628,-501,-2231,,57053,21734,892,,101193,,768816,127939,,,1051805,83346,-5188,-111761,371938,204185,517360,1854260,-19929,2016829,-346482,39.51,9,600766,415428,39.43,8,19.14,1560961800,1571661000.0,1571142600.0,3.98,3.46,34.652,4.673401,0.1348667,42.08,-2.7546005,-0.06546104,19.77,19.5554,0.98914516,9.880754,1251196672,2,2.0546188,-0.77459717,-1.9316638,40.45,39.29,39.84,40.1,39.3254,1569336467,67279,31618000,15,534445,0.42,0.010473816,11.365723,35.156303,4.169098,0.118587494,0.01313897147194636,0.9680322577627258,0.1915201751349531,620.3781639395309 53 | WIRE,36706,4290,-26181,60822,178405,55043,-6468,-10002,165,627,-,1098961,,,16513,,-1668,99510,,,189722,101684,23534,,-300,102367,,,,,,78150,78150,78150,236742,720456,99510,165,,,,,24032,,,298658,,750421,90212,,,818060,81590,-482,-26065,519237,73572,97604,1189173,4348,1288683,-,56.68,8,115183,76928,56.53,10,36.002,1564585200,1493668800.0,1493409600.0,3.99,3.819,55.162285,1.4877167,0.026969817,61.68,-5.029999,-0.08154991,40.96,15.690002,0.3830567,14.197995,1184302208,2,1.5735238,0.6100006,1.0885092,56.9431,56.37,56.37,56.04,56.65,1569336432,10060,20905600,15,720456,0.08,0.0014275517,14.833727,55.95884,0.6911621,0.012351259,0.0036797795805908464,0.1354753100813929,0.10847296712082348,919.0019640206319 54 | WNC,153113,24038,-34009,629039,132690,-58831,-39539,-18713,32653,3928,744,1983627,,,39183,,-17768,136023,,311084,283651,96004,26583,218228,-28759,184404,,503018,,,-82463,69421,69421,69421,190936,-55463,136023,18671,-476,3060,38076,75656,55136,-3343,,206991,,150244,128160,,1880,1304393,112471,-158129,-13173,549419,271676,830544,2131255,-40019,2267278,-306178,14.84,40,428333,441998,14.83,9,9.173,1564570800,1572652800.0,1572307200.0,1.51,1.098,14.346,0.47399998,0.03304057,18.9,-4.08,-0.21587302,12.25,2.5699997,0.20979589,9.814569,807046784,2,1.615611,-0.03999996,-0.26917875,14.925,14.82,14.86,14.86,14.82,1569336409,35681,54456600,15,473849,0.315,0.021197846,13.497267,14.618116,0.20188332,0.013810487,0.00811651847526287,1.7527609006244604,0.14650447716466639,355.95404886387996 55 | WOR,393517,47750,-84499,283177,92363,-29604,73346,-33649,-116875,-21770,,3279601,,,95602,,-52334,141563,,334607,479955,206456,43183,196059,-38063,484280,56693,598356,214930,117148,-1394,153455,163273,153455,510486,300580,141563,20623,-10726,,9276,15595,266026,-43464,,578664,,591533,338392,,150943,2510796,197859,-238938,11475,1165913,698020,1562402,3617993,64893,3759556,-43464,37.15,14,353800,263731,37.1,9,14.986,1569429000,1569429000.0,1569429000.0,4.16,2.61,36.426,0.6340027,0.017405225,46.66,-9.599998,-0.20574364,31.42,5.6400013,0.17950355,8.908654,2060372992,2,2.4729748,-0.5299988,-1.4099462,37.72,37.06,37.52,37.59,37.06,1569336369,29977,56153100,15,831246,0.92,0.024474595,14.199235,37.748623,-0.6886215,-0.018242296,0.004696641859487722,1.879590398028983,0.18460840713819976,548.036255345046 56 | WWW,202300,3800,-21700,201400,143100,-337900,-95000,-44500,40600,-37100,107600,1317900,3100,,31500,,-28600,246400,-8700,424400,921300,227400,27100,676400,-24500,317600,,437700,,-,-213700,192600,200300,200100,361200,-114800,246400,83700,-2700,-2700,,3800,280400,-88300,15300,130900,,1169700,659600,,7400,2183100,97500,-404500,-22200,867700,473100,1191500,1992800,-19000,2239200,-492700,28.21,8,860266,847806,28.16,8,9.896,1565173800,1573489800.0,1572953400.0,2.48,1.88,26.38943,1.7855701,0.06766233,39.77,-11.595001,-0.29155144,23.05,5.125,0.22234274,11.360887,2497905152,2,2.8471098,-0.20499992,-0.72233945,28.61,28.125,28.57,28.38,28.175,1569336434,43133,85252496,15,986000,0.36,0.012684991,14.986702,30.064566,-1.8895664,-0.06285028,0.009944647251149104,1.20841784989858,0.20294117647058824,1115.5346337977849 57 | -------------------------------------------------------------------------------- /data/spy/__init__.py: -------------------------------------------------------------------------------- 1 | from . import tickers 2 | -------------------------------------------------------------------------------- /data/spy/sp100.csv: -------------------------------------------------------------------------------- 1 | 0,AAPL 2 | 1,ABBV 3 | 2,ABT 4 | 3,ACN 5 | 4,ADBE 6 | 5,AGN 7 | 6,AIG 8 | 7,ALL 9 | 8,AMGN 10 | 9,AMZN 11 | 10,AXP 12 | 11,BA 13 | 12,BAC 14 | 13,BIIB 15 | 14,BK 16 | 15,BKNG 17 | 16,BLK 18 | 17,BMY 19 | 18,BRK-B 20 | 19,C 21 | 20,CAT 22 | 21,CHTR 23 | 22,CL 24 | 23,CMCSA 25 | 24,COF 26 | 25,COP 27 | 26,COST 28 | 27,CSCO 29 | 28,CVS 30 | 29,CVX 31 | 30,DD 32 | 31,DHR 33 | 32,DIS 34 | 33,DUK 35 | 34,EMR 36 | 35,EXC 37 | 36,F 38 | 37,FB 39 | 38,FDX 40 | 39,GD 41 | 40,GE 42 | 41,GILD 43 | 42,GM 44 | 43,GOOG 45 | 44,GOOGL 46 | 45,GS 47 | 46,HD 48 | 47,HON 49 | 48,IBM 50 | 49,INTC 51 | 50,JNJ 52 | 51,JPM 53 | 52,KHC 54 | 53,KMI 55 | 54,KO 56 | 55,LLY 57 | 56,LMT 58 | 57,LOW 59 | 58,MA 60 | 59,MCD 61 | 60,MDLZ 62 | 61,MDT 63 | 62,MET 64 | 63,MMM 65 | 64,MO 66 | 65,MRK 67 | 66,MS 68 | 67,MSFT 69 | 68,NEE 70 | 69,NFLX 71 | 70,NKE 72 | 71,NVDA 73 | 72,ORCL 74 | 73,OXY 75 | 74,PEP 76 | 75,PFE 77 | 76,PG 78 | 77,PM 79 | 78,PYPL 80 | 79,QCOM 81 | 80,RTN 82 | 81,SBUX 83 | 82,SLB 84 | 83,SO 85 | 84,SPG 86 | 85,T 87 | 86,TGT 88 | 87,TMO 89 | 88,TXN 90 | 89,UNH 91 | 90,UNP 92 | 91,UPS 93 | 92,USB 94 | 93,UTX 95 | 94,V 96 | 95,VZ 97 | 96,WBA 98 | 97,WFC 99 | 98,WMT 100 | 99,XOM 101 | -------------------------------------------------------------------------------- /data/spy/sp500-tech.csv: -------------------------------------------------------------------------------- 1 | 0,AAPL 2 | 1,ACN 3 | 2,ADBE 4 | 3,ADI 5 | 4,ADP 6 | 5,ADS 7 | 6,ADSK 8 | 7,AKAM 9 | 8,AMAT 10 | 9,AMD 11 | 10,ANET 12 | 11,ANSS 13 | 12,APH 14 | 13,AVGO 15 | 14,BR 16 | 15,CDNS 17 | 16,CDW 18 | 17,CRM 19 | 18,CSCO 20 | 19,CTSH 21 | 20,CTXS 22 | 21,DXC 23 | 22,FFIV 24 | 23,FIS 25 | 24,FISV 26 | 25,FLIR 27 | 26,FLT 28 | 27,FTNT 29 | 28,GLW 30 | 29,GPN 31 | 30,HPE 32 | 31,HPQ 33 | 32,IBM 34 | 33,INTC 35 | 34,INTU 36 | 35,IPGP 37 | 36,IT 38 | 37,JKHY 39 | 38,JNPR 40 | 39,KEYS 41 | 40,KLAC 42 | 41,LDOS 43 | 42,LRCX 44 | 43,MA 45 | 44,MCHP 46 | 45,MSFT 47 | 46,MSI 48 | 47,MU 49 | 48,MXIM 50 | 49,NOW 51 | 50,NTAP 52 | 51,NVDA 53 | 52,ORCL 54 | 53,PAYX 55 | 54,PYPL 56 | 55,QCOM 57 | 56,QRVO 58 | 57,SNPS 59 | 58,STX 60 | 59,SWKS 61 | 60,TEL 62 | 61,TXN 63 | 62,V 64 | 63,VRSN 65 | 64,WDC 66 | 65,WU 67 | 66,XLNX 68 | 67,XRX 69 | -------------------------------------------------------------------------------- /data/spy/spy.py: -------------------------------------------------------------------------------- 1 | from data.spy.tickers import SpyTickers 2 | 3 | if __name__ == '__main__': 4 | # get tickers from wikipedia 5 | spyTickers = SpyTickers() 6 | -------------------------------------------------------------------------------- /data/spy/tickers.csv: -------------------------------------------------------------------------------- 1 | 0,A 2 | 1,AAL 3 | 2,AAP 4 | 3,AAPL 5 | 4,ABBV 6 | 5,ABC 7 | 6,ABMD 8 | 7,ABT 9 | 8,ACN 10 | 9,ADBE 11 | 10,ADI 12 | 11,ADM 13 | 12,ADP 14 | 13,ADSK 15 | 14,AEE 16 | 15,AEP 17 | 16,AES 18 | 17,AFL 19 | 18,AIG 20 | 19,AIV 21 | 20,AIZ 22 | 21,AJG 23 | 22,AKAM 24 | 23,ALB 25 | 24,ALGN 26 | 25,ALK 27 | 26,ALL 28 | 27,ALLE 29 | 28,ALXN 30 | 29,AMAT 31 | 30,AMCR 32 | 31,AMD 33 | 32,AME 34 | 33,AMGN 35 | 34,AMP 36 | 35,AMT 37 | 36,AMZN 38 | 37,ANET 39 | 38,ANSS 40 | 39,ANTM 41 | 40,AON 42 | 41,AOS 43 | 42,APA 44 | 43,APD 45 | 44,APH 46 | 45,APTV 47 | 46,ARE 48 | 47,ATO 49 | 48,ATVI 50 | 49,AVB 51 | 50,AVGO 52 | 51,AVY 53 | 52,AWK 54 | 53,AXP 55 | 54,AZO 56 | 55,BA 57 | 56,BAC 58 | 57,BAX 59 | 58,BBY 60 | 59,BDX 61 | 60,BEN 62 | 61,BF-B 63 | 62,BIIB 64 | 63,BIO 65 | 64,BK 66 | 65,BKNG 67 | 66,BKR 68 | 67,BLK 69 | 68,BLL 70 | 69,BMY 71 | 70,BR 72 | 71,BRK-B 73 | 72,BSX 74 | 73,BWA 75 | 74,BXP 76 | 75,C 77 | 76,CAG 78 | 77,CAH 79 | 78,CARR 80 | 79,CAT 81 | 80,CB 82 | 81,CBOE 83 | 82,CBRE 84 | 83,CCI 85 | 84,CCL 86 | 85,CDNS 87 | 86,CDW 88 | 87,CE 89 | 88,CERN 90 | 89,CF 91 | 90,CFG 92 | 91,CHD 93 | 92,CHRW 94 | 93,CHTR 95 | 94,CI 96 | 95,CINF 97 | 96,CL 98 | 97,CLX 99 | 98,CMA 100 | 99,CMCSA 101 | 100,CME 102 | 101,CMG 103 | 102,CMI 104 | 103,CMS 105 | 104,CNC 106 | 105,CNP 107 | 106,COF 108 | 107,COG 109 | 108,COO 110 | 109,COP 111 | 110,COST 112 | 111,CPB 113 | 112,CPRT 114 | 113,CRM 115 | 114,CSCO 116 | 115,CSX 117 | 116,CTAS 118 | 117,CTLT 119 | 118,CTSH 120 | 119,CTVA 121 | 120,CTXS 122 | 121,CVS 123 | 122,CVX 124 | 123,CXO 125 | 124,D 126 | 125,DAL 127 | 126,DD 128 | 127,DE 129 | 128,DFS 130 | 129,DG 131 | 130,DGX 132 | 131,DHI 133 | 132,DHR 134 | 133,DIS 135 | 134,DISCA 136 | 135,DISCK 137 | 136,DISH 138 | 137,DLR 139 | 138,DLTR 140 | 139,DOV 141 | 140,DOW 142 | 141,DPZ 143 | 142,DRE 144 | 143,DRI 145 | 144,DTE 146 | 145,DUK 147 | 146,DVA 148 | 147,DVN 149 | 148,DXC 150 | 149,DXCM 151 | 150,EA 152 | 151,EBAY 153 | 152,ECL 154 | 153,ED 155 | 154,EFX 156 | 155,EIX 157 | 156,EL 158 | 157,EMN 159 | 158,EMR 160 | 159,EOG 161 | 160,EQIX 162 | 161,EQR 163 | 162,ES 164 | 163,ESS 165 | 164,ETN 166 | 165,ETR 167 | 166,ETSY 168 | 167,EVRG 169 | 168,EW 170 | 169,EXC 171 | 170,EXPD 172 | 171,EXPE 173 | 172,EXR 174 | 173,F 175 | 174,FANG 176 | 175,FAST 177 | 176,FB 178 | 177,FBHS 179 | 178,FCX 180 | 179,FDX 181 | 180,FE 182 | 181,FFIV 183 | 182,FIS 184 | 183,FISV 185 | 184,FITB 186 | 185,FLIR 187 | 186,FLS 188 | 187,FLT 189 | 188,FMC 190 | 189,FOX 191 | 190,FOXA 192 | 191,FRC 193 | 192,FRT 194 | 193,FTI 195 | 194,FTNT 196 | 195,FTV 197 | 196,GD 198 | 197,GE 199 | 198,GILD 200 | 199,GIS 201 | 200,GL 202 | 201,GLW 203 | 202,GM 204 | 203,GOOG 205 | 204,GOOGL 206 | 205,GPC 207 | 206,GPN 208 | 207,GPS 209 | 208,GRMN 210 | 209,GS 211 | 210,GWW 212 | 211,HAL 213 | 212,HAS 214 | 213,HBAN 215 | 214,HBI 216 | 215,HCA 217 | 216,HD 218 | 217,HES 219 | 218,HFC 220 | 219,HIG 221 | 220,HII 222 | 221,HLT 223 | 222,HOLX 224 | 223,HON 225 | 224,HPE 226 | 225,HPQ 227 | 226,HRL 228 | 227,HSIC 229 | 228,HST 230 | 229,HSY 231 | 230,HUM 232 | 231,HWM 233 | 232,IBM 234 | 233,ICE 235 | 234,IDXX 236 | 235,IEX 237 | 236,IFF 238 | 237,ILMN 239 | 238,INCY 240 | 239,INFO 241 | 240,INTC 242 | 241,INTU 243 | 242,IP 244 | 243,IPG 245 | 244,IPGP 246 | 245,IQV 247 | 246,IR 248 | 247,IRM 249 | 248,ISRG 250 | 249,IT 251 | 250,ITW 252 | 251,IVZ 253 | 252,J 254 | 253,JBHT 255 | 254,JCI 256 | 255,JKHY 257 | 256,JNJ 258 | 257,JNPR 259 | 258,JPM 260 | 259,K 261 | 260,KEY 262 | 261,KEYS 263 | 262,KHC 264 | 263,KIM 265 | 264,KLAC 266 | 265,KMB 267 | 266,KMI 268 | 267,KMX 269 | 268,KO 270 | 269,KR 271 | 270,KSU 272 | 271,L 273 | 272,LB 274 | 273,LDOS 275 | 274,LEG 276 | 275,LEN 277 | 276,LH 278 | 277,LHX 279 | 278,LIN 280 | 279,LKQ 281 | 280,LLY 282 | 281,LMT 283 | 282,LNC 284 | 283,LNT 285 | 284,LOW 286 | 285,LRCX 287 | 286,LUMN 288 | 287,LUV 289 | 288,LVS 290 | 289,LW 291 | 290,LYB 292 | 291,LYV 293 | 292,MA 294 | 293,MAA 295 | 294,MAR 296 | 295,MAS 297 | 296,MCD 298 | 297,MCHP 299 | 298,MCK 300 | 299,MCO 301 | 300,MDLZ 302 | 301,MDT 303 | 302,MET 304 | 303,MGM 305 | 304,MHK 306 | 305,MKC 307 | 306,MKTX 308 | 307,MLM 309 | 308,MMC 310 | 309,MNST 311 | 310,MO 312 | 311,MOS 313 | 312,MPC 314 | 313,MRK 315 | 314,MRO 316 | 315,MS 317 | 316,MSCI 318 | 317,MSFT 319 | 318,MSI 320 | 319,MTB 321 | 320,MTD 322 | 321,MU 323 | 322,MXIM 324 | 323,NCLH 325 | 324,NDAQ 326 | 325,NEE 327 | 326,NEM 328 | 327,NFLX 329 | 328,NI 330 | 329,NKE 331 | 330,NLOK 332 | 331,NLSN 333 | 332,NOC 334 | 333,NOV 335 | 334,NOW 336 | 335,NRG 337 | 336,NSC 338 | 337,NTAP 339 | 338,NTRS 340 | 339,NUE 341 | 340,NVDA 342 | 341,NVR 343 | 342,NWL 344 | 343,NWS 345 | 344,NWSA 346 | 345,O 347 | 346,ODFL 348 | 347,OKE 349 | 348,OMC 350 | 349,ORCL 351 | 350,ORLY 352 | 351,OTIS 353 | 352,OXY 354 | 353,PAYC 355 | 354,PAYX 356 | 355,PBCT 357 | 356,PCAR 358 | 357,PEAK 359 | 358,PEG 360 | 359,PEP 361 | 360,PFE 362 | 361,PFG 363 | 362,PG 364 | 363,PGR 365 | 364,PH 366 | 365,PHM 367 | 366,PKG 368 | 367,PKI 369 | 368,PLD 370 | 369,PM 371 | 370,PNC 372 | 371,PNR 373 | 372,PNW 374 | 373,POOL 375 | 374,PPG 376 | 375,PPL 377 | 376,PRGO 378 | 377,PRU 379 | 378,PSA 380 | 379,PSX 381 | 380,PVH 382 | 381,PWR 383 | 382,PXD 384 | 383,PYPL 385 | 384,QCOM 386 | 385,QRVO 387 | 386,RCL 388 | 387,RE 389 | 388,REG 390 | 389,REGN 391 | 390,RF 392 | 391,RHI 393 | 392,RJF 394 | 393,RL 395 | 394,RMD 396 | 395,ROK 397 | 396,ROL 398 | 397,ROP 399 | 398,ROST 400 | 399,RSG 401 | 400,RTX 402 | 401,SBAC 403 | 402,SBUX 404 | 403,SCHW 405 | 404,SEE 406 | 405,SHW 407 | 406,SIVB 408 | 407,SJM 409 | 408,SLB 410 | 409,SLG 411 | 410,SNA 412 | 411,SNPS 413 | 412,SO 414 | 413,SPG 415 | 414,SPGI 416 | 415,SRE 417 | 416,STE 418 | 417,STT 419 | 418,STX 420 | 419,STZ 421 | 420,SWK 422 | 421,SWKS 423 | 422,SYF 424 | 423,SYK 425 | 424,SYY 426 | 425,T 427 | 426,TAP 428 | 427,TDG 429 | 428,TDY 430 | 429,TEL 431 | 430,TER 432 | 431,TFC 433 | 432,TFX 434 | 433,TGT 435 | 434,TIF 436 | 435,TJX 437 | 436,TMO 438 | 437,TMUS 439 | 438,TPR 440 | 439,TROW 441 | 440,TRV 442 | 441,TSCO 443 | 442,TSN 444 | 443,TT 445 | 444,TTWO 446 | 445,TWTR 447 | 446,TXN 448 | 447,TXT 449 | 448,TYL 450 | 449,UA 451 | 450,UAA 452 | 451,UAL 453 | 452,UDR 454 | 453,UHS 455 | 454,ULTA 456 | 455,UNH 457 | 456,UNM 458 | 457,UNP 459 | 458,UPS 460 | 459,URI 461 | 460,USB 462 | 461,V 463 | 462,VAR 464 | 463,VFC 465 | 464,VIAC 466 | 465,VLO 467 | 466,VMC 468 | 467,VNO 469 | 468,VNT 470 | 469,VRSK 471 | 470,VRSN 472 | 471,VRTX 473 | 472,VTR 474 | 473,VTRS 475 | 474,VZ 476 | 475,WAB 477 | 476,WAT 478 | 477,WBA 479 | 478,WDC 480 | 479,WEC 481 | 480,WELL 482 | 481,WFC 483 | 482,WHR 484 | 483,WLTW 485 | 484,WM 486 | 485,WMB 487 | 486,WMT 488 | 487,WRB 489 | 488,WRK 490 | 489,WST 491 | 490,WU 492 | 491,WY 493 | 492,WYNN 494 | 493,XEL 495 | 494,XLNX 496 | 495,XOM 497 | 496,XRAY 498 | 497,XRX 499 | 498,XYL 500 | 499,YUM 501 | 500,ZBH 502 | 501,ZBRA 503 | 502,ZION 504 | 503,ZTS 505 | -------------------------------------------------------------------------------- /data/spy/tickers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import pandas_datareader.data as web 4 | 5 | from datetime import datetime 6 | 7 | 8 | class SpyTickers: 9 | def __init__(self): 10 | self.tickers = self.download() 11 | 12 | def download(self): 13 | print('Downloading S&P 500 members...') 14 | self.ticker_csv_path = os.path.join(os.path.dirname(__file__), 'tickers.csv') 15 | try: 16 | tickers = pd.read_csv(self.ticker_csv_path, header=None)[1] 17 | print('tickers.csv found. Nothing downloaded.') 18 | except FileNotFoundError: 19 | print('No tickers.csv file...') 20 | data = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies') 21 | table = data[0] 22 | tickers = table.iloc[1:, 0].tolist() 23 | tickers = pd.Series([t.replace('.', '-') for t in tickers]).sort_values(ignore_index=True) 24 | tickers.to_csv(self.ticker_csv_path, header=False) 25 | print("Tickers downloaded and saved.") 26 | return tickers 27 | -------------------------------------------------------------------------------- /jupyter/spy_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 148, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "import pandas_datareader.data as web\n", 11 | "import numpy as np\n", 12 | "from matplotlib import pyplot as plt" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 36, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "tickers = pd.read_csv('data/spy/tickers.csv', header=None)[1].tolist()" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 179, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "# calculate cumulative product of the mean of all daily returns\n", 31 | "# i.e. simulate growth of $1 by equally weighting all current S&P 500\n", 32 | "# constituents\n", 33 | "sim_rsp = pd.concat(\n", 34 | " [pd.read_csv(f\"data/price/{ticker}.csv\", index_col='Date', parse_dates=True)[\n", 35 | " 'Close'\n", 36 | " ].rename(ticker)\n", 37 | " for ticker in tickers],\n", 38 | " axis=1,\n", 39 | " sort=True,\n", 40 | " )\n", 41 | "\n", 42 | "sim_rsp_shift = sim_rsp.shift(1, axis=0)\n", 43 | "sim_rsp_pct = (sim_rsp - sim_rsp_shift) / sim_rsp_shift\n", 44 | "sim_rsp_pct = sim_rsp_pct.replace([np.inf, -np.inf], np.nan)\n", 45 | "\n", 46 | "df_sim_rsp = sim_rsp_pct.mean(axis=1, skipna=True) + 1\n", 47 | "df_sim_rsp = df_sim_rsp['2003-05-01':].cumprod().rename(\"SIM\")" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 191, 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "text/plain": [ 58 | "Date\n", 59 | "2019-07-11 9.572254\n", 60 | "2019-07-12 9.642153\n", 61 | "2019-07-15 9.632375\n", 62 | "2019-07-16 9.617813\n", 63 | "2019-07-17 9.580047\n", 64 | "Name: SIM, dtype: float64" 65 | ] 66 | }, 67 | "execution_count": 191, 68 | "metadata": {}, 69 | "output_type": "execute_result" 70 | } 71 | ], 72 | "source": [ 73 | "df_sim_rsp.tail()" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 174, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# actual RSP data\n", 83 | "rsp = (\n", 84 | " (pd.read_csv(f\"data/price/rsp.csv\", index_col='Date', parse_dates=True)[\"Adj Close\"]\n", 85 | ".pct_change() + 0.002 / 252 + 1) # 0.20% annual ER\n", 86 | ".cumprod()\n", 87 | ".rename(\"RSP\")\n", 88 | ")" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 185, 94 | "metadata": {}, 95 | "outputs": [ 96 | { 97 | "data": { 98 | "text/plain": [ 99 | "Date\n", 100 | "2019-07-23 5.613428\n", 101 | "2019-07-24 5.650912\n", 102 | "2019-07-25 5.614543\n", 103 | "2019-07-26 5.642796\n", 104 | "2019-07-29 5.640789\n", 105 | "Name: RSP, dtype: float64" 106 | ] 107 | }, 108 | "execution_count": 185, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | } 112 | ], 113 | "source": [ 114 | "rsp.tail()" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 190, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "data": { 124 | "image/png": "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\n", 125 | "text/plain": [ 126 | "
" 127 | ] 128 | }, 129 | "metadata": { 130 | "needs_background": "light" 131 | }, 132 | "output_type": "display_data" 133 | } 134 | ], 135 | "source": [ 136 | "plt.title('SPY vs. Survivorship-Biased Strategy')\n", 137 | "plt.plot(df_sim_rsp, label='SIM')\n", 138 | "plt.plot(rsp, label='RSP')\n", 139 | "plt.legend()\n", 140 | "plt.tight_layout()\n", 141 | "plt.show()" 142 | ] 143 | } 144 | ], 145 | "metadata": { 146 | "kernelspec": { 147 | "display_name": "Python 3", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.7.0" 162 | } 163 | }, 164 | "nbformat": 4, 165 | "nbformat_minor": 2 166 | } 167 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samwindham/algo-trader/a1e335d87050f3e42151659416c7d1f170d650bc/test/__init__.py -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from data.info.test_info import TestInfo 4 | 5 | def suite(): 6 | test_suite = unittest.TestSuite() 7 | 8 | # data/info 9 | print('==> data.info') 10 | test_suite.addTest(TestInfo('test_load_info')) 11 | test_suite.addTest(TestInfo('test_balance')) 12 | test_suite.addTest(TestInfo('test_cashflow')) 13 | test_suite.addTest(TestInfo('test_income')) 14 | test_suite.addTest(TestInfo('test_all_info')) 15 | 16 | return test_suite 17 | 18 | if __name__ == '__main__': 19 | runner = unittest.TextTestRunner(verbosity=2) 20 | runner.run(suite()) 21 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | from . import log, fin_calc 2 | -------------------------------------------------------------------------------- /tools/download_info.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | import pandas as pd 4 | from api import yahoo 5 | from data.info import info 6 | 7 | 8 | async def download_info(ticker): 9 | loop = asyncio.get_event_loop() 10 | return await loop.run_in_executor(None, yahoo.get_info, ticker) 11 | 12 | 13 | def download_all_info(tickers): 14 | # create new event loop 15 | event_loop = asyncio.new_event_loop() 16 | 17 | # create all co-routines and tasks 18 | coroutines = [download_info(t) for t in tickers] 19 | tasks = [event_loop.create_task(c) for c in coroutines] 20 | 21 | # run event loop concurrently 22 | print("Tasks created, running event loop:") 23 | event_loop.run_until_complete(asyncio.wait(tasks)) 24 | 25 | # get results 26 | info_dict = {ticker: task.result() for (ticker, task) in zip(tickers, tasks)} 27 | event_loop.close() 28 | 29 | return info_dict 30 | 31 | 32 | def save_info(info_dict): 33 | info.save(info_dict) 34 | 35 | 36 | if __name__ == '__main__': 37 | ticker_csv_path = os.path.join(os.path.dirname(__file__), '../data/spy/tickers.csv') 38 | tickers = pd.read_csv(ticker_csv_path, header=None)[1] 39 | 40 | print('---- DOWNLOADING ----') 41 | info_dict = download_all_info(tickers) 42 | print('---- SAVING ----') 43 | save_info(info_dict) 44 | 45 | print('Done.') 46 | -------------------------------------------------------------------------------- /tools/download_prices.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from datetime import date 4 | import pandas as pd 5 | 6 | from api import yahoo 7 | 8 | 9 | def save_ticker(ticker): 10 | historical = yahoo.get_daily(ticker) 11 | historical.to_csv(os.path.join(os.path.dirname(__file__), '../data/price/{ticker}.csv'.format(ticker=ticker))) 12 | 13 | 14 | def save_all(tickers): 15 | group_size = 10 16 | for i in range(0, len(tickers), group_size): 17 | ticker_group = list(tickers)[i: i + group_size] 18 | print(ticker_group) 19 | historical = yahoo.get_daily_async(ticker_group) 20 | for ticker in ticker_group: 21 | first_valid = historical[ticker][historical[ticker].notnull().any(axis=1)].index[0] 22 | historical[ticker].loc[first_valid:].to_csv(os.path.join( 23 | os.path.dirname(__file__), 24 | '../data/price/{ticker}.csv'.format(ticker=ticker))) 25 | 26 | 27 | if __name__ == '__main__': 28 | PARSER = argparse.ArgumentParser() 29 | PARSER.add_argument('-t', '--ticker', nargs='+') 30 | ARGS = PARSER.parse_args() 31 | 32 | if ARGS.ticker: 33 | if len(ARGS.ticker) > 1: 34 | save_all(ARGS.ticker) 35 | else: 36 | save_ticker(ARGS.ticker[0]) 37 | else: 38 | TICKER_CSV_PATH = os.path.join(os.path.dirname(__file__), '../data/spy/tickers.csv') 39 | TICKERS = pd.read_csv(TICKER_CSV_PATH, header=None)[1] 40 | 41 | save_all(TICKERS) 42 | save_ticker('SPY') 43 | save_ticker('RSP') 44 | -------------------------------------------------------------------------------- /tools/fin_calc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import scipy.stats as stats 4 | from datetime import datetime, timedelta 5 | 6 | 7 | def log_returns(data): 8 | return np.log(data).diff().iloc[1:] 9 | 10 | 11 | def calc_beta(x_name, window, returns_data): 12 | window_inv = 1.0 / window 13 | 14 | x_sum = returns_data[x_name].rolling(window, min_periods=window).sum() 15 | y_sum = returns_data.rolling(window, min_periods=window).sum() 16 | 17 | xy_sum = returns_data.mul(returns_data[x_name], axis=0).rolling(window, min_periods=window).sum() 18 | xx_sum = np.square(returns_data[x_name]).rolling(window, min_periods=window).sum() 19 | 20 | xy_cov = xy_sum - window_inv * y_sum.mul(x_sum, axis=0) 21 | x_var = xx_sum - window_inv * np.square(x_sum) 22 | 23 | betas = xy_cov.divide(x_var, axis=0)[window - 1:] 24 | betas.columns.name = None 25 | return betas 26 | 27 | 28 | # alpha = return - risk_free - beta * (market - risk_free) 29 | def calc_alpha(returns, market_returns, risk_free_returns, beta): 30 | returns_over_risk_free = returns.subtract(risk_free_returns, axis=0) 31 | market_over_risk_free = market_returns - risk_free_returns 32 | beta_market_risk_free = beta.multiply(market_over_risk_free, axis=0) 33 | 34 | alpha = returns_over_risk_free - beta_market_risk_free.values 35 | return alpha 36 | 37 | 38 | def get_ndays_return(daily_returns, ndays=22): 39 | ndays_returns = (1 + daily_returns).rolling(ndays, min_periods=ndays).apply(np.prod, raw=True) - 1 40 | return ndays_returns.iloc[ndays-1:] 41 | 42 | 43 | def top_alpha(stocks, market, risk_free, window, top_n_count=0): 44 | assert(stocks.shape[0] == market.shape[0] and market.shape[0] == risk_free.shape[0] 45 | ), 'inputs do not have same shape: {} {} {}'.format(stocks.shape[0], market.shape[0], risk_free.shape[0]) 46 | 47 | # calculate betas for all stocks 48 | market_name = 'market_returns' 49 | market = market.rename(market_name) 50 | returns_data = pd.concat([market, stocks], axis=1) 51 | betas = calc_beta(market_name, window, returns_data).drop(market_name, axis=1) 52 | 53 | # calculate n-day returns for each date 54 | stocks_nday_returns = get_ndays_return(stocks, window) 55 | market_nday_returns = get_ndays_return(market, window) 56 | risk_free_nday_returns = get_ndays_return(risk_free, window) 57 | 58 | # calculate alpha 59 | alpha = calc_alpha(stocks_nday_returns, market_nday_returns, risk_free_nday_returns, betas) 60 | 61 | return alpha.iloc[-1].nlargest(top_n_count) if top_n_count > 0 else alpha.sort(ascending=False) 62 | 63 | 64 | def var(returns, confidence): 65 | return returns.quantile(confidence, interpolation='higher') 66 | 67 | 68 | def cvar(returns, value_at_risk): 69 | return returns[returns.lt(value_at_risk)].mean() 70 | 71 | 72 | if __name__ == '__main__': 73 | # do nothing 74 | print('fin_calc imported') 75 | -------------------------------------------------------------------------------- /tools/hurst.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | 4 | import pandas as pd 5 | import numpy as np 6 | 7 | import scipy 8 | from scipy import stats 9 | 10 | import statsmodels 11 | from statsmodels.tsa.stattools import adfuller 12 | 13 | from matplotlib import pyplot as plt 14 | 15 | 16 | from tools import fin_calc 17 | 18 | 19 | def hurst_exp(d): 20 | lags = range(2, 100) 21 | 22 | # Calculate the array of the variances of the lagged differences 23 | tau = [np.sqrt(np.std(d.diff(lag))) for lag in lags] 24 | 25 | # Use a linear fit to estimate the Hurst Exponent 26 | poly = np.polyfit(np.log(lags), np.log(tau), 1) 27 | 28 | # Return the Hurst exponent from the polyfit output 29 | return poly[0]*2.0 30 | 31 | 32 | def variance_ratio(ts, lag=2): 33 | """ 34 | Returns the variance ratio test result 35 | """ 36 | # make sure we are working with an array, convert if necessary 37 | ts = np.asarray(ts) 38 | 39 | # Apply the formula to calculate the test 40 | n = len(ts) 41 | mu = sum(ts[1:n] - ts[:n-1]) / n 42 | m = (n - lag + 1) * (1 - lag / n) 43 | b = sum(np.square(ts[1:n] - ts[:n-1] - mu)) / (n - 1) 44 | t = sum(np.square(ts[lag:n] - ts[:n - lag] - lag * mu)) / m 45 | return t / (lag * b) 46 | 47 | 48 | def main(tickers): 49 | df = load_data(tickers[0], tickers[1]).iloc[-1000:] 50 | 51 | a = df[tickers[0]] 52 | b = df[tickers[1]] 53 | 54 | # a_returns = fin_calc.log_returns(a) 55 | # b_returns = fin_calc.log_returns(b) 56 | 57 | series = a - b 58 | 59 | h = hurst_exp(np.log(series)) 60 | print('Hurst:\t\t', h) 61 | 62 | vr = variance_ratio(np.log(series), 2) 63 | print('Var Ratio:\t', vr) 64 | 65 | ylag = np.roll(series, 1) 66 | ylag[0] = 0 67 | ydelta = series.diff(1) 68 | ydelta[0] = 0 69 | 70 | beta, _ = np.polyfit(ydelta, ylag, 1) 71 | halflife = -np.log(2) / beta 72 | print('Half Life:\t', halflife) 73 | 74 | # start = 0 75 | # end = len(df) 76 | 77 | # mean = stationary.iloc[start:end].mean() 78 | 79 | # plt.plot(list(df.index)[start:end], (stationary - mean).iloc[start:end]) 80 | # plt.plot(list(df.index)[start:end], (df.ziv + df.vxx).iloc[start:end]) 81 | # plt.plot([0 for x in range(end-start)]) 82 | 83 | # plt.tight_layout() 84 | # plt.show() 85 | 86 | # adf, pval, lag, n, crit, icbest = adfuller(stationary - mean, maxlag=1) 87 | # print('adf:\t', adf) 88 | # print('pval:\t', pval) 89 | # print('lag:\t', lag) 90 | # print('crit:') 91 | # print(crit) 92 | 93 | 94 | def load_data(ticker_a, ticker_b): 95 | a_path = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker_a}.csv') 96 | a = pd.read_csv(a_path, index_col=0)['Adj Close'].rename(ticker_a) 97 | 98 | b_path = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker_b}.csv') 99 | b = pd.read_csv(b_path, index_col=0)['Adj Close'].rename(ticker_b) 100 | 101 | df = pd.DataFrame({ 102 | ticker_a: a, 103 | ticker_b: b 104 | }).dropna() 105 | 106 | return df 107 | 108 | 109 | if __name__ == '__main__': 110 | PARSER = argparse.ArgumentParser() 111 | PARSER.add_argument('tickers', nargs=2) 112 | ARGS = PARSER.parse_args() 113 | 114 | main(ARGS.tickers) 115 | -------------------------------------------------------------------------------- /tools/log/__init__.py: -------------------------------------------------------------------------------- 1 | from . import log 2 | -------------------------------------------------------------------------------- /tools/log/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | LOG_FILE_PATH = os.path.join(os.path.dirname(__file__), './log') 5 | 6 | 7 | def log(log_type, message): 8 | time = datetime.now() 9 | out = '{} -- {}: {}\n'.format(time, log_type, message) 10 | 11 | with open(LOG_FILE_PATH, 'a') as f: 12 | f.write(out) 13 | 14 | 15 | def last(): 16 | with open(LOG_FILE_PATH, 'rb') as f: 17 | f.seek(-2, os.SEEK_END) 18 | while f.read(1) != b'\n': 19 | f.seek(-2, os.SEEK_CUR) 20 | last_line = f.readline().decode() 21 | print(last_line) 22 | 23 | 24 | def tail(count=5): 25 | with open(LOG_FILE_PATH, 'r') as f: 26 | total_lines_wanted = count 27 | 28 | BLOCK_SIZE = 1024 29 | f.seek(0, 2) 30 | block_end_byte = f.tell() 31 | lines_to_go = total_lines_wanted 32 | block_number = -1 33 | blocks = [] # blocks of size BLOCK_SIZE, in reverse order starting 34 | # from the end of the file 35 | while lines_to_go > 0 and block_end_byte > 0: 36 | if (block_end_byte - BLOCK_SIZE > 0): 37 | # read the last block we haven't yet read 38 | f.seek(block_number*BLOCK_SIZE, 2) 39 | blocks.append(f.read(BLOCK_SIZE)) 40 | else: 41 | # file too small, start from begining 42 | f.seek(0, 0) 43 | # only read what was not read 44 | blocks.append(f.read(block_end_byte)) 45 | lines_found = blocks[-1].count('\n') 46 | lines_to_go -= lines_found 47 | block_end_byte -= BLOCK_SIZE 48 | block_number -= 1 49 | all_read_text = ''.join(reversed(blocks)) 50 | for line in all_read_text.splitlines()[-total_lines_wanted:]: 51 | print(line) 52 | 53 | 54 | if __name__ == '__main__': 55 | # do nothing 56 | print("log imported") 57 | -------------------------------------------------------------------------------- /tools/markowitz.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | 4 | import pandas as pd 5 | 6 | from pypfopt import expected_returns 7 | from pypfopt import risk_models 8 | from pypfopt.efficient_frontier import EfficientFrontier 9 | from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices 10 | 11 | 12 | def optimize(tickers, cash=1000, longshort=False): 13 | print(f'Cash: ${cash}') 14 | date_start = 20 * 6 15 | 16 | df = pd.DataFrame() 17 | for t in tickers: 18 | path = os.path.join(os.path.dirname(__file__), f'../data/price/{t}.csv') 19 | price = pd.read_csv(path, parse_dates=True, index_col='Date')['Adj Close'].rename(t) 20 | df[t] = price[-date_start:] 21 | 22 | mu = expected_returns.mean_historical_return(df) 23 | S = risk_models.sample_cov(df) 24 | 25 | # Optimise for maximal Sharpe ratio 26 | ef = EfficientFrontier(mu, S, weight_bounds=((-1, 1) if longshort else (0, 1))) 27 | raw_weights = ef.max_sharpe() 28 | clean_weights = ef.clean_weights() 29 | 30 | latest_prices = get_latest_prices(df) 31 | da = DiscreteAllocation(raw_weights, latest_prices, total_portfolio_value=cash) 32 | allocation, leftover = da.lp_portfolio() 33 | 34 | print('\nWeights:', clean_weights) 35 | print('\nShares:', allocation) 36 | print(f'\n${leftover:.2f} leftover') 37 | 38 | ef.portfolio_performance(verbose=True) 39 | 40 | 41 | if __name__ == '__main__': 42 | PARSER = argparse.ArgumentParser() 43 | PARSER.add_argument('-t', '--ticker', nargs='+') 44 | PARSER.add_argument('--cash', nargs=1, type=int) 45 | PARSER.add_argument('-ls', '--longshort', action="store_true") 46 | ARGS = PARSER.parse_args() 47 | 48 | CASH = ARGS.cash or [1000] 49 | 50 | if ARGS.ticker: 51 | optimize(ARGS.ticker, CASH[0], ARGS.longshort) 52 | else: 53 | TICKERS = ['TLT', 'FB', 'AAPL', 'AMZN', 'NFLX', 'GOOG'] 54 | 55 | optimize(TICKERS, CASH[0], ARGS.longshort) 56 | -------------------------------------------------------------------------------- /tools/plot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from datetime import datetime 4 | 5 | import numpy as np 6 | import pandas as pd 7 | from pandas.plotting import register_matplotlib_converters 8 | from matplotlib import pyplot as plt 9 | 10 | 11 | def plot(data, plot_returns=False): 12 | if plot_returns: 13 | data = _log_returns(data) 14 | 15 | plt.clf() 16 | for y in data: 17 | print(y.name) 18 | plt.plot(y, label=y.name) 19 | 20 | plt.legend() 21 | plt.tight_layout() 22 | plt.show() 23 | 24 | 25 | def _log_returns(data): 26 | log_d = [] 27 | for d in data: 28 | r = np.log(d).diff() 29 | r.iloc[0] = 0.0 30 | r = np.cumprod(r + 1) - 1 31 | log_d.append(r) 32 | return log_d 33 | 34 | 35 | if __name__ == '__main__': 36 | register_matplotlib_converters() # Suppress Pandas warning 37 | 38 | PARSER = argparse.ArgumentParser() 39 | PARSER.add_argument('tickers', nargs='+') 40 | PARSER.add_argument('-r', '--returns', action="store_true") 41 | PARSER.add_argument('-s', '--start', nargs=1, type=int) 42 | PARSER.add_argument('-e', '--end', nargs=1, type=int) 43 | ARGS = PARSER.parse_args() 44 | 45 | TICKERS = ARGS.tickers 46 | 47 | START = ARGS.start or [1900] 48 | END = ARGS.end or [2100] 49 | START_DATE = datetime(START[0], 1, 1) 50 | END_DATE = datetime(END[0], 1, 1) 51 | 52 | DATA = [] 53 | for ticker in TICKERS: 54 | datapath = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker}.csv') 55 | ticker_data = pd.read_csv(datapath, index_col='Date', parse_dates=True)['Adj Close'].rename(ticker) 56 | DATA.append(ticker_data.loc[START_DATE: END_DATE]) 57 | 58 | plot(DATA, plot_returns=ARGS.returns) 59 | -------------------------------------------------------------------------------- /tools/stats.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from datetime import datetime 4 | 5 | import numpy as np 6 | import pandas as pd 7 | import scipy 8 | from scipy.stats import norm, laplace, t, levy_stable 9 | from scipy.stats import kstest, chisquare 10 | 11 | from . import fin_calc 12 | 13 | 14 | def get_returns(data): 15 | total_returns = (data.iloc[-1] - data.iloc[0]) / data.iloc[0] 16 | annual_returns = (1 + total_returns) ** (255 / len(data)) - 1 17 | returns = fin_calc.log_returns(data) 18 | 19 | print('\n Returns:') 20 | print(f' - total:\t{round(total_returns * 100,2)}%') 21 | print(f' - annual:\t{round(annual_returns * 100, 2)}%') 22 | 23 | return (returns, total_returns, annual_returns) 24 | 25 | 26 | def get_moments(returns): 27 | moments = scipy.stats.describe(returns) 28 | 29 | print('\n Moments:') 30 | print(f' - mean:\t{round(moments.mean, 5)}') 31 | print(f' - std:\t{round(np.sqrt(moments.variance), 5)}') 32 | print(f' - skew:\t{round(moments.skewness, 5)}') 33 | print(f' - kurt:\t{round(moments.kurtosis, 5)}') 34 | 35 | return moments 36 | 37 | 38 | def get_simulations(returns): 39 | simulation_size = 100000 40 | 41 | sim_index = ['normal', 'laplace', 'student-t', 'levy-stable'] 42 | sim_list = [norm, laplace, t, levy_stable] 43 | assert len(sim_index) == len(sim_list), 'Mismatch lengths' 44 | 45 | simulations = {} 46 | 47 | for name, sim in zip(sim_index, sim_list): 48 | fit_params = [] 49 | if name == 'levy-stable': 50 | def pconv( 51 | alpha, beta, mu, sigma): return( 52 | alpha, beta, mu - sigma * beta * np.tan(np.pi * alpha / 2.0), sigma) 53 | fit_params = pconv(*sim._fitstart(returns)) 54 | else: 55 | fit_params = sim.fit(returns) 56 | 57 | rvs = pd.Series(sim.rvs(*fit_params, size=simulation_size)) 58 | simulations[name] = {'sim': sim, 'rvs': rvs, 'params': fit_params} 59 | 60 | return simulations 61 | 62 | 63 | def get_risk(returns, sims): 64 | print('\n Risk:') 65 | confidence_level = .05 66 | 67 | var = fin_calc.var(returns, confidence_level) 68 | cvar = fin_calc.cvar(returns, var) 69 | 70 | risk_values = [[var, cvar]] 71 | 72 | # Calculate VAR and cVAR for each simulation 73 | for key in sims: 74 | rvs = sims[key]['rvs'] 75 | sim_var = fin_calc.var(rvs, confidence_level) 76 | sim_cvar = fin_calc.cvar(rvs, sim_var) 77 | risk_values.append([sim_var, sim_cvar]) 78 | 79 | risk_columns = ['VAR', 'cVAR'] 80 | risk_df = pd.DataFrame(risk_values, columns=risk_columns, index=['historical', *sims.keys()]) 81 | print(risk_df) 82 | return risk_df 83 | 84 | 85 | def get_fit(data, sims): 86 | print('\n Goodness of Fit: (p-value > 0.05)') 87 | 88 | for key in sims: 89 | sim = sims[key] 90 | ks = kstest(data, lambda x, s=sim: s['sim'].cdf(x, *s['params'])) 91 | 92 | print(f'\t{key}: \t{ks.pvalue >= 0.05}\t(p-value {round(ks.pvalue, 5)})') 93 | 94 | 95 | def get_stats(datas, verbose=False): 96 | for data in datas: 97 | print(f'--- {data.name} --- ({data.index[0].date()}, {data.index[-1].date()})') 98 | 99 | returns, total_returns, annual_returns = get_returns(data) 100 | get_moments(returns) 101 | if verbose: 102 | simulations = get_simulations(returns) 103 | get_risk(returns, simulations) 104 | get_fit(returns, simulations) 105 | 106 | 107 | if __name__ == '__main__': 108 | PARSER = argparse.ArgumentParser() 109 | PARSER.add_argument('tickers', nargs='+') 110 | PARSER.add_argument('-v', '--verbose', action="store_true") 111 | PARSER.add_argument('-s', '--start', nargs=1, type=int) 112 | PARSER.add_argument('-e', '--end', nargs=1, type=int) 113 | ARGS = PARSER.parse_args() 114 | 115 | TICKERS = ARGS.tickers 116 | 117 | START = ARGS.start or [1900] 118 | END = ARGS.end or [2100] 119 | START_DATE = datetime(START[0], 1, 1) 120 | END_DATE = datetime(END[0], 1, 1) 121 | 122 | DATA = [] 123 | for ticker in TICKERS: 124 | datapath = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker}.csv') 125 | ticker_data = pd.read_csv(datapath, index_col='Date', parse_dates=True)['Adj Close'].rename(ticker) 126 | DATA.append(ticker_data.loc[START_DATE: END_DATE]) 127 | 128 | get_stats(DATA, verbose=ARGS.verbose) 129 | -------------------------------------------------------------------------------- /tools/std.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | 6 | 7 | def std(ticker, length=250, usereturns=False): 8 | path = os.path.join(os.path.dirname(__file__), f'../data/price/{ticker}.csv') 9 | price = pd.read_csv(path, parse_dates=True, index_col='Date')['Adj Close'].rename('Price') 10 | s = price 11 | 12 | if usereturns: 13 | s = np.log(price).diff().iloc[1:] 14 | 15 | print(f'{ticker} ${price.iloc[-1]} ({length})' + (' [Using Returns]' if usereturns else '')) 16 | print('std:\t\t', round(s.iloc[-length:].std(), 5)) 17 | 18 | 19 | if __name__ == '__main__': 20 | PARSER = argparse.ArgumentParser() 21 | PARSER.add_argument('ticker', nargs=1) 22 | PARSER.add_argument('--length', nargs=1, type=int) 23 | PARSER.add_argument('-r', '--usereturns', action="store_true") 24 | ARGS = PARSER.parse_args() 25 | ARG_ITEMS = vars(ARGS) 26 | 27 | # Run 28 | STD_ARGS = {k: (v[0] if isinstance(v, list) else v) for k, v in ARG_ITEMS.items() if v is not None} 29 | 30 | # Run function with parsed arguments 31 | std(**STD_ARGS) 32 | -------------------------------------------------------------------------------- /tools/update_prices.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from datetime import date, datetime, timedelta 4 | 5 | from api import yahoo 6 | from tools.log import log 7 | 8 | 9 | def get_last_date(file_path): 10 | with open(file_path, 'rb') as f: 11 | f.seek(-2, os.SEEK_END) 12 | while f.read(1) != b'\n': 13 | f.seek(-2, os.SEEK_CUR) 14 | last_line = f.readline().decode() 15 | return datetime.strptime(last_line[:10], '%Y-%m-%d').date() 16 | 17 | 18 | def update_ticker(ticker): 19 | file_path = os.path.join(os.path.dirname(__file__), '../data/price/{ticker}.csv'.format(ticker=ticker)) 20 | last_date = None 21 | try: 22 | last_date = get_last_date(file_path) 23 | except OSError as e: 24 | print('!!! Read error.') 25 | log.log(type(e).__name__, e) 26 | except ValueError as e: 27 | print('!!! Invalid date format.') 28 | log.log(type(e).__name__, e) 29 | 30 | if last_date == date.today() - timedelta(days=1): 31 | return 32 | 33 | if last_date is not None: 34 | historical = yahoo.get_daily(ticker, last_date) 35 | historical = historical[historical.index > last_date.strftime('%Y-%m-%d')] 36 | historical.to_csv(file_path, mode='a', header=False) 37 | else: 38 | historical = yahoo.get_daily(ticker, last_date) 39 | historical.to_csv(file_path) 40 | 41 | 42 | def update_all(tickers): 43 | for i, ticker in enumerate(tickers): 44 | print(i, '-', ticker) 45 | update_ticker(ticker) 46 | 47 | 48 | if __name__ == '__main__': 49 | PARSER = argparse.ArgumentParser() 50 | PARSER.add_argument('-t', '--ticker', nargs='+') 51 | ARGS = PARSER.parse_args() 52 | 53 | if ARGS.ticker: 54 | update_all(ARGS.ticker) 55 | else: 56 | DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/price/') 57 | FILE_LIST = os.listdir(DATA_PATH) 58 | TICKERS = [f[:-4] for f in FILE_LIST if os.path.isfile(os.path.join(DATA_PATH, f))] 59 | 60 | update_all(TICKERS) 61 | -------------------------------------------------------------------------------- /tools/validate_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | 4 | 5 | def print_err(message, note=None): 6 | if note: 7 | print('!!!', message, note) 8 | else: 9 | print('!!!', message) 10 | 11 | 12 | def validate(tickers): 13 | for ticker in tickers: 14 | try: 15 | d = pd.read_csv(DATA_PATH + ticker + '.csv', index_col=0, parse_dates=True) 16 | write_data = False 17 | if len(d) < 2: 18 | print_err(ticker, '(empty)') 19 | 20 | if (d.dtypes == object).any(): 21 | print_err(ticker, '(bad dtype)') 22 | 23 | if (abs(d['Adj Close']) <= 1E-8).any(): 24 | print_err(ticker, '(0 Adj Close)') 25 | zero_values = abs(d['Adj Close']) <= 1E-8 26 | print(d.loc[zero_values]) 27 | d.loc[zero_values, 'Adj Close'] = d.loc[zero_values, 'Close'] 28 | write_data = True 29 | 30 | if d.isnull().any(axis=1).any(): 31 | print_err(ticker, '(null)') 32 | d = d.interpolate(method='time') 33 | write_data = True 34 | 35 | if write_data: 36 | print_err('writing...') 37 | d.to_csv(DATA_PATH + ticker + '.csv') 38 | 39 | except Exception as e: 40 | print_err(e) 41 | print('done.') 42 | 43 | 44 | if __name__ == '__main__': 45 | print('loading files...') 46 | DATA_PATH = os.path.join(os.path.dirname(__file__), '../data/price/') 47 | FILE_LIST = os.listdir(DATA_PATH) 48 | TICKERS = [f[:-4] for f in FILE_LIST if os.path.isfile(os.path.join(DATA_PATH, f))] 49 | 50 | print('loaded.') 51 | print('validating data...') 52 | validate(TICKERS) 53 | -------------------------------------------------------------------------------- /tools/vix_term.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | 4 | class VixTermStructure: 5 | def __init__(self, days=1): 6 | assert days > 0 7 | self._data = self.download(days) 8 | self._term_structure = self._data.loc[:, 'F1':'F12'] 9 | self._contango_data = self._data.iloc[:, -3:] 10 | 11 | def download(self, days=1): 12 | print('Downloading VIX Term-Structure...') 13 | 14 | url = f'http://vixcentral.com/historical/?days={days}' 15 | 16 | data = pd.read_html(url)[0] 17 | 18 | header = data.iloc[0] 19 | data = data[1:-1] 20 | data = data.set_index(0) 21 | del data.index.name 22 | data.columns = header[1:] 23 | 24 | print("Term-Structure downloaded.") 25 | return data 26 | 27 | def get(self, month, month2=None): 28 | if month2 is None: 29 | return float(self._term_structure.iloc[0, month-1]) 30 | else: 31 | terms = self._term_structure.iloc[0, month-1: month2-1] 32 | terms = terms.astype(float) 33 | return terms 34 | 35 | def contango(self, months=(1, 2)): 36 | front = self.get(months[0]) 37 | back = self.get(months[1]) 38 | return (back / front - 1.0) 39 | 40 | 41 | if __name__ == '__main__': 42 | vts = VixTermStructure() 43 | print(vts._term_structure) 44 | 45 | print('F1:', vts.get(1)) 46 | print('F2:', vts.get(2)) 47 | print() 48 | 49 | print(vts._contango_data) 50 | print() 51 | 52 | print('Contango (1/2):', vts.contango((1, 2))) 53 | print('Contango (3/5):', vts.contango((3, 5))) 54 | print('Contango (4/7):', vts.contango((4, 7))) 55 | --------------------------------------------------------------------------------