├── .gitattributes ├── .gitignore ├── Chapter1 └── buylowsellhigh.py ├── Chapter2 ├── apo.py ├── bbands.py ├── ema.py ├── macd.py ├── mom.py ├── rsi.py ├── seasonality.py ├── sma.py └── stddev.py ├── Chapter3 ├── ch3_knn.py ├── ch3_logistic.py ├── ch3_svc.py ├── lasso.py ├── lr.py ├── ridge.py └── scatter.py ├── Chapter4 ├── ch4_double_moving_average.py ├── ch4_naive_momentum_strategy2.py ├── ch4_pairs_correlation.py ├── ch4_pairs_correlation_init.py ├── ch4_pairs_correlation_real_symbol.py └── ch4_turtle_trading.py ├── Chapter5 ├── AUDUSD=X_data.pkl ├── CADUSD=X_data.pkl ├── CHFUSD=X_data.pkl ├── EURUSD=X_data.pkl ├── GBPUSD=X_data.pkl ├── GOOG_data.pkl ├── JPYUSD=X_data.pkl ├── NZDUSD=X_data.pkl ├── basic_mean_reversion.csv ├── basic_mean_reversion.py ├── basic_trend_following.csv ├── basic_trend_following.py ├── compare_csvs.py ├── stat_arb.py ├── statistical_arbitrage.csv ├── volatility_adjusted_mean_reversion.csv ├── volatility_adjusted_trend_following.csv ├── volatility_mean_reversion.py └── volatility_trend_following.py ├── Chapter6 ├── risk_measures.py ├── volatility_adjusted_mean_reversion.csv ├── volatility_mean_reversion.py ├── volatility_mean_reversion_with_dynamic_risk_allocation.py └── volatility_mean_reversion_with_risk_checks.py ├── Chapter7 ├── LiquidityProvider.py ├── LiquidityProvider_ut.py ├── MarketSimulator.py ├── MarketSimulator_ut.py ├── OrderBook.py ├── OrderBook_ut.py ├── OrderManager.py ├── OrderManager_ut.py ├── TradingSimulation.py ├── TradingSimulation_ut.py ├── TradingStrategy.py └── TradingStrategy_ut.py ├── Chapter8 └── fixsim │ ├── .gitignore │ ├── FIX44.simulator.xml │ ├── LICENSE │ ├── README.md │ ├── fixsim-client.conf.ini │ ├── fixsim-client.conf.yaml │ ├── fixsim-client.py │ ├── fixsim-server.conf.ini │ ├── fixsim-server.conf.yaml │ ├── fixsim-server.py │ └── fixsim │ ├── __init__.py │ ├── client.py │ ├── server.py │ └── sim.py ├── Chapter9 ├── TradingStrategyDualMA.py ├── eventbasedbacktester.py ├── forloopbacktester.py ├── goog.sql ├── goog_data.h5 ├── goog_db.py ├── hd5pandareader.py ├── kdb_data.py ├── omstimeout.py ├── simulatedclock.py ├── test.h5 └── test2.h5 ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # ========================= 22 | # Operating System Files 23 | # ========================= 24 | -------------------------------------------------------------------------------- /Chapter1/buylowsellhigh.py: -------------------------------------------------------------------------------- 1 | from pandas_datareader import data 2 | start_date = '2014-01-01' 3 | end_date = '2018-01-01' 4 | goog_data = data.DataReader('GOOG', 'yahoo', start_date, end_date) 5 | 6 | 7 | import numpy as np 8 | import pandas as pd 9 | 10 | goog_data_signal = pd.DataFrame(index=goog_data.index) 11 | goog_data_signal['price'] = goog_data['Adj Close'] 12 | goog_data_signal['daily_difference'] = goog_data_signal['price'].diff() 13 | goog_data_signal['signal'] = 0.0 14 | goog_data_signal['signal'][:] = np.where(goog_data_signal['daily_difference'][:] > 0, 1.0, 0.0) 15 | 16 | goog_data_signal['positions'] = goog_data_signal['signal'].diff() 17 | 18 | import matplotlib.pyplot as plt 19 | fig = plt.figure() 20 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 21 | goog_data_signal['price'].plot(ax=ax1, color='r', lw=2.) 22 | 23 | ax1.plot(goog_data_signal.loc[goog_data_signal.positions == 1.0].index, 24 | goog_data_signal.price[goog_data_signal.positions == 1.0], 25 | '^', markersize=5, color='m') 26 | 27 | ax1.plot(goog_data_signal.loc[goog_data_signal.positions == -1.0].index, 28 | goog_data_signal.price[goog_data_signal.positions == -1.0], 29 | 'v', markersize=5, color='k') 30 | 31 | #plt.show() 32 | 33 | 34 | # Set the initial capital 35 | initial_capital= float(1000.0) 36 | 37 | positions = pd.DataFrame(index=goog_data_signal.index).fillna(0.0) 38 | portfolio = pd.DataFrame(index=goog_data_signal.index).fillna(0.0) 39 | 40 | 41 | positions['GOOG'] = goog_data_signal['signal'] 42 | portfolio['positions'] = (positions.multiply(goog_data_signal['price'], axis=0)) 43 | portfolio['cash'] = initial_capital - (positions.diff().multiply(goog_data_signal['price'], axis=0)).cumsum() 44 | portfolio['total'] = portfolio['positions'] + portfolio['cash'] 45 | portfolio.plot() 46 | plt.show() 47 | 48 | 49 | fig = plt.figure() 50 | ax1 = fig.add_subplot(111, ylabel='Portfolio value in $') 51 | portfolio['total'].plot(ax=ax1, lw=2.) 52 | ax1.plot(portfolio.loc[goog_data_signal.positions == 1.0].index,portfolio.total[goog_data_signal.positions == 1.0],'^', markersize=10, color='m') 53 | ax1.plot(portfolio.loc[goog_data_signal.positions == -1.0].index,portfolio.total[goog_data_signal.positions == -1.0],'v', markersize=10, color='k') 54 | plt.show() -------------------------------------------------------------------------------- /Chapter2/apo.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Absolute Price Oscillator (APO) is based 21 | on the absolute differences between two moving averages of different 22 | lengths, a ‘Fast’ and a ‘Slow’ moving average. 23 | 24 | APO = Fast Exponential Moving Average - Slow Exponential Moving Average 25 | ''' 26 | num_periods_fast = 10 # time period for the fast EMA 27 | K_fast = 2 / (num_periods_fast + 1) # smoothing factor for fast EMA 28 | ema_fast = 0 29 | num_periods_slow = 40 # time period for slow EMA 30 | K_slow = 2 / (num_periods_slow + 1) # smoothing factor for slow EMA 31 | ema_slow = 0 32 | 33 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 34 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 35 | apo_values = [] # track computed absolute price oscillator values 36 | for close_price in close: 37 | if (ema_fast == 0): # first observation 38 | ema_fast = close_price 39 | ema_slow = close_price 40 | else: 41 | ema_fast = (close_price - ema_fast) * K_fast + ema_fast 42 | ema_slow = (close_price - ema_slow) * K_slow + ema_slow 43 | 44 | ema_fast_values.append(ema_fast) 45 | ema_slow_values.append(ema_slow) 46 | apo_values.append(ema_fast - ema_slow) 47 | 48 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 49 | goog_data = goog_data.assign(FastExponential10DayMovingAverage=pd.Series(ema_fast_values, index=goog_data.index)) 50 | goog_data = goog_data.assign(SlowExponential40DayMovingAverage=pd.Series(ema_slow_values, index=goog_data.index)) 51 | goog_data = goog_data.assign(AbsolutePriceOscillator=pd.Series(apo_values, index=goog_data.index)) 52 | 53 | close_price = goog_data['ClosePrice'] 54 | ema_f = goog_data['FastExponential10DayMovingAverage'] 55 | ema_s = goog_data['SlowExponential40DayMovingAverage'] 56 | apo = goog_data['AbsolutePriceOscillator'] 57 | 58 | import matplotlib.pyplot as plt 59 | 60 | fig = plt.figure() 61 | ax1 = fig.add_subplot(211, ylabel='Google price in $') 62 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 63 | ema_f.plot(ax=ax1, color='b', lw=2., legend=True) 64 | ema_s.plot(ax=ax1, color='r', lw=2., legend=True) 65 | ax2 = fig.add_subplot(212, ylabel='APO') 66 | apo.plot(ax=ax2, color='black', lw=2., legend=True) 67 | plt.show() 68 | -------------------------------------------------------------------------------- /Chapter2/bbands.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Bollinger Band (BBANDS) study created 21 | by John Bollinger plots upper and lower envelope bands around the 22 | price of the instrument. The width of the bands is based on the 23 | standard deviation of the closing prices from a moving average of 24 | price. 25 | Middle 26 | Band = n-period moving average 27 | 28 | Upper 29 | Band = Middle Band + ( y * n-period standard deviation) 30 | 31 | Lower Band = Middle Band - ( y * 32 | n-period standard deviation) 33 | 34 | Where: 35 | 36 | n = number of periods 37 | y = factor to apply to the standard deviation value, (typical default for y = 2) 38 | Detailed: 39 | 40 | Calculate the moving average. 41 | The formula is: 42 | d = ((P1-MA)^2 + (P2-MA)^2 + ... (Pn-MA)^2)/n 43 | 44 | Pn is the price you pay for the nth interval 45 | n is the number of periods you select 46 | Subtract the moving average 47 | from each of the individual data points used in the moving average 48 | calculation. This gives you a list of deviations from the average. 49 | Square each deviation and add them all together. Divide this sum 50 | by the number of periods you selected. 51 | 52 | Take the square root of d. This gives you the standard deviation. 53 | 54 | delta = sqrt(d) 55 | 56 | Compute the bands by using the following formulas: 57 | Upper Band = MA + delta 58 | Middle Band = MA 59 | Lower Band = MA - delta 60 | 61 | ''' 62 | import statistics as stats 63 | import math as math 64 | 65 | time_period = 20 # history length for Simple Moving Average for middle band 66 | stdev_factor = 2 # Standard Deviation Scaling factor for the upper and lower bands 67 | history = [] # price history for computing simple moving average 68 | sma_values = [] # moving average of prices for visualization purposes 69 | upper_band = [] # upper band values 70 | lower_band = [] # lower band values 71 | 72 | for close_price in close: 73 | history.append(close_price) 74 | if len(history) > time_period: # we only want to maintain at most 'time_period' number of price observations 75 | del (history[0]) 76 | 77 | sma = stats.mean(history) 78 | sma_values.append(sma) # simple moving average or middle band 79 | variance = 0 # variance is the square of standard deviation 80 | for hist_price in history: 81 | variance = variance + ((hist_price - sma) ** 2) 82 | 83 | stdev = math.sqrt(variance / len(history)) # use square root to get standard deviation 84 | 85 | upper_band.append(sma + stdev_factor * stdev) 86 | lower_band.append(sma - stdev_factor * stdev) 87 | 88 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 89 | goog_data = goog_data.assign(MiddleBollingerBand20DaySMA=pd.Series(sma_values, index=goog_data.index)) 90 | goog_data = goog_data.assign(UpperBollingerBand20DaySMA2StdevFactor=pd.Series(upper_band, index=goog_data.index)) 91 | goog_data = goog_data.assign(LowerBollingerBand20DaySMA2StdevFactor=pd.Series(lower_band, index=goog_data.index)) 92 | 93 | close_price = goog_data['ClosePrice'] 94 | mband = goog_data['MiddleBollingerBand20DaySMA'] 95 | uband = goog_data['UpperBollingerBand20DaySMA2StdevFactor'] 96 | lband = goog_data['LowerBollingerBand20DaySMA2StdevFactor'] 97 | 98 | import matplotlib.pyplot as plt 99 | 100 | fig = plt.figure() 101 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 102 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 103 | mband.plot(ax=ax1, color='b', lw=2., legend=True) 104 | uband.plot(ax=ax1, color='g', lw=2., legend=True) 105 | lband.plot(ax=ax1, color='r', lw=2., legend=True) 106 | plt.show() 107 | -------------------------------------------------------------------------------- /Chapter2/ema.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Exponential Moving Average (EMA) represents 21 | an average of prices, but places more weight on recent prices. The 22 | weighting applied to the most recent price depends on the selected 23 | period of the moving average. The shorter the period for the EMA, 24 | the more weight that will be applied to the most recent price. 25 | 26 | EMA = ( P - EMAp ) * K + EMAp 27 | 28 | Where: 29 | 30 | P = Price for the current period 31 | EMAp = the Exponential moving Average for the previous period 32 | K = the smoothing constant, equal to 2 / (n + 1) 33 | n = the number of periods in a simple moving average roughly approximated by the EMA 34 | ''' 35 | num_periods = 20 # number of days over which to average 36 | K = 2 / (num_periods + 1) # smoothing constant 37 | ema_p = 0 38 | 39 | ema_values = [] # to hold computed EMA values 40 | for close_price in close: 41 | if (ema_p == 0): # first observation, EMA = current-price 42 | ema_p = close_price 43 | else: 44 | ema_p = (close_price - ema_p) * K + ema_p 45 | 46 | ema_values.append(ema_p) 47 | 48 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 49 | goog_data = goog_data.assign(Exponential20DayMovingAverage=pd.Series(ema_values, index=goog_data.index)) 50 | 51 | close_price = goog_data['ClosePrice'] 52 | ema = goog_data['Exponential20DayMovingAverage'] 53 | 54 | import matplotlib.pyplot as plt 55 | 56 | fig = plt.figure() 57 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 58 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 59 | ema.plot(ax=ax1, color='b', lw=2., legend=True) 60 | plt.show() 61 | -------------------------------------------------------------------------------- /Chapter2/macd.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Moving Average Convergence Divergence 21 | (MACD) was developed by Gerald Appel, and is based on the differences 22 | between two moving averages of different lengths, a Fast and a Slow moving 23 | average. A second line, called the Signal line is plotted as a moving 24 | average of the MACD. A third line, called the MACD Histogram is 25 | optionally plotted as a histogram of the difference between the 26 | MACD and the Signal Line. 27 | 28 | MACD = FastMA - SlowMA 29 | 30 | Where: 31 | 32 | FastMA is the shorter moving average and SlowMA is the longer moving average. 33 | SignalLine = MovAvg (MACD) 34 | MACD Histogram = MACD - SignalLine 35 | ''' 36 | num_periods_fast = 10 # fast EMA time period 37 | K_fast = 2 / (num_periods_fast + 1) # fast EMA smoothing factor 38 | ema_fast = 0 39 | num_periods_slow = 40 # slow EMA time period 40 | K_slow = 2 / (num_periods_slow + 1) # slow EMA smoothing factor 41 | ema_slow = 0 42 | num_periods_macd = 20 # MACD EMA time period 43 | K_macd = 2 / (num_periods_macd + 1) # MACD EMA smoothing factor 44 | ema_macd = 0 45 | 46 | ema_fast_values = [] # track fast EMA values for visualization purposes 47 | ema_slow_values = [] # track slow EMA values for visualization purposes 48 | macd_values = [] # track MACD values for visualization purposes 49 | macd_signal_values = [] # MACD EMA values tracker 50 | macd_historgram_values = [] # MACD - MACD-EMA 51 | for close_price in close: 52 | if (ema_fast == 0): # first observation 53 | ema_fast = close_price 54 | ema_slow = close_price 55 | else: 56 | ema_fast = (close_price - ema_fast) * K_fast + ema_fast 57 | ema_slow = (close_price - ema_slow) * K_slow + ema_slow 58 | 59 | ema_fast_values.append(ema_fast) 60 | ema_slow_values.append(ema_slow) 61 | 62 | macd = ema_fast - ema_slow # MACD is fast_MA - slow_EMA 63 | if ema_macd == 0: 64 | ema_macd = macd 65 | else: 66 | ema_macd = (macd - ema_macd) * K_macd + ema_macd # signal is EMA of MACD values 67 | 68 | macd_values.append(macd) 69 | macd_signal_values.append(ema_macd) 70 | macd_historgram_values.append(macd - ema_macd) 71 | 72 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 73 | goog_data = goog_data.assign(FastExponential10DayMovingAverage=pd.Series(ema_fast_values, index=goog_data.index)) 74 | goog_data = goog_data.assign(SlowExponential40DayMovingAverage=pd.Series(ema_slow_values, index=goog_data.index)) 75 | goog_data = goog_data.assign(MovingAverageConvergenceDivergence=pd.Series(macd_values, index=goog_data.index)) 76 | goog_data = goog_data.assign(Exponential20DayMovingAverageOfMACD=pd.Series(macd_signal_values, index=goog_data.index)) 77 | goog_data = goog_data.assign(MACDHistorgram=pd.Series(macd_historgram_values, index=goog_data.index)) 78 | 79 | close_price = goog_data['ClosePrice'] 80 | ema_f = goog_data['FastExponential10DayMovingAverage'] 81 | ema_s = goog_data['SlowExponential40DayMovingAverage'] 82 | macd = goog_data['MovingAverageConvergenceDivergence'] 83 | ema_macd = goog_data['Exponential20DayMovingAverageOfMACD'] 84 | macd_histogram = goog_data['MACDHistorgram'] 85 | 86 | import matplotlib.pyplot as plt 87 | 88 | fig = plt.figure() 89 | ax1 = fig.add_subplot(311, ylabel='Google price in $') 90 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 91 | ema_f.plot(ax=ax1, color='b', lw=2., legend=True) 92 | ema_s.plot(ax=ax1, color='r', lw=2., legend=True) 93 | ax2 = fig.add_subplot(312, ylabel='MACD') 94 | macd.plot(ax=ax2, color='black', lw=2., legend=True) 95 | ema_macd.plot(ax=ax2, color='g', lw=2., legend=True) 96 | ax3 = fig.add_subplot(313, ylabel='MACD') 97 | macd_histogram.plot(ax=ax3, color='r', kind='bar', legend=True, use_index=False) 98 | plt.show() 99 | -------------------------------------------------------------------------------- /Chapter2/mom.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Momentum (MOM) indicator compares the 21 | current price with the previous price from a selected number of 22 | periods ago. This indicator is similar to the “Rate of Change” indicator, 23 | but the MOM does not normalize the price, so different instruments 24 | can have different indicator values based on their point values. 25 | 26 | MOM = Price - Price of n periods ago 27 | ''' 28 | time_period = 20 # how far to look back to find reference price to compute momentum 29 | history = [] # history of observed prices to use in momentum calculation 30 | mom_values = [] # track momentum values for visualization purposes 31 | 32 | for close_price in close: 33 | history.append(close_price) 34 | if len(history) > time_period: # history is at most 'time_period' number of observations 35 | del (history[0]) 36 | 37 | mom = close_price - history[0] 38 | mom_values.append(mom) 39 | 40 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 41 | goog_data = goog_data.assign(MomentumFromPrice20DaysAgo=pd.Series(mom_values, index=goog_data.index)) 42 | 43 | close_price = goog_data['ClosePrice'] 44 | mom = goog_data['MomentumFromPrice20DaysAgo'] 45 | 46 | import matplotlib.pyplot as plt 47 | 48 | fig = plt.figure() 49 | ax1 = fig.add_subplot(211, ylabel='Google price in $') 50 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 51 | ax2 = fig.add_subplot(212, ylabel='Momentum in $') 52 | mom.plot(ax=ax2, color='b', lw=2., legend=True) 53 | plt.show() 54 | -------------------------------------------------------------------------------- /Chapter2/rsi.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | The Relative Strength Index (RSI) was published 21 | by J. Welles Wilder. The current price is normalized as a percentage 22 | between 0 and 100. The name of this oscillator is misleading because 23 | it does not compare the instrument relative to another instrument 24 | or set of instruments, but rather represents the current price relative 25 | to other recent pieces within the selected lookback window length. 26 | 27 | RSI = 100 - (100 / (1 + RS)) 28 | 29 | Where: 30 | RS = ratio of smoothed average of n-period gains divided by the 31 | absolute value of the smoothed average of n-period losses. 32 | ''' 33 | import statistics as stats 34 | 35 | time_period = 20 # look back period to compute gains & losses 36 | gain_history = [] # history of gains over look back period (0 if no gain, magnitude of gain if gain) 37 | loss_history = [] # history of losses over look back period (0 if no loss, magnitude of loss if loss) 38 | avg_gain_values = [] # track avg gains for visualization purposes 39 | avg_loss_values = [] # track avg losses for visualization purposes 40 | rsi_values = [] # track computed RSI values 41 | last_price = 0 # current_price - last_price > 0 => gain. current_price - last_price < 0 => loss. 42 | 43 | for close_price in close: 44 | if last_price == 0: 45 | last_price = close_price 46 | 47 | gain_history.append(max(0, close_price - last_price)) 48 | loss_history.append(max(0, last_price - close_price)) 49 | last_price = close_price 50 | 51 | if len(gain_history) > time_period: # maximum observations is equal to lookback period 52 | del (gain_history[0]) 53 | del (loss_history[0]) 54 | 55 | avg_gain = stats.mean(gain_history) # average gain over lookback period 56 | avg_loss = stats.mean(loss_history) # average loss over lookback period 57 | 58 | avg_gain_values.append(avg_gain) 59 | avg_loss_values.append(avg_loss) 60 | 61 | rs = 0 62 | if avg_loss > 0: # to avoid division by 0, which is undefined 63 | rs = avg_gain / avg_loss 64 | 65 | rsi = 100 - (100 / (1 + rs)) 66 | rsi_values.append(rsi) 67 | 68 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 69 | goog_data = goog_data.assign(RelativeStrengthAvgGainOver20Days=pd.Series(avg_gain_values, index=goog_data.index)) 70 | goog_data = goog_data.assign(RelativeStrengthAvgLossOver20Days=pd.Series(avg_loss_values, index=goog_data.index)) 71 | goog_data = goog_data.assign(RelativeStrengthIndicatorOver20Days=pd.Series(rsi_values, index=goog_data.index)) 72 | 73 | close_price = goog_data['ClosePrice'] 74 | rs_gain = goog_data['RelativeStrengthAvgGainOver20Days'] 75 | rs_loss = goog_data['RelativeStrengthAvgLossOver20Days'] 76 | rsi = goog_data['RelativeStrengthIndicatorOver20Days'] 77 | 78 | import matplotlib.pyplot as plt 79 | 80 | fig = plt.figure() 81 | ax1 = fig.add_subplot(311, ylabel='Google price in $') 82 | close_price.plot(ax=ax1, color='black', lw=2., legend=True) 83 | ax2 = fig.add_subplot(312, ylabel='RS') 84 | rs_gain.plot(ax=ax2, color='g', lw=2., legend=True) 85 | rs_loss.plot(ax=ax2, color='r', lw=2., legend=True) 86 | ax3 = fig.add_subplot(313, ylabel='RSI') 87 | rsi.plot(ax=ax3, color='b', lw=2., legend=True) 88 | plt.show() 89 | -------------------------------------------------------------------------------- /Chapter2/seasonality.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | 4 | from pandas_datareader import data 5 | start_date = '2001-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME='goog_data_large.pkl' 8 | 9 | try: 10 | goog_data = pd.read_pickle(SRC_DATA_FILENAME) 11 | print('File data found...reading GOOG data') 12 | except FileNotFoundError: 13 | print('File not found...downloading the GOOG data') 14 | goog_data = data.DataReader('GOOG', 'yahoo', start_date, end_date) 15 | goog_data.to_pickle(SRC_DATA_FILENAME) 16 | 17 | 18 | goog_monthly_return = goog_data['Adj Close'].pct_change().groupby( 19 | [goog_data['Adj Close'].index.year, 20 | goog_data['Adj Close'].index.month]).mean() 21 | 22 | goog_montly_return_list=[] 23 | for i in range(len(goog_monthly_return)): 24 | goog_montly_return_list.append\ 25 | ({'month':goog_monthly_return.index[i][1], 26 | 'monthly_return': goog_monthly_return[i]}) 27 | 28 | goog_montly_return_list=pd.DataFrame(goog_montly_return_list, 29 | columns=('month','monthly_return')) 30 | 31 | goog_montly_return_list.boxplot(column='monthly_return', by='month') 32 | ax = plt.gca() 33 | labels = [item.get_text() for item in ax.get_xticklabels()] 34 | labels=['Jan','Feb','Mar','Apr','May','Jun',\ 35 | 'Jul','Aug','Sep','Oct','Nov','Dec'] 36 | ax.set_xticklabels(labels) 37 | ax.set_ylabel('GOOG return') 38 | plt.tick_params(axis='both', which='major', labelsize=7) 39 | plt.title("GOOG Montly return 2001-2018") 40 | plt.suptitle("") 41 | plt.show() 42 | 43 | 44 | fig = plt.figure() 45 | goog_data['Adj Close'].pct_change().groupby( 46 | [goog_data['Adj Close'].index.month]) 47 | ax1 = fig.add_subplot(111, ylabel='Monthly return') 48 | goog_monthly_return.plot() 49 | plt.xlabel('Time') 50 | plt.show() 51 | 52 | # Displaying rolling statistics 53 | def plot_rolling_statistics_ts(ts, titletext,ytext, window_size=12): 54 | ts.plot(color='red', label='Original', lw=0.5) 55 | ts.rolling(window_size).mean().plot( 56 | color='blue',label='Rolling Mean') 57 | ts.rolling(window_size).std().plot( 58 | color='black', label='Rolling Std') 59 | plt.legend(loc='best') 60 | plt.ylabel(ytext) 61 | plt.title(titletext) 62 | plt.show(block=False) 63 | 64 | 65 | plot_rolling_statistics_ts(goog_monthly_return[1:],'GOOG prices rolling mean and standard deviation','Monthly return') 66 | plot_rolling_statistics_ts(goog_data['Adj Close'],'GOOG prices rolling mean and standard deviation','Daily prices',365) 67 | 68 | plot_rolling_statistics_ts(goog_data['Adj Close']-goog_data['Adj Close'].rolling(365).mean(),'GOOG prices without trend','Daily prices',365) 69 | 70 | 71 | from statsmodels.tsa.stattools import adfuller 72 | 73 | def test_stationarity(timeseries): 74 | print('Results of Dickey-Fuller Test:') 75 | dftest = adfuller(timeseries[1:], autolag='AIC') 76 | dfoutput = pd.Series(dftest[0:4], index=['Test Statistic', 'p-value', '#Lags Used', 'Number of Observations Used']) 77 | print (dfoutput) 78 | 79 | test_stationarity(goog_monthly_return[1:]) 80 | test_stationarity(goog_data['Adj Close']) 81 | 82 | 83 | from statsmodels.graphics.tsaplots import plot_acf 84 | from statsmodels.graphics.tsaplots import plot_pacf 85 | from matplotlib import pyplot 86 | 87 | pyplot.figure() 88 | pyplot.subplot(211) 89 | plot_acf(goog_monthly_return[1:], ax=pyplot.gca(),lags=10) 90 | pyplot.subplot(212) 91 | plot_pacf(goog_monthly_return[1:], ax=pyplot.gca(),lags=10) 92 | pyplot.show() 93 | 94 | from statsmodels.tsa.arima_model import ARIMA 95 | 96 | model = ARIMA(goog_monthly_return[1:], order=(2, 0, 2)) 97 | fitted_results = model.fit() 98 | goog_monthly_return[1:].plot() 99 | fitted_results.fittedvalues.plot(color='red') 100 | plt.show() -------------------------------------------------------------------------------- /Chapter2/sma.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | 21 | The Simple Moving Average (SMA) is calculated 22 | by adding the price of an instrument over a number of time periods 23 | and then dividing the sum by the number of time periods. The SMA 24 | is basically the average price of the given time period, with equal 25 | weighting given to the price of each period. 26 | 27 | Simple Moving Average 28 | SMA = ( Sum ( Price, n ) ) / n 29 | 30 | Where: n = Time Period 31 | ''' 32 | import statistics as stats 33 | 34 | time_period = 20 # number of days over which to average 35 | history = [] # to track a history of prices 36 | sma_values = [] # to track simple moving average values 37 | for close_price in close: 38 | history.append(close_price) 39 | if len(history) > time_period: # we remove oldest price because we only average over last 'time_period' prices 40 | del (history[0]) 41 | 42 | sma_values.append(stats.mean(history)) 43 | 44 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 45 | goog_data = goog_data.assign(Simple20DayMovingAverage=pd.Series(sma_values, index=goog_data.index)) 46 | 47 | close_price = goog_data['ClosePrice'] 48 | sma = goog_data['Simple20DayMovingAverage'] 49 | 50 | import matplotlib.pyplot as plt 51 | 52 | fig = plt.figure() 53 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 54 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 55 | sma.plot(ax=ax1, color='r', lw=2., legend=True) 56 | plt.show() 57 | -------------------------------------------------------------------------------- /Chapter2/stddev.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from pandas_datareader import data 4 | 5 | start_date = '2014-01-01' 6 | end_date = '2018-01-01' 7 | SRC_DATA_FILENAME = 'goog_data.pkl' 8 | 9 | try: 10 | goog_data2 = pd.read_pickle(SRC_DATA_FILENAME) 11 | except FileNotFoundError: 12 | goog_data2 = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | goog_data2.to_pickle(SRC_DATA_FILENAME) 14 | 15 | goog_data = goog_data2.tail(620) 16 | 17 | close = goog_data['Close'] 18 | 19 | ''' 20 | Standard Deviation is a statistical calculation 21 | used to measure the variability. In trading this value is known 22 | as volatility. A low standard deviation indicates that the data 23 | points tend to be very close to the mean, whereas high standard 24 | deviation indicates that the data points are spread out over a large 25 | range of values. 26 | 27 | n = number of periods 28 | 29 | Calculate the moving average. 30 | The formula is: 31 | d = ((P1-MA)^2 + (P2-MA)^2 + ... (Pn-MA)^2)/n 32 | 33 | Pn is the price you pay for the nth interval 34 | n is the number of periods you select 35 | 36 | Take the square root of d. This gives you the standard deviation. 37 | 38 | stddev = sqrt(d) 39 | 40 | ''' 41 | import statistics as stats 42 | import math as math 43 | 44 | time_period = 20 # look back period 45 | history = [] # history of prices 46 | sma_values = [] # to track moving average values for visualization purposes 47 | stddev_values = [] # history of computed stdev values 48 | 49 | for close_price in close: 50 | history.append(close_price) 51 | if len(history) > time_period: # we track at most 'time_period' number of prices 52 | del (history[0]) 53 | 54 | sma = stats.mean(history) 55 | sma_values.append(sma) 56 | variance = 0 # variance is square of standard deviation 57 | for hist_price in history: 58 | variance = variance + ((hist_price - sma) ** 2) 59 | 60 | stdev = math.sqrt(variance / len(history)) 61 | 62 | stddev_values.append(stdev) 63 | 64 | goog_data = goog_data.assign(ClosePrice=pd.Series(close, index=goog_data.index)) 65 | goog_data = goog_data.assign(StandardDeviationOver20Days=pd.Series(stddev_values, index=goog_data.index)) 66 | 67 | close_price = goog_data['ClosePrice'] 68 | stddev = goog_data['StandardDeviationOver20Days'] 69 | 70 | import matplotlib.pyplot as plt 71 | 72 | fig = plt.figure() 73 | ax1 = fig.add_subplot(211, ylabel='Google price in $') 74 | close_price.plot(ax=ax1, color='g', lw=2., legend=True) 75 | ax2 = fig.add_subplot(212, ylabel='Stddev in $') 76 | stddev.plot(ax=ax2, color='b', lw=2., legend=True) 77 | ax2.axhline(y=stats.mean(stddev_values), color='k') 78 | plt.show() 79 | -------------------------------------------------------------------------------- /Chapter3/ch3_knn.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.neighbors import KNeighborsClassifier 5 | from sklearn.metrics import accuracy_score 6 | 7 | 8 | from pandas_datareader import data 9 | start_date = '2001-01-01' 10 | end_date = '2018-01-01' 11 | SRC_DATA_FILENAME='goog_data_large.pkl' 12 | 13 | try: 14 | goog_data = pd.read_pickle(SRC_DATA_FILENAME) 15 | print('File data found...reading GOOG data') 16 | except FileNotFoundError: 17 | print('File not found...downloading the GOOG data') 18 | goog_data = data.DataReader('GOOG', 'yahoo', start_date, end_date) 19 | goog_data.to_pickle(SRC_DATA_FILENAME) 20 | 21 | goog_data['Open-Close']=goog_data.Open-goog_data.Close 22 | goog_data['High-Low']=goog_data.High-goog_data.Low 23 | goog_data=goog_data.dropna() 24 | X=goog_data[['Open-Close','High-Low']] 25 | Y=np.where(goog_data['Close'].shift(-1)>goog_data['Close'],1,-1) 26 | 27 | split_ratio=0.8 28 | split_value=int(split_ratio * len(goog_data)) 29 | X_train=X[:split_value] 30 | Y_train=Y[:split_value] 31 | X_test=X[split_value:] 32 | Y_test=Y[split_value:] 33 | 34 | 35 | knn=KNeighborsClassifier(n_neighbors=15) 36 | knn.fit(X_train, Y_train) 37 | accuracy_train = accuracy_score(Y_train, knn.predict(X_train)) 38 | accuracy_test = accuracy_score(Y_test, knn.predict(X_test)) 39 | 40 | 41 | 42 | 43 | goog_data['Predicted_Signal']=knn.predict(X) 44 | goog_data['GOOG_Returns']=np.log(goog_data['Close']/ 45 | goog_data['Close'].shift(1)) 46 | 47 | 48 | def calculate_return(df,split_value,symbol): 49 | cum_goog_return= df[split_value:]['%s_Returns' % symbol].cumsum() * 100 50 | df['Strategy_Returns']= df['%s_Returns' % symbol] * df['Predicted_Signal'].shift(1) 51 | return cum_goog_return 52 | 53 | def calculate_strategy_return(df,split_value): 54 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 55 | return cum_strategy_return 56 | 57 | cum_goog_return=calculate_return(goog_data,split_value=len(X_train),symbol='GOOG') 58 | cum_strategy_return= calculate_strategy_return(goog_data,split_value=len(X_train)) 59 | 60 | 61 | def plot_chart(cum_symbol_return, cum_strategy_return, symbol): 62 | plt.figure(figsize=(10,5)) 63 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 64 | plt.plot(cum_strategy_return,label='Strategy Returns') 65 | plt.legend() 66 | plt.show() 67 | 68 | plot_chart(cum_goog_return, cum_strategy_return,symbol='GOOG') 69 | 70 | 71 | 72 | # print(accuracy_train, accuracy_test) 73 | 74 | # goog_data['Predicted_Signal']=knn.predict(X) 75 | # goog_data['GOOG_Returns']=np.log(goog_data['Close']/ 76 | # goog_data['Close'].shift(1)) 77 | # cum_goog_return=goog_data[split_value:]['GOOG_Returns'].cumsum()*100 78 | # 79 | # goog_data['Strategy_Returns']=goog_data['GOOG_Returns'] * goog_data['Predicted_Signal'].shift(1) 80 | # cum_strategy_return=goog_data[split_value:]['Strategy_Returns'].cumsum()*100 81 | # 82 | # plt.figure(figsize=(10,5)) 83 | # plt.plot(cum_goog_return,label='GOOG Returns') 84 | # plt.plot(cum_strategy_return,label='Strategy Returns') 85 | # plt.legend() 86 | # plt.show() 87 | 88 | 89 | def sharpe_ratio(symbol_returns, strategy_returns): 90 | strategy_std=strategy_returns.std() 91 | sharpe=(strategy_returns-symbol_returns)/strategy_std 92 | return sharpe.mean() 93 | 94 | print(sharpe_ratio(cum_strategy_return,cum_goog_return)) 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Chapter3/ch3_logistic.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.metrics import accuracy_score 5 | from sklearn.linear_model import LogisticRegression 6 | 7 | 8 | from pandas_datareader import data 9 | start_date = '2001-01-01' 10 | end_date = '2018-01-01' 11 | SRC_DATA_FILENAME='goog_data_large.pkl' 12 | 13 | try: 14 | goog_data = pd.read_pickle(SRC_DATA_FILENAME) 15 | print('File data found...reading GOOG data') 16 | except FileNotFoundError: 17 | print('File not found...downloading the GOOG data') 18 | goog_data = data.DataReader('GOOG', 'yahoo', start_date, end_date) 19 | goog_data.to_pickle(SRC_DATA_FILENAME) 20 | 21 | goog_data['Open-Close']=goog_data.Open-goog_data.Close 22 | goog_data['High-Low']=goog_data.High-goog_data.Low 23 | goog_data=goog_data.dropna() 24 | X=goog_data[['Open-Close','High-Low']] 25 | Y=np.where(goog_data['Close'].shift(-1)>goog_data['Close'],1,-1) 26 | 27 | split_ratio=0.8 28 | split_value=int(split_ratio * len(goog_data)) 29 | X_train=X[:split_value] 30 | Y_train=Y[:split_value] 31 | X_test=X[split_value:] 32 | Y_test=Y[split_value:] 33 | 34 | 35 | logistic=LogisticRegression() 36 | logistic.fit(X_train, Y_train) 37 | accuracy_train = accuracy_score(Y_train, logistic.predict(X_train)) 38 | accuracy_test = accuracy_score(Y_test, logistic.predict(X_test)) 39 | print(accuracy_train, accuracy_test) 40 | 41 | 42 | goog_data['Predicted_Signal']=logistic.predict(X) 43 | goog_data['GOOG_Returns']=np.log(goog_data['Close']/goog_data['Close'].shift(1)) 44 | 45 | 46 | def calculate_return(df,split_value,symbol): 47 | cum_goog_return= df[split_value:]['%s_Returns' % symbol].cumsum() * 100 48 | df['Strategy_Returns']= df['%s_Returns' % symbol] * df['Predicted_Signal'].shift(1) 49 | return cum_goog_return 50 | 51 | def calculate_strategy_return(df,split_value): 52 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 53 | return cum_strategy_return 54 | 55 | cum_goog_return=calculate_return(goog_data,split_value=len(X_train),symbol='GOOG') 56 | cum_strategy_return= calculate_strategy_return(goog_data,split_value=len(X_train)) 57 | 58 | 59 | def plot_shart(cum_symbol_return, cum_strategy_return, symbol): 60 | plt.figure(figsize=(10,5)) 61 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 62 | plt.plot(cum_strategy_return,label='Strategy Returns') 63 | plt.legend() 64 | plt.show() 65 | 66 | plot_shart(cum_goog_return, cum_strategy_return,symbol='GOOG') 67 | 68 | def sharpe_ratio(symbol_returns, strategy_returns): 69 | strategy_std=strategy_returns.std() 70 | sharpe=(strategy_returns-symbol_returns)/strategy_std 71 | return sharpe.mean() 72 | 73 | accuracy_train = accuracy_score(Y_train, logistic.predict(X_train)) 74 | accuracy_test = accuracy_score(Y_test, logistic.predict(X_test)) 75 | print(accuracy_train, accuracy_test) 76 | print(sharpe_ratio(cum_strategy_return,cum_goog_return)) 77 | 78 | 79 | 80 | 81 | 82 | # goog_data['Close'].shift(1)) 83 | # cum_goog_return=goog_data[split_value:]['GOOG_Returns'].cumsum()*100 84 | # 85 | # goog_data['Strategy_Returns']=goog_data['GOOG_Returns'] * goog_data['Predicted_Signal'].shift(1) 86 | # cum_strategy_return=goog_data[split_value:]['Strategy_Returns'].cumsum()*100 87 | # 88 | # plt.figure(figsize=(10,5)) 89 | # plt.plot(cum_goog_return,label='GOOG Returns') 90 | # plt.plot(cum_strategy_return,label='Strategy Returns') 91 | # plt.legend() 92 | # plt.show() 93 | # 94 | # 95 | # def sharpe_ratio(symbol_returns, strategy_returns): 96 | # strategy_std=strategy_returns.std() 97 | # sharpe=(strategy_returns-symbol_returns)/strategy_std 98 | # return sharpe.mean() 99 | # 100 | # print(sharpe_ratio(cum_strategy_return,cum_goog_return)) 101 | -------------------------------------------------------------------------------- /Chapter3/ch3_svc.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.metrics import accuracy_score 5 | from sklearn.svm import SVC 6 | from sklearn.model_selection import train_test_split 7 | 8 | 9 | from pandas_datareader import data 10 | 11 | def load_financial_data(start_date, end_date,output_file): 12 | try: 13 | df = pd.read_pickle(output_file) 14 | print('File data found...reading GOOG data') 15 | except FileNotFoundError: 16 | print('File not found...downloading the GOOG data') 17 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 18 | df.to_pickle(output_file) 19 | return df 20 | 21 | goog_data=load_financial_data(start_date='2001-01-01', 22 | end_date = '2018-01-01', 23 | output_file='goog_data_large.pkl') 24 | 25 | 26 | 27 | 28 | def create_trading_condition(df): 29 | df['Open-Close']=df.Open-df.Close 30 | df['High-Low']=df.High-df.Low 31 | df=df.dropna() 32 | X=df[['Open-Close','High-Low']] 33 | Y=np.where(df['Close'].shift(-1)>df['Close'],1,-1) 34 | return (X,Y) 35 | 36 | def create_train_split_group(X,Y,split_ratio=0.8): 37 | return train_test_split(X,Y,shuffle=False,train_size=split_ratio) 38 | 39 | X,Y=create_trading_condition(goog_data) 40 | 41 | X_train,X_test,Y_train,Y_test=\ 42 | create_train_split_group(X,Y,split_ratio=0.8) 43 | 44 | # Fit the model 45 | svc=SVC() 46 | svc.fit(X_train, Y_train) 47 | # Forecast value 48 | 49 | goog_data['Predicted_Signal']=svc.predict(X) 50 | goog_data['GOOG_Returns']=np.log(goog_data['Close']/ 51 | goog_data['Close'].shift(1)) 52 | 53 | 54 | def calculate_return(df,split_value,symbol): 55 | cum_goog_return= df[split_value:]['%s_Returns' % symbol].cumsum() * 100 56 | df['Strategy_Returns']= df['%s_Returns' % symbol] * df['Predicted_Signal'].shift(1) 57 | return cum_goog_return 58 | 59 | def calculate_strategy_return(df,split_value): 60 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 61 | return cum_strategy_return 62 | 63 | cum_goog_return=calculate_return(goog_data,split_value=len(X_train),symbol='GOOG') 64 | cum_strategy_return= calculate_strategy_return(goog_data,split_value=len(X_train)) 65 | 66 | 67 | def plot_shart(cum_symbol_return, cum_strategy_return, symbol): 68 | plt.figure(figsize=(10,5)) 69 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 70 | plt.plot(cum_strategy_return,label='Strategy Returns') 71 | plt.legend() 72 | plt.show() 73 | 74 | plot_shart(cum_goog_return, cum_strategy_return,symbol='GOOG') 75 | 76 | def sharpe_ratio(symbol_returns, strategy_returns): 77 | strategy_std=strategy_returns.std() 78 | sharpe=(strategy_returns-symbol_returns)/strategy_std 79 | return sharpe.mean() 80 | 81 | accuracy_train = accuracy_score(Y_train, svc.predict(X_train)) 82 | accuracy_test = accuracy_score(Y_test, svc.predict(X_test)) 83 | print(accuracy_train, accuracy_test) 84 | print(sharpe_ratio(cum_strategy_return,cum_goog_return)) 85 | -------------------------------------------------------------------------------- /Chapter3/lasso.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.model_selection import train_test_split 5 | 6 | from pandas_datareader import data 7 | 8 | def load_financial_data(start_date, end_date, output_file): 9 | try: 10 | df = pd.read_pickle(output_file) 11 | print('File data found...reading GOOG data') 12 | except FileNotFoundError: 13 | print('File not found...downloading the GOOG data') 14 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 15 | df.to_pickle(output_file) 16 | return df 17 | 18 | def create_classification_trading_condition(df): 19 | df['Open-Close'] = df.Open - df.Close 20 | df['High-Low'] = df.High - df.Low 21 | df = df.dropna() 22 | X = df[['Open-Close', 'High-Low']] 23 | Y = np.where(df['Close'].shift(-1) > df['Close'], 1, -1) 24 | return (df, X, Y) 25 | 26 | def create_regression_trading_condition(df): 27 | df['Open-Close'] = df.Open - df.Close 28 | df['High-Low'] = df.High - df.Low 29 | df['Target'] = df['Close'].shift(-1) - df['Close'] 30 | df = df.dropna() 31 | X = df[['Open-Close', 'High-Low']] 32 | Y = df[['Target']] 33 | return (df, X, Y) 34 | 35 | def create_train_split_group(X, Y, split_ratio=0.8): 36 | return train_test_split(X, Y, shuffle=False, train_size=split_ratio) 37 | 38 | goog_data = load_financial_data( 39 | start_date='2001-01-01', 40 | end_date='2018-01-01', 41 | output_file='goog_data_large.pkl') 42 | 43 | goog_data, X, Y = create_regression_trading_condition(goog_data) 44 | 45 | X_train,X_test,Y_train,Y_test=create_train_split_group(X,Y,split_ratio=0.8) 46 | 47 | from sklearn import linear_model 48 | 49 | # Fit the model 50 | lasso = linear_model.Lasso(alpha=0.1) 51 | lasso.fit(X_train, Y_train) 52 | 53 | # The coefficients 54 | print('Coefficients: \n', lasso.coef_) 55 | 56 | goog_data['Predicted_Signal'] = lasso.predict(X) 57 | goog_data['GOOG_Returns'] = np.log( 58 | goog_data['Close'] / goog_data['Close'].shift(1)) 59 | 60 | print(goog_data.head()) 61 | def calculate_return(df, split_value, symbol): 62 | cum_goog_return = df[split_value:]['%s_Returns' % symbol].cumsum() * 100 63 | df['Strategy_Returns'] = df['%s_Returns' % 64 | symbol] * df['Predicted_Signal'].shift(1) 65 | return cum_goog_return 66 | 67 | 68 | def calculate_strategy_return(df, split_value, symbol): 69 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 70 | return cum_strategy_return 71 | 72 | 73 | cum_goog_return = calculate_return( 74 | goog_data, split_value=len(X_train), symbol='GOOG') 75 | cum_strategy_return = calculate_strategy_return( 76 | goog_data, split_value=len(X_train), symbol='GOOG') 77 | 78 | 79 | def plot_shart(cum_symbol_return, cum_strategy_return, symbol): 80 | plt.figure(figsize=(10, 5)) 81 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 82 | plt.plot(cum_strategy_return, label='Strategy Returns') 83 | plt.legend() 84 | plt.show() 85 | 86 | plot_shart(cum_goog_return, cum_strategy_return, symbol='GOOG') 87 | 88 | 89 | def sharpe_ratio(symbol_returns, strategy_returns): 90 | strategy_std = strategy_returns.std() 91 | sharpe = (strategy_returns - symbol_returns) / strategy_std 92 | return sharpe.mean() 93 | 94 | 95 | print(sharpe_ratio(cum_strategy_return, cum_goog_return)) 96 | 97 | from sklearn.metrics import mean_squared_error, r2_score 98 | 99 | # The mean squared error 100 | print("Mean squared error: %.2f" 101 | % mean_squared_error(Y_train, lasso.predict(X_train))) 102 | # Explained variance score: 1 is perfect prediction 103 | print('Variance score: %.2f' % r2_score(Y_train, lasso.predict(X_train))) 104 | 105 | # The mean squared error 106 | print("Mean squared error: %.2f" 107 | % mean_squared_error(Y_test, lasso.predict(X_test))) 108 | # Explained variance score: 1 is perfect prediction 109 | print('Variance score: %.2f' % r2_score(Y_test, lasso.predict(X_test))) 110 | -------------------------------------------------------------------------------- /Chapter3/lr.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.model_selection import train_test_split 5 | 6 | from pandas_datareader import data 7 | 8 | def load_financial_data(start_date, end_date, output_file): 9 | try: 10 | df = pd.read_pickle(output_file) 11 | print('File data found...reading GOOG data') 12 | except FileNotFoundError: 13 | print('File not found...downloading the GOOG data') 14 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 15 | df.to_pickle(output_file) 16 | return df 17 | 18 | def create_classification_trading_condition(df): 19 | df['Open-Close'] = df.Open - df.Close 20 | df['High-Low'] = df.High - df.Low 21 | df = df.dropna() 22 | X = df[['Open-Close', 'High-Low']] 23 | Y = np.where(df['Close'].shift(-1) > df['Close'], 1, -1) 24 | return (df, X, Y) 25 | 26 | def create_regression_trading_condition(df): 27 | df['Open-Close'] = df.Open - df.Close 28 | df['High-Low'] = df.High - df.Low 29 | df['Target'] = df['Close'].shift(-1) - df['Close'] 30 | df = df.dropna() 31 | X = df[['Open-Close', 'High-Low']] 32 | Y = df[['Target']] 33 | return (df, X, Y) 34 | 35 | def create_train_split_group(X, Y, split_ratio=0.8): 36 | return train_test_split(X, Y, shuffle=False, train_size=split_ratio) 37 | 38 | goog_data = load_financial_data( 39 | start_date='2001-01-01', 40 | end_date='2018-01-01', 41 | output_file='goog_data_large.pkl') 42 | 43 | goog_data, X, Y = create_regression_trading_condition(goog_data) 44 | 45 | X_train,X_test,Y_train,Y_test=create_train_split_group(X,Y,split_ratio=0.8) 46 | 47 | from sklearn import linear_model 48 | # Fit the model 49 | ols = linear_model.LinearRegression() 50 | ols.fit(X_train, Y_train) 51 | # Forecast value 52 | 53 | # The coefficients 54 | print('Coefficients: \n', ols.coef_) 55 | 56 | goog_data['Predicted_Signal'] = ols.predict(X) 57 | goog_data['GOOG_Returns'] = np.log( 58 | goog_data['Close'] / goog_data['Close'].shift(1)) 59 | 60 | print(goog_data.head()) 61 | def calculate_return(df, split_value, symbol): 62 | cum_goog_return = df[split_value:]['%s_Returns' % symbol].cumsum() * 100 63 | df['Strategy_Returns'] = df['%s_Returns' % 64 | symbol] * df['Predicted_Signal'].shift(1) 65 | return cum_goog_return 66 | 67 | 68 | def calculate_strategy_return(df, split_value, symbol): 69 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 70 | return cum_strategy_return 71 | 72 | 73 | cum_goog_return = calculate_return( 74 | goog_data, split_value=len(X_train), symbol='GOOG') 75 | cum_strategy_return = calculate_strategy_return( 76 | goog_data, split_value=len(X_train), symbol='GOOG') 77 | 78 | 79 | def plot_shart(cum_symbol_return, cum_strategy_return, symbol): 80 | plt.figure(figsize=(10, 5)) 81 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 82 | plt.plot(cum_strategy_return, label='Strategy Returns') 83 | plt.legend() 84 | plt.show() 85 | 86 | plot_shart(cum_goog_return, cum_strategy_return, symbol='GOOG') 87 | 88 | 89 | def sharpe_ratio(symbol_returns, strategy_returns): 90 | strategy_std = strategy_returns.std() 91 | sharpe = (strategy_returns - symbol_returns) / strategy_std 92 | return sharpe.mean() 93 | 94 | 95 | print(sharpe_ratio(cum_strategy_return, cum_goog_return)) 96 | 97 | from sklearn.metrics import mean_squared_error, r2_score 98 | 99 | # The mean squared error 100 | print("Mean squared error: %.2f" 101 | % mean_squared_error(Y_train, ols.predict(X_train))) 102 | # Explained variance score: 1 is perfect prediction 103 | print('Variance score: %.2f' % r2_score(Y_train, ols.predict(X_train))) 104 | 105 | # The mean squared error 106 | print("Mean squared error: %.2f" 107 | % mean_squared_error(Y_test, ols.predict(X_test))) 108 | # Explained variance score: 1 is perfect prediction 109 | print('Variance score: %.2f' % r2_score(Y_test, ols.predict(X_test))) 110 | -------------------------------------------------------------------------------- /Chapter3/ridge.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn.model_selection import train_test_split 5 | 6 | from pandas_datareader import data 7 | 8 | def load_financial_data(start_date, end_date, output_file): 9 | try: 10 | df = pd.read_pickle(output_file) 11 | print('File data found...reading GOOG data') 12 | except FileNotFoundError: 13 | print('File not found...downloading the GOOG data') 14 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 15 | df.to_pickle(output_file) 16 | return df 17 | 18 | def create_classification_trading_condition(df): 19 | df['Open-Close'] = df.Open - df.Close 20 | df['High-Low'] = df.High - df.Low 21 | df = df.dropna() 22 | X = df[['Open-Close', 'High-Low']] 23 | Y = np.where(df['Close'].shift(-1) > df['Close'], 1, -1) 24 | return (df, X, Y) 25 | 26 | def create_regression_trading_condition(df): 27 | df['Open-Close'] = df.Open - df.Close 28 | df['High-Low'] = df.High - df.Low 29 | df['Target'] = df['Close'].shift(-1) - df['Close'] 30 | df = df.dropna() 31 | X = df[['Open-Close', 'High-Low']] 32 | Y = df[['Target']] 33 | return (df, X, Y) 34 | 35 | def create_train_split_group(X, Y, split_ratio=0.8): 36 | return train_test_split(X, Y, shuffle=False, train_size=split_ratio) 37 | 38 | goog_data = load_financial_data( 39 | start_date='2001-01-01', 40 | end_date='2018-01-01', 41 | output_file='goog_data_large.pkl') 42 | 43 | goog_data, X, Y = create_regression_trading_condition(goog_data) 44 | 45 | X_train,X_test,Y_train,Y_test=create_train_split_group(X,Y,split_ratio=0.8) 46 | 47 | from sklearn import linear_model 48 | 49 | # Fit the model 50 | ridge = linear_model.Ridge(alpha=10000) 51 | ridge.fit(X_train, Y_train) 52 | 53 | # The coefficients 54 | print('Coefficients: \n', ridge.coef_) 55 | 56 | goog_data['Predicted_Signal'] = ridge.predict(X) 57 | goog_data['GOOG_Returns'] = np.log( 58 | goog_data['Close'] / goog_data['Close'].shift(1)) 59 | 60 | print(goog_data.head()) 61 | def calculate_return(df, split_value, symbol): 62 | cum_goog_return = df[split_value:]['%s_Returns' % symbol].cumsum() * 100 63 | df['Strategy_Returns'] = df['%s_Returns' % 64 | symbol] * df['Predicted_Signal'].shift(1) 65 | return cum_goog_return 66 | 67 | 68 | def calculate_strategy_return(df, split_value, symbol): 69 | cum_strategy_return = df[split_value:]['Strategy_Returns'].cumsum() * 100 70 | return cum_strategy_return 71 | 72 | 73 | cum_goog_return = calculate_return( 74 | goog_data, split_value=len(X_train), symbol='GOOG') 75 | cum_strategy_return = calculate_strategy_return( 76 | goog_data, split_value=len(X_train), symbol='GOOG') 77 | 78 | 79 | def plot_shart(cum_symbol_return, cum_strategy_return, symbol): 80 | plt.figure(figsize=(10, 5)) 81 | plt.plot(cum_symbol_return, label='%s Returns' % symbol) 82 | plt.plot(cum_strategy_return, label='Strategy Returns') 83 | plt.legend() 84 | plt.show() 85 | 86 | plot_shart(cum_goog_return, cum_strategy_return, symbol='GOOG') 87 | 88 | 89 | def sharpe_ratio(symbol_returns, strategy_returns): 90 | strategy_std = strategy_returns.std() 91 | sharpe = (strategy_returns - symbol_returns) / strategy_std 92 | return sharpe.mean() 93 | 94 | 95 | print(sharpe_ratio(cum_strategy_return, cum_goog_return)) 96 | 97 | from sklearn.metrics import mean_squared_error, r2_score 98 | 99 | # The mean squared error 100 | print("Mean squared error: %.2f" 101 | % mean_squared_error(Y_train, ridge.predict(X_train))) 102 | # Explained variance score: 1 is perfect prediction 103 | print('Variance score: %.2f' % r2_score(Y_train, ridge.predict(X_train))) 104 | 105 | # The mean squared error 106 | print("Mean squared error: %.2f" 107 | % mean_squared_error(Y_test, ridge.predict(X_test))) 108 | # Explained variance score: 1 is perfect prediction 109 | print('Variance score: %.2f' % r2_score(Y_test, ridge.predict(X_test))) 110 | -------------------------------------------------------------------------------- /Chapter3/scatter.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | 4 | from pandas_datareader import data 5 | 6 | def load_financial_data(start_date, end_date, output_file): 7 | try: 8 | df = pd.read_pickle(output_file) 9 | print('File data found...reading GOOG data') 10 | except FileNotFoundError: 11 | print('File not found...downloading the GOOG data') 12 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 13 | df.to_pickle(output_file) 14 | return df 15 | 16 | def create_regression_trading_condition(df): 17 | df['Open-Close'] = df.Open - df.Close 18 | df['High-Low'] = df.High - df.Low 19 | df['Target'] = df['Close'].shift(-1) - df['Close'] 20 | df = df.dropna() 21 | X = df[['Open-Close', 'High-Low']] 22 | Y = df[['Target']] 23 | return (df, X, Y) 24 | 25 | goog_data = load_financial_data( 26 | start_date='2001-01-01', 27 | end_date='2018-01-01', 28 | output_file='goog_data_large.pkl') 29 | 30 | create_regression_trading_condition(goog_data) 31 | 32 | pd.plotting.scatter_matrix(goog_data[['Open-Close', 'High-Low', 'Target']], grid=True, diagonal='kde', alpha=0.5) 33 | plt.show() 34 | -------------------------------------------------------------------------------- /Chapter4/ch4_double_moving_average.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import pandas as pd 3 | import numpy as np 4 | from pandas_datareader import data 5 | import matplotlib.pyplot as plt 6 | 7 | def load_financial_data(start_date, end_date,output_file): 8 | try: 9 | df = pd.read_pickle(output_file) 10 | print('File data found...reading GOOG data') 11 | except FileNotFoundError: 12 | print('File not found...downloading the GOOG data') 13 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 14 | df.to_pickle(output_file) 15 | return df 16 | 17 | goog_data=load_financial_data(start_date='2001-01-01', 18 | end_date = '2018-01-01', 19 | output_file='goog_data.pkl') 20 | 21 | 22 | 23 | def double_moving_average(financial_data, short_window, long_window): 24 | signals = pd.DataFrame(index=financial_data.index) 25 | signals['signal'] = 0.0 26 | signals['short_mavg'] = financial_data['Close'].\ 27 | rolling(window=short_window, 28 | min_periods=1, center=False).mean() 29 | signals['long_mavg'] = financial_data['Close'].\ 30 | rolling(window=long_window, 31 | min_periods=1, center=False).mean() 32 | signals['signal'][short_window:] =\ 33 | np.where(signals['short_mavg'][short_window:] 34 | > signals['long_mavg'][short_window:], 1.0, 0.0) 35 | signals['orders'] = signals['signal'].diff() 36 | return signals 37 | 38 | ts=double_moving_average(goog_data,20,100) 39 | 40 | fig = plt.figure() 41 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 42 | goog_data["Adj Close"].plot(ax=ax1, color='g', lw=.5) 43 | ts["short_mavg"].plot(ax=ax1, color='r', lw=2.) 44 | ts["long_mavg"].plot(ax=ax1, color='b', lw=2.) 45 | 46 | ax1.plot(ts.loc[ts.orders== 1.0].index, 47 | goog_data["Adj Close"][ts.orders == 1.0], 48 | '^', markersize=7, color='k') 49 | 50 | ax1.plot(ts.loc[ts.orders== -1.0].index, 51 | goog_data["Adj Close"][ts.orders == -1.0], 52 | 'v', markersize=7, color='k') 53 | 54 | plt.legend(["Price","Short mavg","Long mavg","Buy","Sell"]) 55 | plt.title("Double Moving Average Trading Strategy") 56 | 57 | plt.show() 58 | -------------------------------------------------------------------------------- /Chapter4/ch4_naive_momentum_strategy2.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import pandas as pd 3 | import numpy as np 4 | from pandas_datareader import data 5 | import matplotlib.pyplot as plt 6 | 7 | def load_financial_data(start_date, end_date,output_file): 8 | try: 9 | df = pd.read_pickle(output_file) 10 | print('File data found...reading GOOG data') 11 | except FileNotFoundError: 12 | print('File not found...downloading the GOOG data') 13 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 14 | df.to_pickle(output_file) 15 | return df 16 | 17 | goog_data=load_financial_data(start_date='2001-01-01', 18 | end_date = '2018-01-01', 19 | output_file='goog_data.pkl') 20 | 21 | 22 | 23 | def naive_momentum_trading(financial_data, nb_conseq_days): 24 | signals = pd.DataFrame(index=financial_data.index) 25 | signals['orders'] = 0 26 | cons_day=0 27 | prior_price=0 28 | init=True 29 | for k in range(len(financial_data['Adj Close'])): 30 | price=financial_data['Adj Close'][k] 31 | if init: 32 | prior_price=price 33 | init=False 34 | elif price>prior_price: 35 | if cons_day<0: 36 | cons_day=0 37 | cons_day+=1 38 | elif price0: 40 | cons_day=0 41 | cons_day-=1 42 | if cons_day==nb_conseq_days: 43 | signals['orders'][k]=1 44 | elif cons_day == -nb_conseq_days: 45 | signals['orders'][k]=-1 46 | 47 | 48 | return signals 49 | 50 | 51 | ts=naive_momentum_trading(goog_data, 5) 52 | 53 | fig = plt.figure() 54 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 55 | goog_data["Adj Close"].plot(ax=ax1, color='g', lw=.5) 56 | 57 | ax1.plot(ts.loc[ts.orders== 1.0].index, 58 | goog_data["Adj Close"][ts.orders == 1], 59 | '^', markersize=7, color='k') 60 | 61 | ax1.plot(ts.loc[ts.orders== -1.0].index, 62 | goog_data["Adj Close"][ts.orders == -1], 63 | 'v', markersize=7, color='k') 64 | 65 | plt.legend(["Price","Buy","Sell"]) 66 | plt.title("Naive Momentum Trading Strategy") 67 | 68 | plt.show() 69 | 70 | 71 | 72 | import sys 73 | sys.exit(0) -------------------------------------------------------------------------------- /Chapter4/ch4_pairs_correlation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from statsmodels.tsa.stattools import coint 4 | import matplotlib.pyplot as plt 5 | # Set a seed value to make the experience reproducible 6 | np.random.seed(123) 7 | 8 | # Generate Symbol1 daily returns 9 | Symbol1_returns = np.random.normal(0, 1, 100) 10 | # Create a series for Symbol1 prices 11 | Symbol1_prices = pd.Series(np.cumsum(Symbol1_returns),\ 12 | name='Symbol1') + 10 13 | Symbol1_prices.plot(figsize=(15,7)) 14 | plt.show() 15 | 16 | # Create a series for Symbol2 prices 17 | # We are going to mimikate the Symbol1 behavior 18 | noise = np.random.normal(0, 1, 100) 19 | Symbol2_prices = Symbol1_prices + 10 + noise 20 | Symbol2_prices.name = 'Symbol2' 21 | 22 | plt.title("Symbol 1 and Symbol 2 prices") 23 | Symbol1_prices.plot() 24 | Symbol2_prices.plot() 25 | plt.legend() 26 | plt.show() 27 | 28 | 29 | def zscore(series): 30 | return (series - series.mean()) / np.std(series) 31 | 32 | score, pvalue, _ = coint(Symbol1_prices, Symbol2_prices) 33 | print(pvalue) 34 | ratios = Symbol1_prices / Symbol2_prices 35 | plt.title("Ration between Symbol 1 and Symbol 2 price") 36 | 37 | ratios.plot() 38 | plt.show() 39 | 40 | 41 | #plt.axhline(ratios.mean()) 42 | #plt.legend([' Ratio']) 43 | 44 | 45 | 46 | zscore(ratios).plot() 47 | plt.title("Z-score evolution") 48 | plt.axhline(zscore(ratios).mean(),color="black") 49 | plt.axhline(1.0, color="red") 50 | plt.axhline(-1.0, color="green") 51 | plt.show() 52 | 53 | 54 | 55 | 56 | ratios.plot() 57 | buy = ratios.copy() 58 | sell = ratios.copy() 59 | buy[zscore(ratios)>-1] = 0 60 | sell[zscore(ratios)<1] = 0 61 | buy.plot(color="g", linestyle="None", marker="^") 62 | sell.plot(color="r", linestyle="None", marker="v") 63 | x1,x2,y1,y2 = plt.axis() 64 | plt.axis((x1,x2,ratios.min(),ratios.max())) 65 | plt.legend(["Ratio", "Buy Signal", "Sell Signal"]) 66 | plt.show() 67 | 68 | 69 | symbol1_buy=Symbol1_prices.copy() 70 | symbol1_sell=Symbol1_prices.copy() 71 | symbol2_buy=Symbol2_prices.copy() 72 | symbol2_sell=Symbol2_prices.copy() 73 | 74 | Symbol1_prices.plot() 75 | symbol1_buy[zscore(ratios)>-1] = 0 76 | symbol1_sell[zscore(ratios)<1] = 0 77 | symbol1_buy.plot(color="g", linestyle="None", marker="^") 78 | symbol1_sell.plot(color="r", linestyle="None", marker="v") 79 | 80 | Symbol2_prices.plot() 81 | symbol2_buy[zscore(ratios)<1] = 0 82 | symbol2_sell[zscore(ratios)>-1] = 0 83 | symbol2_buy.plot(color="g", linestyle="None", marker="^") 84 | symbol2_sell.plot(color="r", linestyle="None", marker="v") 85 | 86 | 87 | x1,x2,y1,y2 = plt.axis() 88 | plt.axis((x1,x2,Symbol1_prices.min(),Symbol2_prices.max())) 89 | plt.legend(["Symbol1", "Buy Signal", "Sell Signal","Symbol2"]) 90 | plt.show() 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Chapter4/ch4_pairs_correlation_init.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | pd.set_option('display.max_rows', 500) 3 | pd.set_option('display.max_columns', 500) 4 | pd.set_option('display.width', 1000) 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from statsmodels.tsa.stattools import coint 8 | import seaborn 9 | 10 | 11 | from pandas_datareader import data 12 | 13 | symbolsIds = ['SPY','AAPL','ADBE','LUV','MSFT',\ 14 | 'SKYW','QCOM', 15 | 'HPQ','JNPR','AMD','IBM'] 16 | 17 | def load_financial_data(symbols, start_date, end_date,output_file): 18 | try: 19 | df = pd.read_pickle(output_file) 20 | print('File data found...reading symbols data') 21 | except FileNotFoundError: 22 | print('File not found...downloading the symbols data') 23 | df = data.DataReader(symbols, 'yahoo', start_date, end_date) 24 | df.to_pickle(output_file) 25 | return df 26 | 27 | data=load_financial_data(symbolsIds,start_date='2001-01-01', 28 | end_date = '2018-01-01', 29 | output_file='multi_data_large.pkl') 30 | 31 | 32 | 33 | 34 | def find_cointegrated_pairs(data): 35 | n = data.shape[1] 36 | pvalue_matrix = np.ones((n, n)) 37 | keys = data.keys() 38 | pairs = [] 39 | for i in range(n): 40 | for j in range(i+1, n): 41 | result = coint(data[keys[i]], data[keys[j]]) 42 | pvalue_matrix[i, j] = result[1] 43 | if result[1] < 0.02: 44 | pairs.append((keys[i], keys[j])) 45 | return pvalue_matrix, pairs 46 | 47 | 48 | pvalues, pairs = find_cointegrated_pairs(data['Adj Close']) 49 | print(pairs) 50 | 51 | seaborn.heatmap(pvalues, xticklabels=symbolsIds, 52 | yticklabels=symbolsIds, cmap='RdYlGn_r', 53 | mask = (pvalues >= 0.98)) 54 | plt.show() 55 | print (pairs) 56 | 57 | 58 | print(data.head(3)) 59 | 60 | 61 | -------------------------------------------------------------------------------- /Chapter4/ch4_pairs_correlation_real_symbol.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from statsmodels.tsa.stattools import coint 4 | import matplotlib.pyplot as plt 5 | # Set a seed value to make the experience reproducible 6 | np.random.seed(123) 7 | 8 | 9 | import pandas as pd 10 | pd.set_option('display.max_rows', 500) 11 | pd.set_option('display.max_columns', 500) 12 | pd.set_option('display.width', 1000) 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from statsmodels.tsa.stattools import coint 16 | import seaborn 17 | 18 | 19 | from pandas_datareader import data 20 | 21 | symbolsIds = ['SPY','AAPL','ADBE','LUV','MSFT','SKYW','QCOM', 22 | 'HPQ','JNPR','AMD','IBM'] 23 | 24 | def load_financial_data(symbols, start_date, end_date,output_file): 25 | try: 26 | df = pd.read_pickle(output_file) 27 | print('File data found...reading symbols data') 28 | except FileNotFoundError: 29 | print('File not found...downloading the symbols data') 30 | df = data.DataReader(symbols, 'yahoo', start_date, end_date) 31 | df.to_pickle(output_file) 32 | return df 33 | 34 | data=load_financial_data(symbolsIds,start_date='2001-01-01', 35 | end_date = '2018-01-01', 36 | output_file='multi_data_large.pkl') 37 | 38 | 39 | Symbol1_prices = data['Adj Close']['MSFT'] 40 | Symbol1_prices.plot(figsize=(15,7)) 41 | plt.show() 42 | Symbol2_prices = data['Adj Close']['JNPR'] 43 | Symbol2_prices.name = 'JNPR' 44 | plt.title("MSFT and JNPR prices") 45 | Symbol1_prices.plot() 46 | Symbol2_prices.plot() 47 | plt.legend() 48 | plt.show() 49 | 50 | 51 | 52 | 53 | def zscore(series): 54 | return (series - series.mean()) / np.std(series) 55 | 56 | score, pvalue, _ = coint(Symbol1_prices, Symbol2_prices) 57 | print(pvalue) 58 | ratios = Symbol1_prices / Symbol2_prices 59 | plt.title("Ration between Symbol 1 and Symbol 2 price") 60 | 61 | ratios.plot() 62 | plt.show() 63 | 64 | 65 | #plt.axhline(ratios.mean()) 66 | #plt.legend([' Ratio']) 67 | 68 | 69 | 70 | zscore(ratios).plot() 71 | plt.title("Z-score evolution") 72 | plt.axhline(zscore(ratios).mean(),color="black") 73 | plt.axhline(1.0, color="red") 74 | plt.axhline(-1.0, color="green") 75 | plt.show() 76 | 77 | 78 | 79 | 80 | ratios.plot() 81 | buy = ratios.copy() 82 | sell = ratios.copy() 83 | buy[zscore(ratios)>-1] = 0 84 | sell[zscore(ratios)<1] = 0 85 | buy.plot(color="g", linestyle="None", marker="^") 86 | sell.plot(color="r", linestyle="None", marker="v") 87 | x1,x2,y1,y2 = plt.axis() 88 | plt.axis((x1,x2,ratios.min(),ratios.max())) 89 | plt.legend(["Ratio", "Buy Signal", "Sell Signal"]) 90 | plt.show() 91 | 92 | 93 | symbol1_buy=Symbol1_prices.copy() 94 | symbol1_sell=Symbol1_prices.copy() 95 | symbol2_buy=Symbol2_prices.copy() 96 | symbol2_sell=Symbol2_prices.copy() 97 | 98 | Symbol1_prices.plot() 99 | symbol1_buy[zscore(ratios)>-1] = 0 100 | symbol1_sell[zscore(ratios)<1] = 0 101 | symbol1_buy.plot(color="g", linestyle="None", marker="^") 102 | symbol1_sell.plot(color="r", linestyle="None", marker="v") 103 | 104 | pair_correlation_trading_strategy = pd.DataFrame(index=Symbol1_prices.index) 105 | pair_correlation_trading_strategy['symbol1_price']=Symbol1_prices 106 | pair_correlation_trading_strategy['symbol1_buy']=np.zeros(len(Symbol1_prices)) 107 | pair_correlation_trading_strategy['symbol1_sell']=np.zeros(len(Symbol1_prices)) 108 | pair_correlation_trading_strategy['symbol2_buy']=np.zeros(len(Symbol1_prices)) 109 | pair_correlation_trading_strategy['symbol2_sell']=np.zeros(len(Symbol1_prices)) 110 | 111 | 112 | position=0 113 | for i in range(len(Symbol1_prices)): 114 | s1price=Symbol1_prices[i] 115 | s2price=Symbol2_prices[i] 116 | if not position and symbol1_buy[i]!=0: 117 | pair_correlation_trading_strategy['symbol1_buy'][i]=s1price 118 | pair_correlation_trading_strategy['symbol2_sell'][i] = s2price 119 | position=1 120 | elif not position and symbol1_sell[i]!=0: 121 | pair_correlation_trading_strategy['symbol1_sell'][i] = s1price 122 | pair_correlation_trading_strategy['symbol2_buy'][i] = s2price 123 | position = -1 124 | elif position==-1 and (symbol1_sell[i]==0 or i==len(Symbol1_prices)-1): 125 | pair_correlation_trading_strategy['symbol1_buy'][i] = s1price 126 | pair_correlation_trading_strategy['symbol2_sell'][i] = s2price 127 | position = 0 128 | elif position==1 and (symbol1_buy[i] == 0 or i==len(Symbol1_prices)-1): 129 | pair_correlation_trading_strategy['symbol1_sell'][i] = s1price 130 | pair_correlation_trading_strategy['symbol2_buy'][i] = s2price 131 | position = 0 132 | 133 | 134 | 135 | 136 | Symbol2_prices.plot() 137 | symbol2_buy[zscore(ratios)<1] = 0 138 | symbol2_sell[zscore(ratios)>-1] = 0 139 | symbol2_buy.plot(color="g", linestyle="None", marker="^") 140 | symbol2_sell.plot(color="r", linestyle="None", marker="v") 141 | 142 | x1,x2,y1,y2 = plt.axis() 143 | plt.axis((x1,x2,Symbol1_prices.min(),Symbol2_prices.max())) 144 | plt.legend(["Symbol1", "Buy Signal", "Sell Signal","Symbol2"]) 145 | plt.show() 146 | 147 | 148 | 149 | 150 | Symbol1_prices.plot() 151 | pair_correlation_trading_strategy['symbol1_buy'].plot(color="g", linestyle="None", marker="^") 152 | pair_correlation_trading_strategy['symbol1_sell'].plot(color="r", linestyle="None", marker="v") 153 | Symbol2_prices.plot() 154 | pair_correlation_trading_strategy['symbol2_buy'].plot(color="g", linestyle="None", marker="^") 155 | pair_correlation_trading_strategy['symbol2_sell'].plot(color="r", linestyle="None", marker="v") 156 | x1,x2,y1,y2 = plt.axis() 157 | plt.axis((x1,x2,Symbol1_prices.min(),Symbol2_prices.max())) 158 | plt.legend(["Symbol1", "Buy Signal", "Sell Signal","Symbol2"]) 159 | plt.show() 160 | 161 | pair_correlation_trading_strategy['symbol1_buy'].head() 162 | 163 | 164 | pair_correlation_trading_strategy['symbol1_position']=\ 165 | pair_correlation_trading_strategy['symbol1_buy']-pair_correlation_trading_strategy['symbol1_sell'] 166 | 167 | pair_correlation_trading_strategy['symbol2_position']=\ 168 | pair_correlation_trading_strategy['symbol2_buy']-pair_correlation_trading_strategy['symbol2_sell'] 169 | 170 | pair_correlation_trading_strategy['symbol1_position'].cumsum().plot() 171 | pair_correlation_trading_strategy['symbol2_position'].cumsum().plot() 172 | 173 | pair_correlation_trading_strategy['total_position']=\ 174 | pair_correlation_trading_strategy['symbol1_position']+pair_correlation_trading_strategy['symbol2_position'] 175 | pair_correlation_trading_strategy['total_position'].cumsum().plot() 176 | plt.title("Symbol 1 and Symbol 2 positions") 177 | plt.legend() 178 | plt.show() 179 | 180 | 181 | 182 | pair_correlation_trading_strategy['symbol1_price']=Symbol1_prices 183 | pair_correlation_trading_strategy['symbol1_buy']=np.zeros(len(Symbol1_prices)) 184 | pair_correlation_trading_strategy['symbol1_sell']=np.zeros(len(Symbol1_prices)) 185 | pair_correlation_trading_strategy['symbol2_buy']=np.zeros(len(Symbol1_prices)) 186 | pair_correlation_trading_strategy['symbol2_sell']=np.zeros(len(Symbol1_prices)) 187 | pair_correlation_trading_strategy['delta']=np.zeros(len(Symbol1_prices)) 188 | 189 | 190 | position=0 191 | s1_shares = 1000000 192 | for i in range(len(Symbol1_prices)): 193 | s1positions= Symbol1_prices[i] * s1_shares 194 | s2positions= Symbol2_prices[i] * int(s1positions/Symbol2_prices[i]) 195 | print(Symbol1_prices[i],Symbol2_prices[i]) 196 | delta_position=s1positions-s2positions 197 | if not position and symbol1_buy[i]!=0: 198 | pair_correlation_trading_strategy['symbol1_buy'][i]=s1positions 199 | pair_correlation_trading_strategy['symbol2_sell'][i] = s2positions 200 | pair_correlation_trading_strategy['delta'][i]=delta_position 201 | position=1 202 | elif not position and symbol1_sell[i]!=0: 203 | pair_correlation_trading_strategy['symbol1_sell'][i] = s1positions 204 | pair_correlation_trading_strategy['symbol2_buy'][i] = s2positions 205 | pair_correlation_trading_strategy['delta'][i] = delta_position 206 | position = -1 207 | elif position==-1 and (symbol1_sell[i]==0 or i==len(Symbol1_prices)-1): 208 | pair_correlation_trading_strategy['symbol1_buy'][i] = s1positions 209 | pair_correlation_trading_strategy['symbol2_sell'][i] = s2positions 210 | position = 0 211 | elif position==1 and (symbol1_buy[i] == 0 or i==len(Symbol1_prices)-1): 212 | pair_correlation_trading_strategy['symbol1_sell'][i] = s1positions 213 | pair_correlation_trading_strategy['symbol2_buy'][i] = s2positions 214 | position = 0 215 | 216 | 217 | pair_correlation_trading_strategy['symbol1_position']=\ 218 | pair_correlation_trading_strategy['symbol1_buy']-pair_correlation_trading_strategy['symbol1_sell'] 219 | 220 | pair_correlation_trading_strategy['symbol2_position']=\ 221 | pair_correlation_trading_strategy['symbol2_buy']-pair_correlation_trading_strategy['symbol2_sell'] 222 | 223 | pair_correlation_trading_strategy['symbol1_position'].cumsum().plot() 224 | pair_correlation_trading_strategy['symbol2_position'].cumsum().plot() 225 | 226 | pair_correlation_trading_strategy['total_position']=\ 227 | pair_correlation_trading_strategy['symbol1_position']+pair_correlation_trading_strategy['symbol2_position'] 228 | pair_correlation_trading_strategy['total_position'].cumsum().plot() 229 | plt.title("Symbol 1 and Symbol 2 positions") 230 | plt.legend() 231 | plt.show() 232 | 233 | 234 | pair_correlation_trading_strategy['delta'].plot() 235 | plt.title("Delta Position") 236 | plt.show() -------------------------------------------------------------------------------- /Chapter4/ch4_turtle_trading.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import pandas as pd 3 | import numpy as np 4 | from pandas_datareader import data 5 | import matplotlib.pyplot as plt 6 | 7 | def load_financial_data(start_date, end_date,output_file): 8 | try: 9 | df = pd.read_pickle(output_file) 10 | print('File data found...reading GOOG data') 11 | except FileNotFoundError: 12 | print('File not found...downloading the GOOG data') 13 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 14 | df.to_pickle(output_file) 15 | return df 16 | 17 | goog_data=load_financial_data(start_date='2001-01-01', 18 | end_date = '2018-01-01', 19 | output_file='goog_data.pkl') 20 | 21 | 22 | 23 | def turtle_trading(financial_data, window_size): 24 | signals = pd.DataFrame(index=financial_data.index) 25 | signals['orders'] = 0 26 | # window_size-days high 27 | signals['high'] = financial_data['Adj Close'].shift(1).\ 28 | rolling(window=window_size).max() 29 | # window_size-days low 30 | signals['low'] = financial_data['Adj Close'].shift(1).\ 31 | rolling(window=window_size).min() 32 | # window_size-days mean 33 | signals['avg'] = financial_data['Adj Close'].shift(1).\ 34 | rolling(window=window_size).mean() 35 | 36 | # entry rule : stock price > the higest value for window_size day 37 | # stock price < the lowest value for window_size day 38 | 39 | signals['long_entry'] = financial_data['Adj Close'] > signals.high 40 | signals['short_entry'] = financial_data['Adj Close'] < signals.low 41 | 42 | #exit rule : the stock price crosses the mean of past window_size days. 43 | 44 | signals['long_exit'] = financial_data['Adj Close'] < signals.avg 45 | signals['short_exit'] = financial_data['Adj Close'] > signals.avg 46 | 47 | init=True 48 | position=0 49 | for k in range(len(signals)): 50 | if signals['long_entry'][k] and position==0: 51 | signals.orders.values[k] = 1 52 | position=1 53 | elif signals['short_entry'][k] and position==0: 54 | signals.orders.values[k] = -1 55 | position=-1 56 | elif signals['short_exit'][k] and position>0: 57 | signals.orders.values[k] = -1 58 | position = 0 59 | elif signals['long_exit'][k] and position < 0: 60 | signals.orders.values[k] = 1 61 | position = 0 62 | else: 63 | signals.orders.values[k] = 0 64 | 65 | return signals 66 | 67 | ts=turtle_trading(goog_data, 50) 68 | 69 | fig = plt.figure() 70 | ax1 = fig.add_subplot(111, ylabel='Google price in $') 71 | goog_data["Adj Close"].plot(ax=ax1, color='g', lw=.5) 72 | ts["high"].plot(ax=ax1, color='g', lw=.5) 73 | ts["low"].plot(ax=ax1, color='r', lw=.5) 74 | ts["avg"].plot(ax=ax1, color='b', lw=.5) 75 | 76 | 77 | ax1.plot(ts.loc[ts.orders== 1.0].index, 78 | goog_data["Adj Close"][ts.orders == 1.0], 79 | '^', markersize=7, color='k') 80 | 81 | ax1.plot(ts.loc[ts.orders== -1.0].index, 82 | goog_data["Adj Close"][ts.orders == -1.0], 83 | 'v', markersize=7, color='k') 84 | 85 | 86 | # 87 | # ax1.plot(ts.loc[ts.long_entry== True].index, 88 | # goog_data["Adj Close"][ts.long_entry== True], 89 | # '^', markersize=7, color='k') 90 | # 91 | # ax1.plot(ts.loc[ts.short_entry== True].index, 92 | # goog_data["Adj Close"][ts.short_entry== True], 93 | # 'v', markersize=7, color='k') 94 | # 95 | # ax1.plot(ts.loc[ts.long_exit == True].index, 96 | # goog_data["Adj Close"][ts.long_exit == True], 97 | # 'v', markersize=7, color='k') 98 | # 99 | # ax1.plot(ts.loc[ts.short_exit == True].index, 100 | # goog_data["Adj Close"][ts.short_exit == True], 101 | # 'v', markersize=7, color='k') 102 | 103 | 104 | plt.legend(["Price","Highs","Lows","Average","Buy","Sell"]) 105 | plt.title("Turtle Trading Strategy") 106 | 107 | plt.show() 108 | 109 | 110 | import sys 111 | sys.exit(0) 112 | 113 | # You are going to set your initial amount of money you want 114 | # to invest --- here it is 10,000 115 | initial_capital = float(10000.0) 116 | 117 | # You are going to create a new dataframe positions 118 | # Remember the index is still the same as signals 119 | positions = pd.DataFrame(index=signals.index).fillna(0.0) 120 | 121 | # You are going to buy 10 shares of MSFT when signal is 1 122 | # You are going to sell 10 shares of MSFT when signal is -1 123 | # You will assign these values to the column MSFT of the 124 | # dataframe positions 125 | positions['MSFT'] = 10 * signals['signal'] 126 | 127 | # You are now going to calculate the notional (quantity x price) 128 | # for your portfolio. You will multiply Adj Close from 129 | # the dataframe containing prices and the positions (10 shares) 130 | # You will store it into the variable portfolio 131 | portfolio = positions.multiply(financial_data['Adj Close'], axis=0) 132 | 133 | # Add `holdings` to portfolio 134 | portfolio['holdings'] = (positions.multiply(financial_data['Adj Close'], axis=0)).sum(axis=1) 135 | 136 | # You will store positions.diff into pos_diff 137 | pos_diff = positions.diff() 138 | # You will now add a column cash in your dataframe portfolio 139 | # which will calculate the amount of cash you have 140 | # initial_capital - (the notional you use for your different buy/sell) 141 | portfolio['cash'] = initial_capital - (pos_diff.multiply(financial_data['Adj Close'], axis=0)).sum(axis=1).cumsum() 142 | 143 | # You will now add a column total to your portfolio calculating the part of holding 144 | # and the part of cash 145 | portfolio['total'] = portfolio['cash'] + portfolio['holdings'] 146 | 147 | # Add `returns` to portfolio 148 | portfolio['returns'] = portfolio['total'].pct_change() 149 | 150 | # Print the first lines of `portfolio` 151 | print(portfolio) 152 | 153 | 154 | -------------------------------------------------------------------------------- /Chapter5/AUDUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/AUDUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/CADUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/CADUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/CHFUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/CHFUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/EURUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/EURUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/GBPUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/GBPUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/GOOG_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/GOOG_data.pkl -------------------------------------------------------------------------------- /Chapter5/JPYUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/JPYUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/NZDUSD=X_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter5/NZDUSD=X_data.pkl -------------------------------------------------------------------------------- /Chapter5/basic_mean_reversion.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas_datareader import data 3 | 4 | # Fetch daily data for 4 years 5 | SYMBOL='GOOG' 6 | start_date = '2014-01-01' 7 | end_date = '2018-01-01' 8 | SRC_DATA_FILENAME=SYMBOL + '_data.pkl' 9 | 10 | try: 11 | data = pd.read_pickle(SRC_DATA_FILENAME) 12 | except FileNotFoundError: 13 | data = data.DataReader(SYMBOL, 'yahoo', start_date, end_date) 14 | data.to_pickle(SRC_DATA_FILENAME) 15 | 16 | # Variables/constants for EMA Calculation: 17 | NUM_PERIODS_FAST = 10 # Static time period parameter for the fast EMA 18 | K_FAST = 2 / (NUM_PERIODS_FAST + 1) # Static smoothing factor parameter for fast EMA 19 | ema_fast = 0 20 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 21 | 22 | NUM_PERIODS_SLOW = 40 # Static time period parameter for slow EMA 23 | K_SLOW = 2 / (NUM_PERIODS_SLOW + 1) # Static smoothing factor parameter for slow EMA 24 | ema_slow = 0 25 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 26 | 27 | apo_values = [] # track computed absolute price oscillator value signals 28 | 29 | # Variables for Trading Strategy trade, position & pnl management: 30 | orders = [] # Container for tracking buy/sell order, +1 for buy order, -1 for sell order, 0 for no-action 31 | positions = [] # Container for tracking positions, +ve for long positions, -ve for short positions, 0 for flat/no position 32 | pnls = [] # Container for tracking total_pnls, this is the sum of closed_pnl i.e. pnls already locked in and open_pnl i.e. pnls for open-position marked to market price 33 | 34 | last_buy_price = 0 # Price at which last buy trade was made, used to prevent over-trading at/around the same price 35 | last_sell_price = 0 # Price at which last sell trade was made, used to prevent over-trading at/around the same price 36 | position = 0 # Current position of the trading strategy 37 | buy_sum_price_qty = 0 # Summation of products of buy_trade_price and buy_trade_qty for every buy Trade made since last time being flat 38 | buy_sum_qty = 0 # Summation of buy_trade_qty for every buy Trade made since last time being flat 39 | sell_sum_price_qty = 0 # Summation of products of sell_trade_price and sell_trade_qty for every sell Trade made since last time being flat 40 | sell_sum_qty = 0 # Summation of sell_trade_qty for every sell Trade made since last time being flat 41 | open_pnl = 0 # Open/Unrealized PnL marked to market 42 | closed_pnl = 0 # Closed/Realized PnL so far 43 | 44 | # Constants that define strategy behavior/thresholds 45 | APO_VALUE_FOR_BUY_ENTRY = -10 # APO trading signal value below which to enter buy-orders/long-position 46 | APO_VALUE_FOR_SELL_ENTRY = 10 # APO trading signal value above which to enter sell-orders/short-position 47 | MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # Minimum price change since last trade before considering trading again, this is to prevent over-trading at/around same prices 48 | NUM_SHARES_PER_TRADE = 10 # Number of shares to buy/sell on every trade 49 | MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE # Minimum Open/Unrealized profit at which to close positions and lock profits 50 | 51 | close=data['Close'] 52 | for close_price in close: 53 | # This section updates fast and slow EMA and computes APO trading signal 54 | if (ema_fast == 0): # first observation 55 | ema_fast = close_price 56 | ema_slow = close_price 57 | else: 58 | ema_fast = (close_price - ema_fast) * K_FAST + ema_fast 59 | ema_slow = (close_price - ema_slow) * K_SLOW + ema_slow 60 | 61 | ema_fast_values.append(ema_fast) 62 | ema_slow_values.append(ema_slow) 63 | 64 | apo = ema_fast - ema_slow 65 | apo_values.append(apo) 66 | 67 | # This section checks trading signal against trading parameters/thresholds and positions, to trade. 68 | 69 | # We will perform a sell trade at close_price if the following conditions are met: 70 | # 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 71 | # 2. We are long( +ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 72 | if ((apo > APO_VALUE_FOR_SELL_ENTRY and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE) # APO above sell entry threshold, we should sell 73 | or 74 | (position > 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE))): # long from -ve APO and APO has gone positive or position is profitable, sell to close position 75 | orders.append(-1) # mark the sell trade 76 | last_sell_price = close_price 77 | position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade 78 | sell_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update vwap sell-price 79 | sell_sum_qty += NUM_SHARES_PER_TRADE 80 | print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 81 | 82 | # We will perform a buy trade at close_price if the following conditions are met: 83 | # 1. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 84 | # 2. We are short( -ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 85 | elif ((apo < APO_VALUE_FOR_BUY_ENTRY and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE) # APO below buy entry threshold, we should buy 86 | or 87 | (position < 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE))): # short from +ve APO and APO has gone negative or position is profitable, buy to close position 88 | orders.append(+1) # mark the buy trade 89 | last_buy_price = close_price 90 | position += NUM_SHARES_PER_TRADE # increase position by the size of this trade 91 | buy_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update the vwap buy-price 92 | buy_sum_qty += NUM_SHARES_PER_TRADE 93 | print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 94 | else: 95 | # No trade since none of the conditions were met to buy or sell 96 | orders.append(0) 97 | 98 | positions.append(position) 99 | 100 | # This section updates Open/Unrealized & Closed/Realized positions 101 | open_pnl = 0 102 | if position > 0: 103 | if sell_sum_qty > 0: # long position and some sell trades have been made against it, close that amount based on how much was sold against this long position 104 | open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 105 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 106 | open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty) 107 | elif position < 0: 108 | if buy_sum_qty > 0: # short position and some buy trades have been made against it, close that amount based on how much was bought against this short position 109 | open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 110 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 111 | open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty/sell_sum_qty - close_price) 112 | else: 113 | # flat, so update closed_pnl and reset tracking variables for positions & pnls 114 | closed_pnl += (sell_sum_price_qty - buy_sum_price_qty) 115 | buy_sum_price_qty = 0 116 | buy_sum_qty = 0 117 | sell_sum_price_qty = 0 118 | sell_sum_qty = 0 119 | last_buy_price = 0 120 | last_sell_price = 0 121 | 122 | print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) ) 123 | pnls.append(closed_pnl + open_pnl) 124 | 125 | # This section prepares the dataframe from the trading strategy results and visualizes the results 126 | data = data.assign(ClosePrice=pd.Series(close, index=data.index)) 127 | data = data.assign(Fast10DayEMA=pd.Series(ema_fast_values, index=data.index)) 128 | data = data.assign(Slow40DayEMA=pd.Series(ema_slow_values, index=data.index)) 129 | data = data.assign(APO=pd.Series(apo_values, index=data.index)) 130 | data = data.assign(Trades=pd.Series(orders, index=data.index)) 131 | data = data.assign(Position=pd.Series(positions, index=data.index)) 132 | data = data.assign(Pnl=pd.Series(pnls, index=data.index)) 133 | 134 | import matplotlib.pyplot as plt 135 | 136 | data['ClosePrice'].plot(color='blue', lw=3., legend=True) 137 | data['Fast10DayEMA'].plot(color='y', lw=1., legend=True) 138 | data['Slow40DayEMA'].plot(color='m', lw=1., legend=True) 139 | plt.plot(data.loc[ data.Trades == 1 ].index, data.ClosePrice[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 140 | plt.plot(data.loc[ data.Trades == -1 ].index, data.ClosePrice[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 141 | plt.legend() 142 | plt.show() 143 | 144 | data['APO'].plot(color='k', lw=3., legend=True) 145 | plt.plot(data.loc[ data.Trades == 1 ].index, data.APO[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 146 | plt.plot(data.loc[ data.Trades == -1 ].index, data.APO[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 147 | plt.axhline(y=0, lw=0.5, color='k') 148 | for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ): 149 | plt.axhline(y=i, lw=0.5, color='r') 150 | for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ): 151 | plt.axhline(y=i, lw=0.5, color='g') 152 | plt.legend() 153 | plt.show() 154 | 155 | data['Position'].plot(color='k', lw=1., legend=True) 156 | plt.plot(data.loc[ data.Position == 0 ].index, data.Position[ data.Position == 0 ], color='k', lw=0, marker='.', label='flat') 157 | plt.plot(data.loc[ data.Position > 0 ].index, data.Position[ data.Position > 0 ], color='r', lw=0, marker='+', label='long') 158 | plt.plot(data.loc[ data.Position < 0 ].index, data.Position[ data.Position < 0 ], color='g', lw=0, marker='_', label='short') 159 | plt.axhline(y=0, lw=0.5, color='k') 160 | for i in range( NUM_SHARES_PER_TRADE, NUM_SHARES_PER_TRADE*25, NUM_SHARES_PER_TRADE*5 ): 161 | plt.axhline(y=i, lw=0.5, color='r') 162 | for i in range( -NUM_SHARES_PER_TRADE, -NUM_SHARES_PER_TRADE*25, -NUM_SHARES_PER_TRADE*5 ): 163 | plt.axhline(y=i, lw=0.5, color='g') 164 | plt.legend() 165 | plt.show() 166 | 167 | data['Pnl'].plot(color='k', lw=1., legend=True) 168 | plt.plot(data.loc[ data.Pnl > 0 ].index, data.Pnl[ data.Pnl > 0 ], color='g', lw=0, marker='.') 169 | plt.plot(data.loc[ data.Pnl < 0 ].index, data.Pnl[ data.Pnl < 0 ], color='r', lw=0, marker='.') 170 | plt.legend() 171 | plt.show() 172 | 173 | data.to_csv("basic_mean_reversion.csv", sep=",") -------------------------------------------------------------------------------- /Chapter5/basic_trend_following.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas_datareader import data 3 | 4 | # Fetch daily data for 4 years 5 | SYMBOL='GOOG' 6 | start_date = '2014-01-01' 7 | end_date = '2018-01-01' 8 | SRC_DATA_FILENAME=SYMBOL + '_data.pkl' 9 | 10 | try: 11 | data = pd.read_pickle(SRC_DATA_FILENAME) 12 | except FileNotFoundError: 13 | data = data.DataReader(SYMBOL, 'yahoo', start_date, end_date) 14 | data.to_pickle(SRC_DATA_FILENAME) 15 | 16 | # Variables/constants for EMA Calculation: 17 | NUM_PERIODS_FAST = 10 # Static time period parameter for the fast EMA 18 | K_FAST = 2 / (NUM_PERIODS_FAST + 1) # Static smoothing factor parameter for fast EMA 19 | ema_fast = 0 20 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 21 | 22 | NUM_PERIODS_SLOW = 40 # Static time period parameter for slow EMA 23 | K_SLOW = 2 / (NUM_PERIODS_SLOW + 1) # Static smoothing factor parameter for slow EMA 24 | ema_slow = 0 25 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 26 | 27 | apo_values = [] # track computed absolute price oscillator value signals 28 | 29 | # Variables for Trading Strategy trade, position & pnl management: 30 | orders = [] # Container for tracking buy/sell order, +1 for buy order, -1 for sell order, 0 for no-action 31 | positions = [] # Container for tracking positions, +ve for long positions, -ve for short positions, 0 for flat/no position 32 | pnls = [] # Container for tracking total_pnls, this is the sum of closed_pnl i.e. pnls already locked in and open_pnl i.e. pnls for open-position marked to market price 33 | 34 | last_buy_price = 0 # Price at which last buy trade was made, used to prevent over-trading at/around the same price 35 | last_sell_price = 0 # Price at which last sell trade was made, used to prevent over-trading at/around the same price 36 | position = 0 # Current position of the trading strategy 37 | buy_sum_price_qty = 0 # Summation of products of buy_trade_price and buy_trade_qty for every buy Trade made since last time being flat 38 | buy_sum_qty = 0 # Summation of buy_trade_qty for every buy Trade made since last time being flat 39 | sell_sum_price_qty = 0 # Summation of products of sell_trade_price and sell_trade_qty for every sell Trade made since last time being flat 40 | sell_sum_qty = 0 # Summation of sell_trade_qty for every sell Trade made since last time being flat 41 | open_pnl = 0 # Open/Unrealized PnL marked to market 42 | closed_pnl = 0 # Closed/Realized PnL so far 43 | 44 | # Constants that define strategy behavior/thresholds 45 | APO_VALUE_FOR_BUY_ENTRY = -10 # APO trading signal value above which to enter buy-orders/long-position 46 | APO_VALUE_FOR_SELL_ENTRY = 10 # APO trading signal value below which to enter sell-orders/short-position 47 | MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # Minimum price change since last trade before considering trading again, this is to prevent over-trading at/around same prices 48 | NUM_SHARES_PER_TRADE = 10 # Number of shares to buy/sell on every trade 49 | MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE # Minimum Open/Unrealized profit at which to close positions and lock profits 50 | 51 | close=data['Close'] 52 | for close_price in close: 53 | # This section updates fast and slow EMA and computes APO trading signal 54 | if (ema_fast == 0): # first observation 55 | ema_fast = close_price 56 | ema_slow = close_price 57 | else: 58 | ema_fast = (close_price - ema_fast) * K_FAST + ema_fast 59 | ema_slow = (close_price - ema_slow) * K_SLOW + ema_slow 60 | 61 | ema_fast_values.append(ema_fast) 62 | ema_slow_values.append(ema_slow) 63 | 64 | apo = ema_fast - ema_slow 65 | apo_values.append(apo) 66 | 67 | # This section checks trading signal against trading parameters/thresholds and positions, to trade. 68 | 69 | # We will perform a sell trade at close_price if the following conditions are met: 70 | # 1. The APO trading signal value is below Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 71 | # 2. We are long( +ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 72 | if ((apo > APO_VALUE_FOR_SELL_ENTRY and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE) # APO above sell entry threshold, we should sell 73 | or 74 | (position > 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE))): # long from +ve APO and APO has gone negative or position is profitable, sell to close position 75 | orders.append(-1) # mark the sell trade 76 | last_sell_price = close_price 77 | position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade 78 | sell_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update vwap sell-price 79 | sell_sum_qty += NUM_SHARES_PER_TRADE 80 | print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 81 | 82 | # We will perform a buy trade at close_price if the following conditions are met: 83 | # 1. The APO trading signal value is above Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 84 | # 2. We are short( -ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 85 | elif ((apo > APO_VALUE_FOR_BUY_ENTRY and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE) # APO above buy entry threshold, we should buy 86 | or 87 | (position < 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE))): # short from -ve APO and APO has gone positive or position is profitable, buy to close position 88 | orders.append(+1) # mark the buy trade 89 | last_buy_price = close_price 90 | position += NUM_SHARES_PER_TRADE # increase position by the size of this trade 91 | buy_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update the vwap buy-price 92 | buy_sum_qty += NUM_SHARES_PER_TRADE 93 | print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 94 | else: 95 | # No trade since none of the conditions were met to buy or sell 96 | orders.append(0) 97 | 98 | positions.append(position) 99 | 100 | # This section updates Open/Unrealized & Closed/Realized positions 101 | open_pnl = 0 102 | if position > 0: 103 | if sell_sum_qty > 0: # long position and some sell trades have been made against it, close that amount based on how much was sold against this long position 104 | open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 105 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 106 | open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty) 107 | elif position < 0: 108 | if buy_sum_qty > 0: # short position and some buy trades have been made against it, close that amount based on how much was bought against this short position 109 | open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 110 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 111 | open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty/sell_sum_qty - close_price) 112 | else: 113 | # flat, so update closed_pnl and reset tracking variables for positions & pnls 114 | closed_pnl += (sell_sum_price_qty - buy_sum_price_qty) 115 | buy_sum_price_qty = 0 116 | buy_sum_qty = 0 117 | sell_sum_price_qty = 0 118 | sell_sum_qty = 0 119 | last_buy_price = 0 120 | last_sell_price = 0 121 | 122 | print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) ) 123 | pnls.append(closed_pnl + open_pnl) 124 | 125 | # This section prepares the dataframe from the trading strategy results and visualizes the results 126 | data = data.assign(ClosePrice=pd.Series(close, index=data.index)) 127 | data = data.assign(Fast10DayEMA=pd.Series(ema_fast_values, index=data.index)) 128 | data = data.assign(Slow40DayEMA=pd.Series(ema_slow_values, index=data.index)) 129 | data = data.assign(APO=pd.Series(apo_values, index=data.index)) 130 | data = data.assign(Trades=pd.Series(orders, index=data.index)) 131 | data = data.assign(Position=pd.Series(positions, index=data.index)) 132 | data = data.assign(Pnl=pd.Series(pnls, index=data.index)) 133 | 134 | import matplotlib.pyplot as plt 135 | 136 | data['ClosePrice'].plot(color='blue', lw=3., legend=True) 137 | data['Fast10DayEMA'].plot(color='y', lw=1., legend=True) 138 | data['Slow40DayEMA'].plot(color='m', lw=1., legend=True) 139 | plt.plot(data.loc[ data.Trades == 1 ].index, data.ClosePrice[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 140 | plt.plot(data.loc[ data.Trades == -1 ].index, data.ClosePrice[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 141 | plt.legend() 142 | plt.show() 143 | 144 | data['APO'].plot(color='k', lw=3., legend=True) 145 | plt.plot(data.loc[ data.Trades == 1 ].index, data.APO[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 146 | plt.plot(data.loc[ data.Trades == -1 ].index, data.APO[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 147 | plt.axhline(y=0, lw=0.5, color='k') 148 | for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ): 149 | plt.axhline(y=i, lw=0.5, color='r') 150 | for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ): 151 | plt.axhline(y=i, lw=0.5, color='g') 152 | plt.legend() 153 | plt.show() 154 | 155 | data['Position'].plot(color='k', lw=1., legend=True) 156 | plt.plot(data.loc[ data.Position == 0 ].index, data.Position[ data.Position == 0 ], color='k', lw=0, marker='.', label='flat') 157 | plt.plot(data.loc[ data.Position > 0 ].index, data.Position[ data.Position > 0 ], color='r', lw=0, marker='+', label='long') 158 | plt.plot(data.loc[ data.Position < 0 ].index, data.Position[ data.Position < 0 ], color='g', lw=0, marker='_', label='short') 159 | plt.axhline(y=0, lw=0.5, color='k') 160 | for i in range( NUM_SHARES_PER_TRADE, NUM_SHARES_PER_TRADE*25, NUM_SHARES_PER_TRADE*5 ): 161 | plt.axhline(y=i, lw=0.5, color='r') 162 | for i in range( -NUM_SHARES_PER_TRADE, -NUM_SHARES_PER_TRADE*25, -NUM_SHARES_PER_TRADE*5 ): 163 | plt.axhline(y=i, lw=0.5, color='g') 164 | plt.legend() 165 | plt.show() 166 | 167 | data['Pnl'].plot(color='k', lw=1., legend=True) 168 | plt.plot(data.loc[ data.Pnl > 0 ].index, data.Pnl[ data.Pnl > 0 ], color='g', lw=0, marker='.') 169 | plt.plot(data.loc[ data.Pnl < 0 ].index, data.Pnl[ data.Pnl < 0 ], color='r', lw=0, marker='.') 170 | plt.legend() 171 | plt.show() 172 | 173 | data.to_csv("basic_trend_following.csv", sep=",") -------------------------------------------------------------------------------- /Chapter5/compare_csvs.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | basic_mr = pd.read_csv('basic_trend_following.csv') 4 | vol_mr = pd.read_csv('volatility_adjusted_trend_following.csv') 5 | 6 | import matplotlib.pyplot as plt 7 | 8 | basic_mr['BasicTrendFollowingPnl'].plot(x='Date', color='b', lw=1., legend=True) 9 | vol_mr['VolatilityAdjustedTrendFollowingPnl'].plot(x='Date', color='g', lw=1., legend=True) 10 | plt.show() 11 | -------------------------------------------------------------------------------- /Chapter5/volatility_mean_reversion.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas_datareader import data 3 | 4 | # Fetch daily data for 4 years 5 | SYMBOL='GOOG' 6 | start_date = '2014-01-01' 7 | end_date = '2018-01-01' 8 | SRC_DATA_FILENAME=SYMBOL + '_data.pkl' 9 | 10 | try: 11 | data = pd.read_pickle(SRC_DATA_FILENAME) 12 | except FileNotFoundError: 13 | data = data.DataReader(SYMBOL, 'yahoo', start_date, end_date) 14 | data.to_pickle(SRC_DATA_FILENAME) 15 | 16 | # Variables/constants for EMA Calculation: 17 | NUM_PERIODS_FAST = 10 # Static time period parameter for the fast EMA 18 | K_FAST = 2 / (NUM_PERIODS_FAST + 1) # Static smoothing factor parameter for fast EMA 19 | ema_fast = 0 20 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 21 | 22 | NUM_PERIODS_SLOW = 40 # Static time period parameter for slow EMA 23 | K_SLOW = 2 / (NUM_PERIODS_SLOW + 1) # Static smoothing factor parameter for slow EMA 24 | ema_slow = 0 25 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 26 | 27 | apo_values = [] # track computed absolute price oscillator value signals 28 | 29 | # Variables for Trading Strategy trade, position & pnl management: 30 | orders = [] # Container for tracking buy/sell order, +1 for buy order, -1 for sell order, 0 for no-action 31 | positions = [] # Container for tracking positions, +ve for long positions, -ve for short positions, 0 for flat/no position 32 | pnls = [] # Container for tracking total_pnls, this is the sum of closed_pnl i.e. pnls already locked in and open_pnl i.e. pnls for open-position marked to market price 33 | 34 | last_buy_price = 0 # Price at which last buy trade was made, used to prevent over-trading at/around the same price 35 | last_sell_price = 0 # Price at which last sell trade was made, used to prevent over-trading at/around the same price 36 | position = 0 # Current position of the trading strategy 37 | buy_sum_price_qty = 0 # Summation of products of buy_trade_price and buy_trade_qty for every buy Trade made since last time being flat 38 | buy_sum_qty = 0 # Summation of buy_trade_qty for every buy Trade made since last time being flat 39 | sell_sum_price_qty = 0 # Summation of products of sell_trade_price and sell_trade_qty for every sell Trade made since last time being flat 40 | sell_sum_qty = 0 # Summation of sell_trade_qty for every sell Trade made since last time being flat 41 | open_pnl = 0 # Open/Unrealized PnL marked to market 42 | closed_pnl = 0 # Closed/Realized PnL so far 43 | 44 | # Constants that define strategy behavior/thresholds 45 | APO_VALUE_FOR_BUY_ENTRY = -10 # APO trading signal value below which to enter buy-orders/long-position 46 | APO_VALUE_FOR_SELL_ENTRY = 10 # APO trading signal value above which to enter sell-orders/short-position 47 | MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # Minimum price change since last trade before considering trading again, this is to prevent over-trading at/around same prices 48 | NUM_SHARES_PER_TRADE = 10 # Number of shares to buy/sell on every trade 49 | MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE # Minimum Open/Unrealized profit at which to close positions and lock profits 50 | 51 | import statistics as stats 52 | import math as math 53 | 54 | # Constants/variables that are used to compute standard deviation as a volatility measure 55 | SMA_NUM_PERIODS = 20 # look back period 56 | price_history = [] # history of prices 57 | 58 | close=data['Close'] 59 | for close_price in close: 60 | price_history.append(close_price) 61 | if len(price_history) > SMA_NUM_PERIODS: # we track at most 'time_period' number of prices 62 | del (price_history[0]) 63 | 64 | sma = stats.mean(price_history) 65 | variance = 0 # variance is square of standard deviation 66 | for hist_price in price_history: 67 | variance = variance + ((hist_price - sma) ** 2) 68 | 69 | stdev = math.sqrt(variance / len(price_history)) 70 | stdev_factor = stdev/15 71 | if stdev_factor == 0: 72 | stdev_factor = 1 73 | 74 | # This section updates fast and slow EMA and computes APO trading signal 75 | if (ema_fast == 0): # first observation 76 | ema_fast = close_price 77 | ema_slow = close_price 78 | else: 79 | ema_fast = (close_price - ema_fast) * K_FAST*stdev_factor + ema_fast 80 | ema_slow = (close_price - ema_slow) * K_SLOW*stdev_factor + ema_slow 81 | 82 | ema_fast_values.append(ema_fast) 83 | ema_slow_values.append(ema_slow) 84 | 85 | apo = ema_fast - ema_slow 86 | apo_values.append(apo) 87 | 88 | # This section checks trading signal against trading parameters/thresholds and positions, to trade. 89 | 90 | # We will perform a sell trade at close_price if the following conditions are met: 91 | # 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 92 | # 2. We are long( +ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 93 | if ((apo > APO_VALUE_FOR_SELL_ENTRY*stdev_factor and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO above sell entry threshold, we should sell 94 | or 95 | (position > 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # long from -ve APO and APO has gone positive or position is profitable, sell to close position 96 | orders.append(-1) # mark the sell trade 97 | last_sell_price = close_price 98 | position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade 99 | sell_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update vwap sell-price 100 | sell_sum_qty += NUM_SHARES_PER_TRADE 101 | print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 102 | 103 | # We will perform a buy trade at close_price if the following conditions are met: 104 | # 1. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 105 | # 2. We are short( -ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 106 | elif ((apo < APO_VALUE_FOR_BUY_ENTRY*stdev_factor and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO below buy entry threshold, we should buy 107 | or 108 | (position < 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # short from +ve APO and APO has gone negative or position is profitable, buy to close position 109 | orders.append(+1) # mark the buy trade 110 | last_buy_price = close_price 111 | position += NUM_SHARES_PER_TRADE # increase position by the size of this trade 112 | buy_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update the vwap buy-price 113 | buy_sum_qty += NUM_SHARES_PER_TRADE 114 | print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 115 | else: 116 | # No trade since none of the conditions were met to buy or sell 117 | orders.append(0) 118 | 119 | positions.append(position) 120 | 121 | # This section updates Open/Unrealized & Closed/Realized positions 122 | open_pnl = 0 123 | if position > 0: 124 | if sell_sum_qty > 0: # long position and some sell trades have been made against it, close that amount based on how much was sold against this long position 125 | open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 126 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 127 | open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty) 128 | elif position < 0: 129 | if buy_sum_qty > 0: # short position and some buy trades have been made against it, close that amount based on how much was bought against this short position 130 | open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 131 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 132 | open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty/sell_sum_qty - close_price) 133 | else: 134 | # flat, so update closed_pnl and reset tracking variables for positions & pnls 135 | closed_pnl += (sell_sum_price_qty - buy_sum_price_qty) 136 | buy_sum_price_qty = 0 137 | buy_sum_qty = 0 138 | sell_sum_price_qty = 0 139 | sell_sum_qty = 0 140 | last_buy_price = 0 141 | last_sell_price = 0 142 | 143 | print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) ) 144 | pnls.append(closed_pnl + open_pnl) 145 | 146 | # This section prepares the dataframe from the trading strategy results and visualizes the results 147 | data = data.assign(ClosePrice=pd.Series(close, index=data.index)) 148 | data = data.assign(Fast10DayEMA=pd.Series(ema_fast_values, index=data.index)) 149 | data = data.assign(Slow40DayEMA=pd.Series(ema_slow_values, index=data.index)) 150 | data = data.assign(APO=pd.Series(apo_values, index=data.index)) 151 | data = data.assign(Trades=pd.Series(orders, index=data.index)) 152 | data = data.assign(Position=pd.Series(positions, index=data.index)) 153 | data = data.assign(Pnl=pd.Series(pnls, index=data.index)) 154 | 155 | import matplotlib.pyplot as plt 156 | 157 | data['ClosePrice'].plot(color='blue', lw=3., legend=True) 158 | data['Fast10DayEMA'].plot(color='y', lw=1., legend=True) 159 | data['Slow40DayEMA'].plot(color='m', lw=1., legend=True) 160 | plt.plot(data.loc[ data.Trades == 1 ].index, data.ClosePrice[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 161 | plt.plot(data.loc[ data.Trades == -1 ].index, data.ClosePrice[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 162 | plt.legend() 163 | plt.show() 164 | 165 | data['APO'].plot(color='k', lw=3., legend=True) 166 | plt.plot(data.loc[ data.Trades == 1 ].index, data.APO[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 167 | plt.plot(data.loc[ data.Trades == -1 ].index, data.APO[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 168 | plt.axhline(y=0, lw=0.5, color='k') 169 | for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ): 170 | plt.axhline(y=i, lw=0.5, color='r') 171 | for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ): 172 | plt.axhline(y=i, lw=0.5, color='g') 173 | plt.legend() 174 | plt.show() 175 | 176 | data['Position'].plot(color='k', lw=1., legend=True) 177 | plt.plot(data.loc[ data.Position == 0 ].index, data.Position[ data.Position == 0 ], color='k', lw=0, marker='.', label='flat') 178 | plt.plot(data.loc[ data.Position > 0 ].index, data.Position[ data.Position > 0 ], color='r', lw=0, marker='+', label='long') 179 | plt.plot(data.loc[ data.Position < 0 ].index, data.Position[ data.Position < 0 ], color='g', lw=0, marker='_', label='short') 180 | plt.axhline(y=0, lw=0.5, color='k') 181 | for i in range( NUM_SHARES_PER_TRADE, NUM_SHARES_PER_TRADE*25, NUM_SHARES_PER_TRADE*5 ): 182 | plt.axhline(y=i, lw=0.5, color='r') 183 | for i in range( -NUM_SHARES_PER_TRADE, -NUM_SHARES_PER_TRADE*25, -NUM_SHARES_PER_TRADE*5 ): 184 | plt.axhline(y=i, lw=0.5, color='g') 185 | plt.legend() 186 | plt.show() 187 | 188 | data['Pnl'].plot(color='k', lw=1., legend=True) 189 | plt.plot(data.loc[ data.Pnl > 0 ].index, data.Pnl[ data.Pnl > 0 ], color='g', lw=0, marker='.') 190 | plt.plot(data.loc[ data.Pnl < 0 ].index, data.Pnl[ data.Pnl < 0 ], color='r', lw=0, marker='.') 191 | plt.legend() 192 | plt.show() 193 | 194 | data.to_csv("volatility_adjusted_mean_reversion.csv", sep=",") -------------------------------------------------------------------------------- /Chapter5/volatility_trend_following.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas_datareader import data 3 | 4 | # Fetch daily data for 4 years 5 | SYMBOL='GOOG' 6 | start_date = '2014-01-01' 7 | end_date = '2018-01-01' 8 | SRC_DATA_FILENAME=SYMBOL + '_data.pkl' 9 | 10 | try: 11 | data = pd.read_pickle(SRC_DATA_FILENAME) 12 | except FileNotFoundError: 13 | data = data.DataReader(SYMBOL, 'yahoo', start_date, end_date) 14 | data.to_pickle(SRC_DATA_FILENAME) 15 | 16 | # Variables/constants for EMA Calculation: 17 | NUM_PERIODS_FAST = 10 # Static time period parameter for the fast EMA 18 | K_FAST = 2 / (NUM_PERIODS_FAST + 1) # Static smoothing factor parameter for fast EMA 19 | ema_fast = 0 20 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 21 | 22 | NUM_PERIODS_SLOW = 40 # Static time period parameter for slow EMA 23 | K_SLOW = 2 / (NUM_PERIODS_SLOW + 1) # Static smoothing factor parameter for slow EMA 24 | ema_slow = 0 25 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 26 | 27 | apo_values = [] # track computed absolute price oscillator value signals 28 | 29 | # Variables for Trading Strategy trade, position & pnl management: 30 | orders = [] # Container for tracking buy/sell order, +1 for buy order, -1 for sell order, 0 for no-action 31 | positions = [] # Container for tracking positions, +ve for long positions, -ve for short positions, 0 for flat/no position 32 | pnls = [] # Container for tracking total_pnls, this is the sum of closed_pnl i.e. pnls already locked in and open_pnl i.e. pnls for open-position marked to market price 33 | 34 | last_buy_price = 0 # Price at which last buy trade was made, used to prevent over-trading at/around the same price 35 | last_sell_price = 0 # Price at which last sell trade was made, used to prevent over-trading at/around the same price 36 | position = 0 # Current position of the trading strategy 37 | buy_sum_price_qty = 0 # Summation of products of buy_trade_price and buy_trade_qty for every buy Trade made since last time being flat 38 | buy_sum_qty = 0 # Summation of buy_trade_qty for every buy Trade made since last time being flat 39 | sell_sum_price_qty = 0 # Summation of products of sell_trade_price and sell_trade_qty for every sell Trade made since last time being flat 40 | sell_sum_qty = 0 # Summation of sell_trade_qty for every sell Trade made since last time being flat 41 | open_pnl = 0 # Open/Unrealized PnL marked to market 42 | closed_pnl = 0 # Closed/Realized PnL so far 43 | 44 | # Constants that define strategy behavior/thresholds 45 | APO_VALUE_FOR_BUY_ENTRY = 10 # APO trading signal value above which to enter buy-orders/long-position 46 | APO_VALUE_FOR_SELL_ENTRY = -10 # APO trading signal value below which to enter sell-orders/short-position 47 | MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # Minimum price change since last trade before considering trading again, this is to prevent over-trading at/around same prices 48 | NUM_SHARES_PER_TRADE = 10 # Number of shares to buy/sell on every trade 49 | MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE # Minimum Open/Unrealized profit at which to close positions and lock profits 50 | 51 | import statistics as stats 52 | import math as math 53 | 54 | # Constants/variables that are used to compute standard deviation as a volatility measure 55 | SMA_NUM_PERIODS = 20 # look back period 56 | price_history = [] # history of prices 57 | 58 | close=data['Close'] 59 | for close_price in close: 60 | price_history.append(close_price) 61 | if len(price_history) > SMA_NUM_PERIODS: # we track at most 'time_period' number of prices 62 | del (price_history[0]) 63 | 64 | sma = stats.mean(price_history) 65 | variance = 0 # variance is square of standard deviation 66 | for hist_price in price_history: 67 | variance = variance + ((hist_price - sma) ** 2) 68 | 69 | stdev = math.sqrt(variance / len(price_history)) 70 | stdev_factor = stdev/15 71 | if stdev_factor == 0: 72 | stdev_factor = 1 73 | 74 | # This section updates fast and slow EMA and computes APO trading signal 75 | if (ema_fast == 0): # first observation 76 | ema_fast = close_price 77 | ema_slow = close_price 78 | else: 79 | ema_fast = (close_price - ema_fast) * K_FAST*stdev_factor + ema_fast 80 | ema_slow = (close_price - ema_slow) * K_SLOW*stdev_factor + ema_slow 81 | 82 | ema_fast_values.append(ema_fast) 83 | ema_slow_values.append(ema_slow) 84 | 85 | apo = ema_fast - ema_slow 86 | apo_values.append(apo) 87 | 88 | # This section checks trading signal against trading parameters/thresholds and positions, to trade. 89 | 90 | # We will perform a sell trade at close_price if the following conditions are met: 91 | # 1. The APO trading signal value is below Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 92 | # 2. We are long( +ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 93 | if ((apo < APO_VALUE_FOR_SELL_ENTRY/stdev_factor and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO below sell entry threshold, we should sell 94 | or 95 | (position > 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # long from +ve APO and APO has gone negative or position is profitable, sell to close position 96 | orders.append(-1) # mark the sell trade 97 | last_sell_price = close_price 98 | position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade 99 | sell_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update vwap sell-price 100 | sell_sum_qty += NUM_SHARES_PER_TRADE 101 | print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 102 | 103 | # We will perform a buy trade at close_price if the following conditions are met: 104 | # 1. The APO trading signal value is above Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 105 | # 2. We are short( -ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 106 | elif ((apo > APO_VALUE_FOR_BUY_ENTRY/stdev_factor and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stdev_factor) # APO above buy entry threshold, we should buy 107 | or 108 | (position < 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE/stdev_factor))): # short from -ve APO and APO has gone positive or position is profitable, buy to close position 109 | orders.append(+1) # mark the buy trade 110 | last_buy_price = close_price 111 | position += NUM_SHARES_PER_TRADE # increase position by the size of this trade 112 | buy_sum_price_qty += (close_price*NUM_SHARES_PER_TRADE) # update the vwap buy-price 113 | buy_sum_qty += NUM_SHARES_PER_TRADE 114 | print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position ) 115 | else: 116 | # No trade since none of the conditions were met to buy or sell 117 | orders.append(0) 118 | 119 | positions.append(position) 120 | 121 | # This section updates Open/Unrealized & Closed/Realized positions 122 | open_pnl = 0 123 | if position > 0: 124 | if sell_sum_qty > 0: # long position and some sell trades have been made against it, close that amount based on how much was sold against this long position 125 | open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 126 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 127 | open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty) 128 | elif position < 0: 129 | if buy_sum_qty > 0: # short position and some buy trades have been made against it, close that amount based on how much was bought against this short position 130 | open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty) 131 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 132 | open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty/sell_sum_qty - close_price) 133 | else: 134 | # flat, so update closed_pnl and reset tracking variables for positions & pnls 135 | closed_pnl += (sell_sum_price_qty - buy_sum_price_qty) 136 | buy_sum_price_qty = 0 137 | buy_sum_qty = 0 138 | sell_sum_price_qty = 0 139 | sell_sum_qty = 0 140 | last_buy_price = 0 141 | last_sell_price = 0 142 | 143 | print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) ) 144 | pnls.append(closed_pnl + open_pnl) 145 | 146 | # This section prepares the dataframe from the trading strategy results and visualizes the results 147 | data = data.assign(ClosePrice=pd.Series(close, index=data.index)) 148 | data = data.assign(Fast10DayEMA=pd.Series(ema_fast_values, index=data.index)) 149 | data = data.assign(Slow40DayEMA=pd.Series(ema_slow_values, index=data.index)) 150 | data = data.assign(APO=pd.Series(apo_values, index=data.index)) 151 | data = data.assign(Trades=pd.Series(orders, index=data.index)) 152 | data = data.assign(Position=pd.Series(positions, index=data.index)) 153 | data = data.assign(Pnl=pd.Series(pnls, index=data.index)) 154 | 155 | import matplotlib.pyplot as plt 156 | 157 | data['ClosePrice'].plot(color='blue', lw=3., legend=True) 158 | data['Fast10DayEMA'].plot(color='y', lw=1., legend=True) 159 | data['Slow40DayEMA'].plot(color='m', lw=1., legend=True) 160 | plt.plot(data.loc[ data.Trades == 1 ].index, data.ClosePrice[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 161 | plt.plot(data.loc[ data.Trades == -1 ].index, data.ClosePrice[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 162 | plt.legend() 163 | plt.show() 164 | 165 | data['APO'].plot(color='k', lw=3., legend=True) 166 | plt.plot(data.loc[ data.Trades == 1 ].index, data.APO[data.Trades == 1 ], color='r', lw=0, marker='^', markersize=7, label='buy') 167 | plt.plot(data.loc[ data.Trades == -1 ].index, data.APO[data.Trades == -1 ], color='g', lw=0, marker='v', markersize=7, label='sell') 168 | plt.axhline(y=0, lw=0.5, color='k') 169 | for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ): 170 | plt.axhline(y=i, lw=0.5, color='r') 171 | for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ): 172 | plt.axhline(y=i, lw=0.5, color='g') 173 | plt.legend() 174 | plt.show() 175 | 176 | data['Position'].plot(color='k', lw=1., legend=True) 177 | plt.plot(data.loc[ data.Position == 0 ].index, data.Position[ data.Position == 0 ], color='k', lw=0, marker='.', label='flat') 178 | plt.plot(data.loc[ data.Position > 0 ].index, data.Position[ data.Position > 0 ], color='r', lw=0, marker='+', label='long') 179 | plt.plot(data.loc[ data.Position < 0 ].index, data.Position[ data.Position < 0 ], color='g', lw=0, marker='_', label='short') 180 | plt.axhline(y=0, lw=0.5, color='k') 181 | for i in range( NUM_SHARES_PER_TRADE, NUM_SHARES_PER_TRADE*25, NUM_SHARES_PER_TRADE*5 ): 182 | plt.axhline(y=i, lw=0.5, color='r') 183 | for i in range( -NUM_SHARES_PER_TRADE, -NUM_SHARES_PER_TRADE*25, -NUM_SHARES_PER_TRADE*5 ): 184 | plt.axhline(y=i, lw=0.5, color='g') 185 | plt.legend() 186 | plt.show() 187 | 188 | data['Pnl'].plot(color='k', lw=1., legend=True) 189 | plt.plot(data.loc[ data.Pnl > 0 ].index, data.Pnl[ data.Pnl > 0 ], color='g', lw=0, marker='.') 190 | plt.plot(data.loc[ data.Pnl < 0 ].index, data.Pnl[ data.Pnl < 0 ], color='r', lw=0, marker='.') 191 | plt.legend() 192 | plt.show() 193 | 194 | data.to_csv("volatility_adjusted_trend_following.csv", sep=",") -------------------------------------------------------------------------------- /Chapter6/risk_measures.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | 4 | results = pd.read_csv('volatility_adjusted_mean_reversion.csv') 5 | 6 | num_days = len(results.index) 7 | 8 | pnl = results['Pnl'] 9 | 10 | weekly_losses = [] 11 | monthly_losses = [] 12 | 13 | for i in range(0, num_days): 14 | if i >= 5 and pnl[i - 5] > pnl[i]: 15 | weekly_losses.append(pnl[i] - pnl[i - 5]) 16 | 17 | if i >= 20 and pnl[i - 20] > pnl[i]: 18 | monthly_losses.append(pnl[i] - pnl[i - 20]) 19 | 20 | plt.hist(weekly_losses, 50) 21 | plt.gca().set(title='Weekly Loss Distribution', xlabel='$', ylabel='Frequency') 22 | plt.show() 23 | 24 | plt.hist(monthly_losses, 50) 25 | plt.gca().set(title='Monthly Loss Distribution', xlabel='$', ylabel='Frequency') 26 | plt.show() 27 | 28 | max_pnl = 0 29 | max_drawdown = 0 30 | drawdown_max_pnl = 0 31 | drawdown_min_pnl = 0 32 | 33 | for i in range(0, num_days): 34 | max_pnl = max(max_pnl, pnl[i]) 35 | drawdown = max_pnl - pnl[i] 36 | 37 | if drawdown > max_drawdown: 38 | max_drawdown = drawdown 39 | drawdown_max_pnl = max_pnl 40 | drawdown_min_pnl = pnl[i] 41 | 42 | print('Max Drawdown:', max_drawdown) 43 | 44 | results['Pnl'].plot(x='Date', legend=True) 45 | plt.axhline(y=drawdown_max_pnl, color='g') 46 | plt.axhline(y=drawdown_min_pnl, color='r') 47 | plt.show() 48 | 49 | position = results['Position'] 50 | plt.hist(position, 20) 51 | plt.gca().set(title='Position Distribution', xlabel='Shares', ylabel='Frequency') 52 | plt.show() 53 | 54 | position_holding_times = [] 55 | current_pos = 0 56 | current_pos_start = 0 57 | for i in range(0, num_days): 58 | pos = results['Position'].iloc[i] 59 | 60 | # flat and starting a new position 61 | if current_pos == 0: 62 | if pos != 0: 63 | current_pos = pos 64 | current_pos_start = i 65 | continue 66 | 67 | # going from long position to flat or short position or 68 | # going from short position to flat or long position 69 | if current_pos * pos <= 0: 70 | current_pos = pos 71 | position_holding_times.append(i - current_pos_start) 72 | current_pos_start = i 73 | 74 | print(position_holding_times) 75 | plt.hist(position_holding_times, 100) 76 | plt.gca().set(title='Position Holding Time Distribution', xlabel='Holding time days', ylabel='Frequency') 77 | plt.show() 78 | 79 | last_week = 0 80 | weekly_pnls = [] 81 | weekly_losses = [] 82 | for i in range(0, num_days): 83 | if i - last_week >= 5: 84 | pnl_change = pnl[i] - pnl[last_week] 85 | weekly_pnls.append(pnl_change) 86 | if pnl_change < 0: 87 | weekly_losses.append(pnl_change) 88 | last_week = i 89 | 90 | from statistics import stdev, mean 91 | print('PnL Standard Deviation:', stdev(weekly_pnls)) 92 | 93 | plt.hist(weekly_pnls, 50) 94 | plt.gca().set(title='Weekly PnL Distribution', xlabel='$', ylabel='Frequency') 95 | plt.show() 96 | 97 | sharpe_ratio = mean(weekly_pnls) / stdev(weekly_pnls) 98 | sortino_ratio = mean(weekly_pnls) / stdev(weekly_losses) 99 | 100 | print('Sharpe ratio:', sharpe_ratio) 101 | print('Sortino ratio:', sortino_ratio) 102 | 103 | executions_this_week = 0 104 | executions_per_week = [] 105 | last_week = 0 106 | for i in range(0, num_days): 107 | if results['Trades'].iloc[i] != 0: 108 | executions_this_week += 1 109 | 110 | if i - last_week >= 5: 111 | executions_per_week.append(executions_this_week) 112 | executions_this_week = 0 113 | last_week = i 114 | 115 | plt.hist(executions_per_week, 10) 116 | plt.gca().set(title='Weekly number of executions Distribution', xlabel='Number of executions', ylabel='Frequency') 117 | plt.show() 118 | 119 | executions_this_month = 0 120 | executions_per_month = [] 121 | last_month = 0 122 | for i in range(0, num_days): 123 | if results['Trades'].iloc[i] != 0: 124 | executions_this_month += 1 125 | 126 | if i - last_month >= 20: 127 | executions_per_month.append(executions_this_month) 128 | executions_this_month = 0 129 | last_month = i 130 | 131 | plt.hist(executions_per_month, 20) 132 | plt.gca().set(title='Monthly number of executions Distribution', xlabel='Number of executions', ylabel='Frequency') 133 | plt.show() 134 | 135 | traded_volume = 0 136 | for i in range(0, num_days): 137 | if results['Trades'].iloc[i] != 0: 138 | traded_volume += abs(results['Position'].iloc[i] - results['Position'].iloc[i-1]) 139 | 140 | print('Total traded volume:', traded_volume) -------------------------------------------------------------------------------- /Chapter6/volatility_mean_reversion_with_risk_checks.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas_datareader import data 3 | 4 | # Fetch daily data for 4 years 5 | SYMBOL = 'GOOG' 6 | start_date = '2014-01-01' 7 | end_date = '2018-01-01' 8 | SRC_DATA_FILENAME = SYMBOL + '_data.pkl' 9 | 10 | try: 11 | data = pd.read_pickle(SRC_DATA_FILENAME) 12 | except FileNotFoundError: 13 | data = data.DataReader(SYMBOL, 'yahoo', start_date, end_date) 14 | data.to_pickle(SRC_DATA_FILENAME) 15 | 16 | # Variables/constants for EMA Calculation: 17 | NUM_PERIODS_FAST = 10 # Static time period parameter for the fast EMA 18 | K_FAST = 2 / (NUM_PERIODS_FAST + 1) # Static smoothing factor parameter for fast EMA 19 | ema_fast = 0 20 | ema_fast_values = [] # we will hold fast EMA values for visualization purposes 21 | 22 | NUM_PERIODS_SLOW = 40 # Static time period parameter for slow EMA 23 | K_SLOW = 2 / (NUM_PERIODS_SLOW + 1) # Static smoothing factor parameter for slow EMA 24 | ema_slow = 0 25 | ema_slow_values = [] # we will hold slow EMA values for visualization purposes 26 | 27 | apo_values = [] # track computed absolute price oscillator value signals 28 | 29 | # Variables for Trading Strategy trade, position & pnl management: 30 | orders = [] # Container for tracking buy/sell order, +1 for buy order, -1 for sell order, 0 for no-action 31 | positions = [] # Container for tracking positions, +ve for long positions, -ve for short positions, 0 for flat/no position 32 | pnls = [] # Container for tracking total_pnls, this is the sum of closed_pnl i.e. pnls already locked in and open_pnl i.e. pnls for open-position marked to market price 33 | 34 | last_buy_price = 0 # Price at which last buy trade was made, used to prevent over-trading at/around the same price 35 | last_sell_price = 0 # Price at which last sell trade was made, used to prevent over-trading at/around the same price 36 | position = 0 # Current position of the trading strategy 37 | buy_sum_price_qty = 0 # Summation of products of buy_trade_price and buy_trade_qty for every buy Trade made since last time being flat 38 | buy_sum_qty = 0 # Summation of buy_trade_qty for every buy Trade made since last time being flat 39 | sell_sum_price_qty = 0 # Summation of products of sell_trade_price and sell_trade_qty for every sell Trade made since last time being flat 40 | sell_sum_qty = 0 # Summation of sell_trade_qty for every sell Trade made since last time being flat 41 | open_pnl = 0 # Open/Unrealized PnL marked to market 42 | closed_pnl = 0 # Closed/Realized PnL so far 43 | 44 | # Constants that define strategy behavior/thresholds 45 | APO_VALUE_FOR_BUY_ENTRY = -10 # APO trading signal value below which to enter buy-orders/long-position 46 | APO_VALUE_FOR_SELL_ENTRY = 10 # APO trading signal value above which to enter sell-orders/short-position 47 | MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # Minimum price change since last trade before considering trading again, this is to prevent over-trading at/around same prices 48 | NUM_SHARES_PER_TRADE = 10 # Number of shares to buy/sell on every trade 49 | MIN_PROFIT_TO_CLOSE = 10 * NUM_SHARES_PER_TRADE # Minimum Open/Unrealized profit at which to close positions and lock profits 50 | 51 | import statistics as stats 52 | import math as math 53 | 54 | # Constants/variables that are used to compute standard deviation as a volatility measure 55 | SMA_NUM_PERIODS = 20 # look back period 56 | price_history = [] # history of prices 57 | 58 | # Risk limits 59 | RISK_LIMIT_WEEKLY_STOP_LOSS = -12000 * 1.5 60 | RISK_LIMIT_MONTHLY_STOP_LOSS = -14000 * 1.5 61 | RISK_LIMIT_MAX_POSITION = 250 * 1.5 62 | RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS = 120 * 1.5 63 | RISK_LIMIT_MAX_TRADE_SIZE = 10 * 1.5 64 | RISK_LIMIT_MAX_TRADED_VOLUME = 4000 * 1.5 65 | 66 | risk_violated = False 67 | 68 | traded_volume = 0 69 | current_pos = 0 70 | current_pos_start = 0 71 | 72 | close = data['Close'] 73 | for close_price in close: 74 | price_history.append(close_price) 75 | if len(price_history) > SMA_NUM_PERIODS: # we track at most 'time_period' number of prices 76 | del (price_history[0]) 77 | 78 | sma = stats.mean(price_history) 79 | variance = 0 # variance is square of standard deviation 80 | for hist_price in price_history: 81 | variance = variance + ((hist_price - sma) ** 2) 82 | 83 | stdev = math.sqrt(variance / len(price_history)) 84 | stdev_factor = stdev / 15 85 | if stdev_factor == 0: 86 | stdev_factor = 1 87 | 88 | # This section updates fast and slow EMA and computes APO trading signal 89 | if (ema_fast == 0): # first observation 90 | ema_fast = close_price 91 | ema_slow = close_price 92 | else: 93 | ema_fast = (close_price - ema_fast) * K_FAST * stdev_factor + ema_fast 94 | ema_slow = (close_price - ema_slow) * K_SLOW * stdev_factor + ema_slow 95 | 96 | ema_fast_values.append(ema_fast) 97 | ema_slow_values.append(ema_slow) 98 | 99 | apo = ema_fast - ema_slow 100 | apo_values.append(apo) 101 | 102 | if NUM_SHARES_PER_TRADE > RISK_LIMIT_MAX_TRADE_SIZE: 103 | print('RiskViolation NUM_SHARES_PER_TRADE', NUM_SHARES_PER_TRADE, ' > RISK_LIMIT_MAX_TRADE_SIZE', RISK_LIMIT_MAX_TRADE_SIZE ) 104 | risk_violated = True 105 | 106 | # This section checks trading signal against trading parameters/thresholds and positions, to trade. 107 | 108 | # We will perform a sell trade at close_price if the following conditions are met: 109 | # 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 110 | # 2. We are long( +ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 111 | if (not risk_violated and 112 | ((apo > APO_VALUE_FOR_SELL_ENTRY * stdev_factor and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE * stdev_factor) # APO above sell entry threshold, we should sell 113 | or 114 | (position > 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE / stdev_factor)))): # long from -ve APO and APO has gone positive or position is profitable, sell to close position 115 | orders.append(-1) # mark the sell trade 116 | last_sell_price = close_price 117 | position -= NUM_SHARES_PER_TRADE # reduce position by the size of this trade 118 | sell_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update vwap sell-price 119 | sell_sum_qty += NUM_SHARES_PER_TRADE 120 | traded_volume += NUM_SHARES_PER_TRADE 121 | print("Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position) 122 | 123 | # We will perform a buy trade at close_price if the following conditions are met: 124 | # 1. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 125 | # 2. We are short( -ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 126 | elif (not risk_violated and 127 | ((apo < APO_VALUE_FOR_BUY_ENTRY * stdev_factor and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE * stdev_factor) # APO below buy entry threshold, we should buy 128 | or 129 | (position < 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE / stdev_factor)))): # short from +ve APO and APO has gone negative or position is profitable, buy to close position 130 | orders.append(+1) # mark the buy trade 131 | last_buy_price = close_price 132 | position += NUM_SHARES_PER_TRADE # increase position by the size of this trade 133 | buy_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update the vwap buy-price 134 | buy_sum_qty += NUM_SHARES_PER_TRADE 135 | traded_volume += NUM_SHARES_PER_TRADE 136 | print("Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position) 137 | else: 138 | # No trade since none of the conditions were met to buy or sell 139 | orders.append(0) 140 | 141 | positions.append(position) 142 | 143 | # flat and starting a new position 144 | if current_pos == 0: 145 | if position != 0: 146 | current_pos = position 147 | current_pos_start = len(positions) 148 | # going from long position to flat or short position or 149 | # going from short position to flat or long position 150 | elif current_pos * position <= 0: 151 | current_pos = position 152 | position_holding_time = len(positions) - current_pos_start 153 | current_pos_start = len(positions) 154 | 155 | if position_holding_time > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS: 156 | print('RiskViolation position_holding_time', position_holding_time, ' > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS', RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS) 157 | risk_violated = True 158 | 159 | if abs(position) > RISK_LIMIT_MAX_POSITION: 160 | print('RiskViolation position', position, ' > RISK_LIMIT_MAX_POSITION', RISK_LIMIT_MAX_POSITION) 161 | risk_violated = True 162 | 163 | if traded_volume > RISK_LIMIT_MAX_TRADED_VOLUME: 164 | print('RiskViolation traded_volume', traded_volume, ' > RISK_LIMIT_MAX_TRADED_VOLUME', RISK_LIMIT_MAX_TRADED_VOLUME) 165 | risk_violated = True 166 | 167 | # This section updates Open/Unrealized & Closed/Realized positions 168 | open_pnl = 0 169 | if position > 0: 170 | if sell_sum_qty > 0: # long position and some sell trades have been made against it, close that amount based on how much was sold against this long position 171 | open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty / sell_sum_qty - buy_sum_price_qty / buy_sum_qty) 172 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 173 | open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty) 174 | elif position < 0: 175 | if buy_sum_qty > 0: # short position and some buy trades have been made against it, close that amount based on how much was bought against this short position 176 | open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty / sell_sum_qty - buy_sum_price_qty / buy_sum_qty) 177 | # mark the remaining position to market i.e. pnl would be what it would be if we closed at current price 178 | open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty / sell_sum_qty - close_price) 179 | else: 180 | # flat, so update closed_pnl and reset tracking variables for positions & pnls 181 | closed_pnl += (sell_sum_price_qty - buy_sum_price_qty) 182 | buy_sum_price_qty = 0 183 | buy_sum_qty = 0 184 | sell_sum_price_qty = 0 185 | sell_sum_qty = 0 186 | last_buy_price = 0 187 | last_sell_price = 0 188 | 189 | print("OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl)) 190 | pnls.append(closed_pnl + open_pnl) 191 | 192 | if len(pnls) > 5: 193 | weekly_loss = pnls[-1] - pnls[-6] 194 | 195 | if weekly_loss < RISK_LIMIT_WEEKLY_STOP_LOSS: 196 | print('RiskViolation weekly_loss', weekly_loss, ' < RISK_LIMIT_WEEKLY_STOP_LOSS', RISK_LIMIT_WEEKLY_STOP_LOSS) 197 | risk_violated = True 198 | 199 | if len(pnls) > 20: 200 | monthly_loss = pnls[-1] - pnls[-21] 201 | 202 | if monthly_loss < RISK_LIMIT_MONTHLY_STOP_LOSS: 203 | print('RiskViolation monthly_loss', monthly_loss, ' < RISK_LIMIT_MONTHLY_STOP_LOSS', RISK_LIMIT_MONTHLY_STOP_LOSS) 204 | risk_violated = True 205 | -------------------------------------------------------------------------------- /Chapter7/LiquidityProvider.py: -------------------------------------------------------------------------------- 1 | from random import randrange 2 | from random import sample,seed 3 | 4 | class LiquidityProvider: 5 | def __init__(self, lp_2_gateway=None): 6 | self.orders = [] 7 | self.order_id = 0 8 | seed(0) 9 | self.lp_2_gateway = lp_2_gateway 10 | 11 | def lookup_orders(self,id): 12 | count=0 13 | for o in self.orders: 14 | if o['id'] == id: 15 | return o, count 16 | count+=1 17 | return None, None 18 | 19 | def insert_manual_order(self,order): 20 | if self.lp_2_gateway is None: 21 | print('simulation mode') 22 | return order 23 | self.lp_2_gateway.append(order.copy()) 24 | 25 | 26 | def read_tick_data_from_data_source(self): 27 | pass 28 | 29 | 30 | def generate_random_order(self): 31 | price=randrange(8,12) 32 | quantity=randrange(1,10)*100 33 | side=sample(['buy','sell'],1)[0] 34 | order_id=randrange(0,self.order_id+1) 35 | o=self.lookup_orders(order_id) 36 | 37 | new_order=False 38 | if o is None: 39 | action='new' 40 | new_order=True 41 | else: 42 | action=sample(['modify','delete'],1)[0] 43 | 44 | ord = { 45 | 'id': self.order_id, 46 | 'price': price, 47 | 'quantity': quantity, 48 | 'side': side, 49 | 'action': action 50 | } 51 | 52 | if not new_order: 53 | self.order_id+=1 54 | self.orders.append(ord) 55 | 56 | if not self.lp_2_gateway: 57 | print('simulation mode') 58 | return ord 59 | self.lp_2_gateway.append(ord.copy()) 60 | 61 | -------------------------------------------------------------------------------- /Chapter7/LiquidityProvider_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.LiquidityProvider import LiquidityProvider 3 | 4 | 5 | class TestMarketSimulator(unittest.TestCase): 6 | def setUp(self): 7 | self.liquidity_provider = LiquidityProvider() 8 | 9 | def test_add_liquidity(self): 10 | self.liquidity_provider.generate_random_order() 11 | self.assertEqual(self.liquidity_provider.orders[0]['id'],0) 12 | self.assertEqual(self.liquidity_provider.orders[0]['side'], 'buy') 13 | self.assertEqual(self.liquidity_provider.orders[0]['quantity'], 700) 14 | self.assertEqual(self.liquidity_provider.orders[0]['price'], 11) 15 | 16 | -------------------------------------------------------------------------------- /Chapter7/MarketSimulator.py: -------------------------------------------------------------------------------- 1 | from random import randrange 2 | 3 | class MarketSimulator: 4 | def __init__(self, om_2_gw=None,gw_2_om=None): 5 | self.orders = [] 6 | self.om_2_gw = om_2_gw 7 | self.gw_2_om = gw_2_om 8 | def lookup_orders(self,order): 9 | count=0 10 | for o in self.orders: 11 | if o['id'] == order['id']: 12 | return o, count 13 | count+=1 14 | return None, None 15 | 16 | 17 | def handle_order_from_gw(self): 18 | if self.om_2_gw is not None: 19 | if len(self.om_2_gw)>0: 20 | self.handle_order(self.om_2_gw.popleft()) 21 | else: 22 | print('simulation mode') 23 | 24 | def fill_all_orders(self,ratio = 100): 25 | orders_to_be_removed = [] 26 | for index, order in enumerate(self.orders): 27 | if randrange(100)<=ratio: 28 | order['status'] = 'filled' 29 | else: 30 | order['status'] = 'cancelled' 31 | orders_to_be_removed.append(index) 32 | if self.gw_2_om is not None: 33 | self.gw_2_om.append(order.copy()) 34 | else: 35 | print('simulation mode') 36 | for i in sorted(orders_to_be_removed,reverse=True): 37 | del(self.orders[i]) 38 | 39 | def handle_order(self, order): 40 | o,offset=self.lookup_orders(order) 41 | if o is None: 42 | if order['action'] == 'New': 43 | order['status'] = 'accepted' 44 | self.orders.append(order) 45 | if self.gw_2_om is not None: 46 | self.gw_2_om.append(order.copy()) 47 | self.fill_all_orders(100) 48 | else: 49 | print('simulation mode') 50 | return 51 | elif order['action'] == 'Cancel' or order['action'] == 'Amend': 52 | print('Order id - not found - Rejection') 53 | if self.gw_2_om is not None: 54 | self.gw_2_om.append(order.copy()) 55 | else: 56 | print('simulation mode') 57 | return 58 | elif o is not None: 59 | if order['action'] == 'New': 60 | print('Duplicate order id - Rejection') 61 | return 62 | elif order['action'] == 'Cancel': 63 | o['status']='cancelled' 64 | if self.gw_2_om is not None: 65 | self.gw_2_om.append(o.copy()) 66 | else: 67 | print('simulation mode') 68 | del (self.orders[offset]) 69 | print('Order cancelled') 70 | elif order['action'] == 'Amend': 71 | o['status'] = 'accepted' 72 | if self.gw_2_om is not None: 73 | self.gw_2_om.append(o.copy()) 74 | else: 75 | print('simulation mode') 76 | print('Order amended') 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Chapter7/MarketSimulator_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.MarketSimulator import MarketSimulator 3 | 4 | 5 | class TestMarketSimulator(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.market_simulator = MarketSimulator() 9 | 10 | def test_accept_order(self): 11 | self.market_simulator 12 | order1 = { 13 | 'id': 10, 14 | 'price': 219, 15 | 'quantity': 10, 16 | 'side': 'bid', 17 | 'action' : 'New' 18 | } 19 | self.market_simulator.handle_order(order1) 20 | self.assertEqual(len(self.market_simulator.orders),1) 21 | self.assertEqual(self.market_simulator.orders[0]['status'], 'accepted') 22 | 23 | def test_accept_order(self): 24 | self.market_simulator 25 | order1 = { 26 | 'id': 10, 27 | 'price': 219, 28 | 'quantity': 10, 29 | 'side': 'bid', 30 | 'action' : 'Amend' 31 | } 32 | self.market_simulator.handle_order(order1) 33 | self.assertEqual(len(self.market_simulator.orders),0) 34 | -------------------------------------------------------------------------------- /Chapter7/OrderBook.py: -------------------------------------------------------------------------------- 1 | class OrderBook: 2 | def __init__(self,gt_2_ob = None,ob_to_ts = None): 3 | self.list_asks = [] 4 | self.list_bids = [] 5 | self.gw_2_ob=gt_2_ob 6 | self.ob_to_ts = ob_to_ts 7 | self.current_bid = None 8 | self.current_ask = None 9 | 10 | def create_book_event(self,bid,offer): 11 | book_event = { 12 | "bid_price": bid['price'] if bid else -1, 13 | "bid_quantity": bid['quantity'] if bid else -1, 14 | "offer_price": offer['price'] if offer else -1, 15 | "offer_quantity": offer['quantity'] if offer else -1 16 | } 17 | return book_event 18 | 19 | def check_generate_top_of_book_event(self): 20 | tob_changed = False 21 | 22 | current_list = self.list_bids 23 | if len(current_list)==0: 24 | if self.current_bid is not None: 25 | tob_changed=True 26 | self.current_bid = None 27 | else: 28 | if self.current_bid!=current_list[0]: 29 | tob_changed=True 30 | self.current_bid=current_list[0] 31 | 32 | current_list = self.list_asks 33 | if len(current_list)==0: 34 | if self.current_ask is not None: 35 | tob_changed=True 36 | self.current_ask = None 37 | else: 38 | if self.current_ask!=current_list[0]: 39 | tob_changed=True 40 | self.current_ask=current_list[0] 41 | 42 | if tob_changed: 43 | be=self.create_book_event(self.current_bid, 44 | self.current_ask) 45 | #print(be) 46 | if self.ob_to_ts is not None: 47 | self.ob_to_ts.append(be) 48 | else: 49 | return be 50 | 51 | 52 | def handle_order_from_gateway(self,order = None): 53 | if self.gw_2_ob is None: 54 | print('simulation mode') 55 | self.handle_order(order) 56 | elif len(self.gw_2_ob)>0: 57 | order_from_gw=self.gw_2_ob.popleft() 58 | self.handle_order(order_from_gw) 59 | 60 | 61 | def handle_order(self,o): 62 | if o['action']=='new': 63 | self.handle_new(o) 64 | elif o['action']=='modify': 65 | self.handle_modify(o) 66 | elif o['action']=='delete': 67 | self.handle_delete(o) 68 | else: 69 | print('Error-Cannot handle this action') 70 | 71 | return self.check_generate_top_of_book_event() 72 | 73 | def handle_new(self,o): 74 | if o['side']=='bid': 75 | self.list_bids.append(o) 76 | self.list_bids.sort(key=lambda x: x['price'],reverse=True) 77 | elif o['side']=='ask': 78 | self.list_asks.append(o) 79 | self.list_asks.sort(key=lambda x: x['price']) 80 | 81 | def get_list(self,o): 82 | if 'side' in o: 83 | if o['side']=='bid': 84 | lookup_list = self.list_bids 85 | elif o['side'] == 'ask': 86 | lookup_list = self.list_asks 87 | else: 88 | print('incorrect side') 89 | return None 90 | return lookup_list 91 | else: 92 | for order in self.list_bids: 93 | if order['id']==o['id']: 94 | return self.list_bids 95 | for order in self.list_asks: 96 | if order['id'] == o['id']: 97 | return self.list_asks 98 | return None 99 | 100 | 101 | def find_order_in_a_list(self,o,lookup_list = None): 102 | if lookup_list is None: 103 | lookup_list = self.get_list(o) 104 | if lookup_list is not None: 105 | for order in lookup_list: 106 | if order['id'] == o['id']: 107 | return order 108 | print('order not found id=%d' % (o['id'])) 109 | return None 110 | 111 | def handle_modify(self,o): 112 | order=self.find_order_in_a_list(o) 113 | if order['quantity'] > o['quantity']: 114 | order['quantity'] = o['quantity'] 115 | else: 116 | print('incorrect size') 117 | return None 118 | 119 | def handle_delete(self,o): 120 | lookup_list = self.get_list(o) 121 | order = self.find_order_in_a_list(o,lookup_list) 122 | if order is not None: 123 | lookup_list.remove(order) 124 | return None 125 | 126 | 127 | def display_content(self): 128 | print('BIDS') 129 | for o in self.list_bids: 130 | print("%d %d %d" % (o['id'],o['price'],o['quantity'])) 131 | print('OFFERS') 132 | for o in self.list_asks: 133 | print("%d %d %d" % (o['id'],o['price'],o['quantity'])) 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /Chapter7/OrderBook_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.OrderBook import OrderBook 3 | 4 | 5 | class TestOrderBook(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.reforderbook = OrderBook() 9 | 10 | def test_handlenew(self): 11 | order1 = { 12 | 'id': 1, 13 | 'price': 219, 14 | 'quantity': 10, 15 | 'side': 'bid', 16 | 'action': 'new' 17 | } 18 | 19 | ob_for_aapl = self.reforderbook 20 | ob_for_aapl.handle_order(order1) 21 | order2 = order1.copy() 22 | order2['id'] = 2 23 | order2['price'] = 220 24 | ob_for_aapl.handle_order(order2) 25 | order3 = order1.copy() 26 | order3['price'] = 223 27 | order3['id'] = 3 28 | ob_for_aapl.handle_order(order3) 29 | order4 = order1.copy() 30 | order4['side'] = 'ask' 31 | order4['price'] = 220 32 | order4['id'] = 4 33 | ob_for_aapl.handle_order(order4) 34 | order5 = order4.copy() 35 | order5['price'] = 223 36 | order5['id'] = 5 37 | ob_for_aapl.handle_order(order5) 38 | order6 = order4.copy() 39 | order6['price'] = 221 40 | order6['id'] = 6 41 | ob_for_aapl.handle_order(order6) 42 | 43 | self.assertEqual(ob_for_aapl.list_bids[0]['id'],3) 44 | self.assertEqual(ob_for_aapl.list_bids[1]['id'], 2) 45 | self.assertEqual(ob_for_aapl.list_bids[2]['id'], 1) 46 | self.assertEqual(ob_for_aapl.list_asks[0]['id'],4) 47 | self.assertEqual(ob_for_aapl.list_asks[1]['id'], 6) 48 | self.assertEqual(ob_for_aapl.list_asks[2]['id'], 5) 49 | 50 | 51 | def test_handleamend(self): 52 | self.test_handlenew() 53 | order1 = { 54 | 'id': 1, 55 | 'quantity': 5, 56 | 'action': 'modify' 57 | } 58 | self.reforderbook.handle_order(order1) 59 | 60 | self.assertEqual(self.reforderbook.list_bids[2]['id'], 1) 61 | self.assertEqual(self.reforderbook.list_bids[2]['quantity'], 5) 62 | 63 | 64 | def test_handledelete(self): 65 | self.test_handlenew() 66 | order1 = { 67 | 'id': 1, 68 | 'action': 'delete' 69 | } 70 | self.assertEqual(len(self.reforderbook.list_bids), 3) 71 | self.reforderbook.handle_order(order1) 72 | self.assertEqual(len(self.reforderbook.list_bids), 2) 73 | 74 | def test_generate_book_event(self): 75 | order1 = { 76 | 'id': 1, 77 | 'price': 219, 78 | 'quantity': 10, 79 | 'side': 'bid', 80 | 'action': 'new' 81 | } 82 | 83 | ob_for_aapl = self.reforderbook 84 | self.assertEqual(ob_for_aapl.handle_order(order1), 85 | {'bid_price': 219, 'bid_quantity': 10, 86 | 'offer_price': -1, 'offer_quantity': -1}) 87 | order2 = order1.copy() 88 | order2['id'] = 2 89 | order2['price'] = 220 90 | order2['side'] = 'ask' 91 | self.assertEqual(ob_for_aapl.handle_order(order2), 92 | {'bid_price': 219, 'bid_quantity': 10, 93 | 'offer_price': 220, 'offer_quantity': 10}) 94 | 95 | 96 | 97 | if __name__ == '__main__': 98 | unittest.main() -------------------------------------------------------------------------------- /Chapter7/OrderManager.py: -------------------------------------------------------------------------------- 1 | class OrderManager: 2 | def __init__(self,ts_2_om = None, om_2_ts = None, 3 | om_2_gw=None,gw_2_om=None): 4 | self.orders=[] 5 | self.order_id=0 6 | self.ts_2_om = ts_2_om 7 | self.om_2_gw = om_2_gw 8 | self.gw_2_om = gw_2_om 9 | self.om_2_ts = om_2_ts 10 | 11 | def check_order_valid(self,order): 12 | if order['quantity'] < 0: 13 | return False 14 | if order['price'] < 0: 15 | return False 16 | return True 17 | 18 | def create_new_order(self,order): 19 | self.order_id += 1 20 | neworder = { 21 | 'id': self.order_id, 22 | 'price': order['price'], 23 | 'quantity': order['quantity'], 24 | 'side': order['side'], 25 | 'status': 'new', 26 | 'action': 'New' 27 | } 28 | return neworder 29 | 30 | def handle_input_from_ts(self): 31 | if self.ts_2_om is not None: 32 | if len(self.ts_2_om)>0: 33 | self.handle_order_from_trading_strategy(self.ts_2_om.popleft()) 34 | else: 35 | print('simulation mode') 36 | 37 | def handle_order_from_trading_strategy(self,order): 38 | if self.check_order_valid(order): 39 | order=self.create_new_order(order).copy() 40 | self.orders.append(order) 41 | if self.om_2_gw is None: 42 | print('simulation mode') 43 | else: 44 | self.om_2_gw.append(order.copy()) 45 | 46 | def lookup_order_by_id(self,id): 47 | for i in range(len(self.orders)): 48 | if self.orders[i]['id']==id: 49 | return self.orders[i] 50 | return None 51 | 52 | def clean_traded_orders(self): 53 | order_offsets=[] 54 | for k in range(len(self.orders)): 55 | if self.orders[k]['status'] == 'filled': 56 | order_offsets.append(k) 57 | if len(order_offsets): 58 | for k in sorted(order_offsets,reverse=True): 59 | del (self.orders[k]) 60 | 61 | def handle_input_from_market(self): 62 | if self.gw_2_om is not None: 63 | if len(self.gw_2_om)>0: 64 | self.handle_order_from_gateway(self.gw_2_om.popleft()) 65 | else: 66 | print('simulation mode') 67 | 68 | def handle_order_from_gateway(self,order_update): 69 | order=self.lookup_order_by_id(order_update['id']) 70 | if order is not None: 71 | order['status']=order_update['status'] 72 | if self.om_2_ts is not None: 73 | self.om_2_ts.append(order.copy()) 74 | else: 75 | print('simulation mode') 76 | self.clean_traded_orders() 77 | else: 78 | print('order not found') 79 | 80 | 81 | -------------------------------------------------------------------------------- /Chapter7/OrderManager_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.OrderManager import OrderManager 3 | 4 | 5 | class TestOrderBook(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.order_manager = OrderManager() 9 | 10 | def test_receive_order_from_trading_strategy(self): 11 | order1 = { 12 | 'id': 10, 13 | 'price': 219, 14 | 'quantity': 10, 15 | 'side': 'bid', 16 | } 17 | self.order_manager.handle_order_from_trading_strategy(order1) 18 | self.assertEqual(len(self.order_manager.orders),1) 19 | self.order_manager.handle_order_from_trading_strategy(order1) 20 | self.assertEqual(len(self.order_manager.orders),2) 21 | self.assertEqual(self.order_manager.orders[0]['id'],1) 22 | self.assertEqual(self.order_manager.orders[1]['id'],2) 23 | 24 | def test_receive_order_from_trading_strategy_error(self): 25 | order1 = { 26 | 'id': 10, 27 | 'price': -219, 28 | 'quantity': 10, 29 | 'side': 'bid', 30 | } 31 | self.order_manager.handle_order_from_trading_strategy(order1) 32 | self.assertEqual(len(self.order_manager.orders),0) 33 | 34 | def display_orders(self): 35 | for o in self.order_manager.orders: 36 | print(o) 37 | 38 | def test_receive_from_gateway_filled(self): 39 | self.test_receive_order_from_trading_strategy() 40 | orderexecution1 = { 41 | 'id': 2, 42 | 'price': 13, 43 | 'quantity': 10, 44 | 'side': 'bid', 45 | 'status' : 'filled' 46 | } 47 | # self.display_orders() 48 | self.order_manager.handle_order_from_gateway(orderexecution1) 49 | self.assertEqual(len(self.order_manager.orders), 1) 50 | 51 | def test_receive_from_gateway_acked(self): 52 | self.test_receive_order_from_trading_strategy() 53 | orderexecution1 = { 54 | 'id': 2, 55 | 'price': 13, 56 | 'quantity': 10, 57 | 'side': 'bid', 58 | 'status' : 'acked' 59 | } 60 | # self.display_orders() 61 | self.order_manager.handle_order_from_gateway(orderexecution1) 62 | self.assertEqual(len(self.order_manager.orders), 2) 63 | self.assertEqual(self.order_manager.orders[1]['status'], 'acked') 64 | -------------------------------------------------------------------------------- /Chapter7/TradingSimulation.py: -------------------------------------------------------------------------------- 1 | from chapter7.LiquidityProvider import LiquidityProvider 2 | from chapter7.TradingStrategy import TradingStrategy 3 | from chapter7.MarketSimulator import MarketSimulator 4 | from chapter7.OrderManager import OrderManager 5 | from chapter7.OrderBook import OrderBook 6 | from collections import deque 7 | 8 | def main(): 9 | lp_2_gateway = deque() 10 | ob_2_ts = deque() 11 | ts_2_om = deque() 12 | ms_2_om = deque() 13 | om_2_ts = deque() 14 | gw_2_om = deque() 15 | om_2_gw = deque() 16 | 17 | lp = LiquidityProvider(lp_2_gateway) 18 | ob = OrderBook(lp_2_gateway, ob_2_ts) 19 | ts = TradingStrategy(ob_2_ts, ts_2_om, om_2_ts) 20 | ms = MarketSimulator(om_2_gw, gw_2_om) 21 | om = OrderManager(ts_2_om, om_2_ts, om_2_gw, gw_2_om) 22 | 23 | lp.read_tick_data_from_data_source() 24 | while len(lp_2_gateway)>0: 25 | ob.handle_order_from_gateway() 26 | ts.handle_input_from_bb() 27 | om.handle_input_from_ts() 28 | ms.handle_order_from_gw() 29 | om.handle_input_from_market() 30 | ts.handle_response_from_om() 31 | lp.read_tick_data_from_data_source() 32 | 33 | 34 | 35 | 36 | if __name__ == '__main__': 37 | main() -------------------------------------------------------------------------------- /Chapter7/TradingSimulation_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.LiquidityProvider import LiquidityProvider 3 | from chapter7.TradingStrategy import TradingStrategy 4 | from chapter7.MarketSimulator import MarketSimulator 5 | from chapter7.OrderManager import OrderManager 6 | from chapter7.OrderBook import OrderBook 7 | from collections import deque 8 | 9 | class TestTradingSimulation(unittest.TestCase): 10 | def setUp(self): 11 | self.lp_2_gateway=deque() 12 | self.ob_2_ts = deque() 13 | self.ts_2_om = deque() 14 | self.ms_2_om = deque() 15 | self.om_2_ts = deque() 16 | self.gw_2_om = deque() 17 | self.om_2_gw = deque() 18 | 19 | 20 | 21 | 22 | self.lp=LiquidityProvider(self.lp_2_gateway) 23 | self.ob=OrderBook(self.lp_2_gateway, self.ob_2_ts) 24 | self.ts=TradingStrategy(self.ob_2_ts,self.ts_2_om,self.om_2_ts) 25 | self.ms=MarketSimulator(self.om_2_gw,self.gw_2_om) 26 | self.om=OrderManager(self.ts_2_om, self.om_2_ts,self.om_2_gw,self.gw_2_om) 27 | 28 | 29 | 30 | def test_add_liquidity(self): 31 | # Order sent from the exchange to the trading system 32 | order1 = { 33 | 'id': 1, 34 | 'price': 219, 35 | 'quantity': 10, 36 | 'side': 'bid', 37 | 'action': 'new' 38 | } 39 | self.lp.insert_manual_order(order1) 40 | self.assertEqual(len(self.lp_2_gateway),1) 41 | self.ob.handle_order_from_gateway() 42 | self.assertEqual(len(self.ob_2_ts), 1) 43 | self.ts.handle_input_from_bb() 44 | self.assertEqual(len(self.ts_2_om), 0) 45 | order2 = { 46 | 'id': 2, 47 | 'price': 218, 48 | 'quantity': 10, 49 | 'side': 'ask', 50 | 'action': 'new' 51 | } 52 | self.lp.insert_manual_order(order2.copy()) 53 | self.assertEqual(len(self.lp_2_gateway),1) 54 | self.ob.handle_order_from_gateway() 55 | self.assertEqual(len(self.ob_2_ts), 1) 56 | self.ts.handle_input_from_bb() 57 | self.assertEqual(len(self.ts_2_om), 2) 58 | self.om.handle_input_from_ts() 59 | self.assertEqual(len(self.ts_2_om), 1) 60 | self.assertEqual(len(self.om_2_gw), 1) 61 | self.om.handle_input_from_ts() 62 | self.assertEqual(len(self.ts_2_om), 0) 63 | self.assertEqual(len(self.om_2_gw), 2) 64 | self.ms.handle_order_from_gw() 65 | self.assertEqual(len(self.gw_2_om), 1) 66 | self.ms.handle_order_from_gw() 67 | self.assertEqual(len(self.gw_2_om), 2) 68 | self.om.handle_input_from_market() 69 | self.om.handle_input_from_market() 70 | self.assertEqual(len(self.om_2_ts), 2) 71 | self.ts.handle_response_from_om() 72 | self.assertEqual(self.ts.get_pnl(),0) 73 | self.ms.fill_all_orders() 74 | self.assertEqual(len(self.gw_2_om), 2) 75 | self.om.handle_input_from_market() 76 | self.om.handle_input_from_market() 77 | self.assertEqual(len(self.om_2_ts), 3) 78 | self.ts.handle_response_from_om() 79 | self.assertEqual(len(self.om_2_ts), 2) 80 | self.ts.handle_response_from_om() 81 | self.assertEqual(len(self.om_2_ts), 1) 82 | self.ts.handle_response_from_om() 83 | self.assertEqual(len(self.om_2_ts), 0) 84 | self.assertEqual(self.ts.get_pnl(),10) 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /Chapter7/TradingStrategy.py: -------------------------------------------------------------------------------- 1 | class TradingStrategy: 2 | def __init__(self, ob_2_ts, ts_2_om, om_2_ts): 3 | self.orders = [] 4 | self.order_id = 0 5 | self.position = 0 6 | self.pnl = 0 7 | self.cash = 10000 8 | self.current_bid = 0 9 | self.current_offer = 0 10 | self.ob_2_ts = ob_2_ts 11 | self.ts_2_om = ts_2_om 12 | self.om_2_ts = om_2_ts 13 | 14 | def create_orders(self,book_event,quantity): 15 | self.order_id+=1 16 | ord = { 17 | 'id': self.order_id, 18 | 'price': book_event['bid_price'], 19 | 'quantity': quantity, 20 | 'side': 'sell', 21 | 'action': 'to_be_sent' 22 | } 23 | self.orders.append(ord.copy()) 24 | 25 | price=book_event['offer_price'] 26 | side='buy' 27 | self.order_id+=1 28 | ord = { 29 | 'id': self.order_id, 30 | 'price': book_event['offer_price'], 31 | 'quantity': quantity, 32 | 'side': 'buy', 33 | 'action': 'to_be_sent' 34 | } 35 | self.orders.append(ord.copy()) 36 | 37 | def signal(self, book_event): 38 | if book_event is not None: 39 | if book_event["bid_price"]>\ 40 | book_event["offer_price"]: 41 | if book_event["bid_price"]>0 and\ 42 | book_event["offer_price"]>0: 43 | return True 44 | else: 45 | return False 46 | else: 47 | return False 48 | 49 | def execution(self): 50 | orders_to_be_removed=[] 51 | for index, order in enumerate(self.orders): 52 | if order['action'] == 'to_be_sent': 53 | # Send order 54 | order['status'] = 'new' 55 | order['action'] = 'no_action' 56 | if self.ts_2_om is None: 57 | print('Simulation mode') 58 | else: 59 | self.ts_2_om.append(order.copy()) 60 | if order['status'] == 'rejected': 61 | orders_to_be_removed.append(index) 62 | if order['status'] == 'filled': 63 | orders_to_be_removed.append(index) 64 | pos = order['quantity'] if order['side'] == 'buy' else -order['quantity'] 65 | self.position+=pos 66 | self.pnl-=pos * order['price'] 67 | self.cash -= pos * order['price'] 68 | for order_index in sorted(orders_to_be_removed,reverse=True): 69 | del (self.orders[order_index]) 70 | 71 | 72 | def handle_input_from_bb(self,book_event=None): 73 | if self.ob_2_ts is None: 74 | print('simulation mode') 75 | self.handle_book_event(book_event) 76 | else: 77 | if len(self.ob_2_ts)>0: 78 | be=self.handle_book_event(self.ob_2_ts.popleft()) 79 | self.handle_book_event(be) 80 | 81 | def handle_book_event(self,book_event): 82 | if book_event is not None: 83 | self.current_bid = book_event['bid_price'] 84 | self.current_offer = book_event['offer_price'] 85 | 86 | if self.signal(book_event): 87 | self.create_orders(book_event 88 | ,min(book_event['bid_quantity'], 89 | book_event['offer_quantity'])) 90 | self.execution() 91 | 92 | def lookup_orders(self,id): 93 | count=0 94 | for o in self.orders: 95 | if o['id'] == id: 96 | return o, count 97 | count+=1 98 | return None, None 99 | 100 | def handle_response_from_om(self): 101 | if self.om_2_ts is not None: 102 | self.handle_market_response(self.om_2_ts.popleft()) 103 | else: 104 | print('simulation mode') 105 | 106 | def handle_market_response(self, order_execution): 107 | order,_=self.lookup_orders(order_execution['id']) 108 | if order is None: 109 | print('error not found') 110 | return 111 | order['status']=order_execution['status'] 112 | self.execution() 113 | 114 | def get_pnl(self): 115 | return self.pnl + self.position * (self.current_bid + self.current_offer)/2 116 | 117 | -------------------------------------------------------------------------------- /Chapter7/TradingStrategy_ut.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from chapter7.TradingStrategy import TradingStrategy 3 | 4 | 5 | class TestMarketSimulator(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.trading_strategy= TradingStrategy() 9 | 10 | 11 | def test_receive_top_of_book(self): 12 | book_event = { 13 | "bid_price" : 12, 14 | "bid_quantity" : 100, 15 | "offer_price" : 11, 16 | "offer_quantity" : 150 17 | } 18 | self.trading_strategy.handle_book_event(book_event) 19 | self.assertEqual(len(self.trading_strategy.orders), 2) 20 | self.assertEqual(self.trading_strategy.orders[0]['side'], 'sell') 21 | self.assertEqual(self.trading_strategy.orders[1]['side'], 'buy') 22 | self.assertEqual(self.trading_strategy.orders[0]['price'], 12) 23 | self.assertEqual(self.trading_strategy.orders[1]['price'], 11) 24 | self.assertEqual(self.trading_strategy.orders[0]['quantity'], 100) 25 | self.assertEqual(self.trading_strategy.orders[1]['quantity'], 100) 26 | self.assertEqual(self.trading_strategy.orders[0]['action'], 'no_action') 27 | self.assertEqual(self.trading_strategy.orders[1]['action'], 'no_action') 28 | 29 | 30 | def test_rejected_order(self): 31 | self.test_receive_top_of_book() 32 | order_execution = { 33 | 'id': 1, 34 | 'price': 12, 35 | 'quantity': 100, 36 | 'side': 'sell', 37 | 'status' : 'rejected' 38 | } 39 | self.trading_strategy.handle_market_response(order_execution) 40 | self.assertEqual(self.trading_strategy.orders[0]['side'], 'buy') 41 | self.assertEqual(self.trading_strategy.orders[0]['price'], 11) 42 | self.assertEqual(self.trading_strategy.orders[0]['quantity'], 100) 43 | self.assertEqual(self.trading_strategy.orders[0]['status'], 'new') 44 | 45 | 46 | def test_filled_order(self): 47 | self.test_receive_top_of_book() 48 | order_execution = { 49 | 'id': 1, 50 | 'price': 11, 51 | 'quantity': 100, 52 | 'side': 'sell', 53 | 'status' : 'filled' 54 | } 55 | self.trading_strategy.handle_market_response(order_execution) 56 | self.assertEqual(len(self.trading_strategy.orders),1) 57 | 58 | order_execution = { 59 | 'id': 2, 60 | 'price': 12, 61 | 'quantity': 100, 62 | 'side': 'buy', 63 | 'status' : 'filled' 64 | } 65 | self.trading_strategy.handle_market_response(order_execution) 66 | self.assertEqual(self.trading_strategy.position, 0) 67 | self.assertEqual(self.trading_strategy.cash, 10100) 68 | self.assertEqual(self.trading_strategy.pnl, 100) 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Chapter8/fixsim/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | .idea/ 5 | .ropeproject/ 6 | tags 7 | # C extensions 8 | *.so 9 | draft/ 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | utils/ 58 | tags/ 59 | -------------------------------------------------------------------------------- /Chapter8/fixsim/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 gloryofrobots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Chapter8/fixsim/README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | FixSim is an example project for FIX protocol client and server implementations. 5 | It can be used for testing your client or server and you might 6 | find a need to add new feature or change fixsim's behavior. FixSim also can be used 7 | just as example for implementing acceptors and initiators. 8 | 9 | Configuration 10 | ------------- 11 | 12 | FixSim consists of two scripts fixsim-client.py and fixsim-server.py. Each 13 | script uses two configuration files, one for FIX Session settings in ini format 14 | and one for business logic in YAML. You can find example configurations with 15 | comments in project tree. For example server and client may be started by commands: 16 | 17 | ``` 18 | python fixsim-server --acceptor_config fixsim-server.conf.ini --config 19 | fixsim-server.conf.yaml 20 | ``` 21 | ``` 22 | python fixsim-client --initiator_config fixsim-client.conf.ini --config 23 | fixsim-client.conf.yaml 24 | ``` 25 | 26 | FixSim depends on twisted but you can easily purge it from poject by replacing reactor infinite loop by differrent infinite loop in main thread and implementing something like twisted.internet.task.LoopingCall which is used for periodical sending snapshots and subscribing to instruments. 27 | 28 | FixSim supports only FIX44 now 29 | 30 | Workflow 31 | -------- 32 | 33 | FixSim business logic is pretty simple. Server receives client session and stores it. Client subscribes to the one or more instrument (like EUR/USD, USD/CAD etc) and server starts sending market data snapshots to client. Client can create a new orderfor each snapshot and send it to acceptor or skip this snapshot (see skip_snapshot_chance attr in client yaml config). Order is created for one randomly selected quote from previously received snapshot. For each such order acceptor can create filled or rejected execution report(see reject rate in server yaml config) 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-client.conf.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | FileStorePath=/tmp/ 3 | FileIncludeMilliseconds=Y 4 | FileIncludeTimeStampForMessages=Y 5 | PersistMessages=Y 6 | ConnectionType=initiator 7 | StartDay=monday 8 | EndDay=sunday 9 | StartTime=08:00:00 10 | EndTime=23:00:00 11 | SocketKeepAlive=Y 12 | SocketTcpNoDelay=Y 13 | SocketReuseAddress=Y 14 | CheckLatency=N 15 | ResetOnLogon=Y 16 | SocketConnectHost = 127.0.0.1 17 | SocketConnectPort=1844 18 | DataDictionary=FIX44.simulator.xml 19 | HeartBtInt = 1 20 | LogonTimeout = 1 21 | [SESSION] 22 | SenderCompID=FIXSIM-CLIENT-MKD 23 | TargetCompID=FIXSIM-SERVER-MKD 24 | BeginString=FIX.4.4 25 | 26 | [SESSION] 27 | SenderCompID=FIXSIM-CLIENT 28 | TargetCompID=FIXSIM-SERVER 29 | BeginString=FIX.4.4 30 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-client.conf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | #interval in seconds for market data resubscribe 3 | subscribe_interval: 10 4 | 5 | #only 44 version supported 6 | fix_version: FIX44 7 | 8 | logging: 9 | target: file 10 | filename: fixsim-client.log 11 | #or logging: 12 | # target: syslog 13 | 14 | #trade chance for each snapshot in percents 15 | skip_snapshot_chance: 0 16 | 17 | instruments: 18 | - symbol: USD/RUB 19 | - symbol: EUR/USD 20 | ... 21 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | from twisted.internet import reactor 4 | 5 | from fixsim.client import create_initiator 6 | 7 | 8 | def parse_args(arguments): 9 | parser = argparse.ArgumentParser(description='Run FIX client simulator') 10 | 11 | parser.add_argument('-ic', '--initiator_config', type=str, required=True 12 | , help='Path to FIX config file') 13 | parser.add_argument('-c', '--client_config', type=str, required=True 14 | , help='Path to FIX client config file') 15 | 16 | result = parser.parse_args(arguments) 17 | return result 18 | 19 | 20 | def main(params): 21 | options = parse_args(params) 22 | 23 | initiator = create_initiator(options.initiator_config, options.client_config) 24 | initiator.start() 25 | 26 | reactor.run() 27 | 28 | 29 | if __name__ == "__main__": 30 | args = [] 31 | if len(sys.argv) > 1: 32 | args = sys.argv[1:] 33 | 34 | main(args) 35 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-server.conf.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | FileStorePath=/tmp/ 3 | FileIncludeMilliseconds=Y 4 | FileIncludeTimeStampForMessages=Y 5 | PersistMessages=Y 6 | ConnectionType=acceptor 7 | StartDay=monday 8 | EndDay=sunday 9 | StartTime=08:00:00 10 | EndTime=23:00:00 11 | SocketKeepAlive=Y 12 | SocketTcpNoDelay=Y 13 | SocketReuseAddress=Y 14 | CheckLatency=N 15 | ResetOnLogon=Y 16 | SocketAcceptPort=1844 17 | SocketAcceptHost = 127.0.0.1 18 | DataDictionary=FIX44.simulator.xml 19 | 20 | [SESSION] 21 | TargetCompID=FIXSIM-CLIENT-MKD 22 | SenderCompID=FIXSIM-SERVER-MKD 23 | BeginString=FIX.4.4 24 | 25 | [SESSION] 26 | TargetCompID=FIXSIM-CLIENT 27 | SenderCompID=FIXSIM-SERVER 28 | BeginString=FIX.4.4 -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-server.conf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | #Snapshot publish interval 3 | publish_interval: 10 4 | 5 | #only 44 version supported 6 | fix_version: FIX44 7 | 8 | logging: 9 | target: file 10 | filename: fixsim.log 11 | #or logging: 12 | # target: syslog 13 | 14 | #Trade reject chance in percents 15 | reject_rate: 0 16 | 17 | instruments: 18 | - symbol: USD/RUB 19 | bid: 20 | - size: 20000 21 | price: 48.223 22 | 23 | 24 | - size: 10000 25 | price: 50.111 26 | 27 | ask: 28 | - size: 20000 29 | price: 49.345 30 | 31 | - size: 10000 32 | price: 51.656 33 | 34 | #variation for bids and asks price 35 | variation: 36 | step: 0.1 37 | #max variation 38 | limit: 20 39 | 40 | - symbol: EUR/USD 41 | bid: 42 | - size: 5000 43 | price: 1.1 44 | 45 | 46 | - size: 2000 47 | price: 1.115 48 | 49 | ask: 50 | - size: 5000 51 | price: 1.3 52 | 53 | - size: 2000 54 | price: 1.212 55 | 56 | variation: 57 | step: 0.1 58 | limit: 20 59 | ... 60 | 61 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim-server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | from twisted.internet import reactor 4 | 5 | from fixsim.server import create_acceptor 6 | 7 | 8 | def parse_options(arguments): 9 | parser = argparse.ArgumentParser(description='Run FIX server simulator') 10 | 11 | parser.add_argument('-ac', '--acceptor_config', type=str, required=True 12 | , help='Path to FIX config file') 13 | parser.add_argument('-c', '--server_config', type=str, required=True 14 | , help='Path to FIX server config file') 15 | 16 | result = parser.parse_args(arguments) 17 | return result 18 | 19 | 20 | def main(params): 21 | options = parse_options(params) 22 | 23 | acceptor = create_acceptor(options.acceptor_config, options.server_config) 24 | acceptor.start() 25 | 26 | reactor.run() 27 | 28 | 29 | if __name__ == "__main__": 30 | args = [] 31 | if len(sys.argv) > 1: 32 | args = sys.argv[1:] 33 | 34 | main(args) 35 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gloryofrobots' 2 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim/client.py: -------------------------------------------------------------------------------- 1 | import quickfix 2 | import copy 3 | import uuid 4 | import random 5 | import datetime 6 | #import yaml 7 | 8 | from twisted.internet import task 9 | 10 | from sim import (FixSimError, FixSimApplication, create_fix_version, 11 | instance_safe_call, create_logger, IncrementID, load_yaml) 12 | 13 | 14 | class Subscription(object): 15 | def __init__(self, symbol): 16 | super(Subscription, self).__init__() 17 | self.symbol = symbol 18 | self.currency = self.symbol.split("/")[0] 19 | 20 | def __repr__(self): 21 | return "" % self.symbol 22 | 23 | 24 | class Subscriptions(object): 25 | def __init__(self): 26 | self.subscriptions = {} 27 | 28 | def add(self, subscription): 29 | if subscription.symbol in self.subscriptions: 30 | raise KeyError("Subscription for symbol has already exist") 31 | self.subscriptions[subscription.symbol] = subscription 32 | 33 | def get(self, symbol): 34 | subscription = self.subscriptions.get(symbol, None) 35 | return subscription 36 | 37 | def __iter__(self): 38 | return self.subscriptions.values().__iter__() 39 | 40 | 41 | class OrderBook(object): 42 | def __init__(self): 43 | self.quotes = [] 44 | 45 | def setSnapshot(self, snaphot): 46 | raise NotImplementedError() 47 | 48 | def __iter__(self): 49 | return self.quotes.__iter__() 50 | 51 | def get(self, quoteID): 52 | for quote in self.quotes: 53 | if quote.id == quoteID: 54 | return quote 55 | 56 | return None 57 | 58 | 59 | class IDGenerator(object): 60 | def __init__(self): 61 | self._orderID = IncrementID() 62 | self._reqID = IncrementID() 63 | 64 | def orderID(self): 65 | return self._orderID.generate() 66 | 67 | def reqID(self): 68 | return self._reqID.generate() 69 | 70 | 71 | def create_initiator(initiator_config, simulation_config): 72 | def create_subscriptions(instruments): 73 | result = Subscriptions() 74 | 75 | for instrument in instruments: 76 | subscription = Subscription(instrument['symbol']) 77 | result.add(subscription) 78 | 79 | return result 80 | 81 | settings = quickfix.SessionSettings(initiator_config) 82 | config = load_yaml(simulation_config) 83 | 84 | fix_version = create_fix_version(config) 85 | 86 | subscriptions = create_subscriptions(config['instruments']) 87 | 88 | logger = create_logger(config) 89 | subscribe_interval = config.get('subscribe_interval', 1) 90 | skip_snapshot_chance = config.get('skip_snapshot_chance', 0) 91 | application = Client(fix_version, logger, skip_snapshot_chance, subscribe_interval, subscriptions) 92 | storeFactory = quickfix.FileStoreFactory(settings) 93 | logFactory = quickfix.ScreenLogFactory(settings) 94 | initiator = quickfix.SocketInitiator(application, storeFactory, settings, logFactory) 95 | return initiator 96 | 97 | 98 | class Snapshot(object): 99 | def __init__(self, symbol): 100 | self.symbol = symbol 101 | self.bid = [] 102 | self.ask = [] 103 | 104 | def getRandomQuote(self): 105 | is_bid = random.randrange(0, 2) 106 | if is_bid: 107 | quotes = self.bid 108 | else: 109 | quotes = self.ask 110 | 111 | quote = random.choice(quotes) 112 | return quote 113 | 114 | def addBid(self, quote): 115 | quote.side = Quote.SELL 116 | self.bid.append(quote) 117 | 118 | def addAsk(self, quote): 119 | quote.side = quote.BUY 120 | self.ask.append(quote) 121 | 122 | def __repr__(self): 123 | return "Snapshot %s\n BID: %s\n ASK: %s" % (self.symbol, self.bid, self.ask) 124 | 125 | 126 | class Quote(object): 127 | SELL = '2' 128 | BUY = '1' 129 | 130 | def __init__(self): 131 | super(Quote, self).__init__() 132 | self.side = None 133 | self.symbol = None 134 | self.currency = None 135 | self.price = None 136 | self.size = None 137 | self.id = None 138 | 139 | def __repr__(self): 140 | return "(%s %s %s, %s)" % (str(self.id), self.side, str(self.price), str(self.size)) 141 | 142 | 143 | class Client(FixSimApplication): 144 | MKD_TOKEN = "MKD" 145 | 146 | def __init__(self, fixVersion, logger, skipSnapshotChance, subscribeInterval, subscriptions): 147 | super(Client, self).__init__(fixVersion, logger) 148 | 149 | self.skipSnapshotChance = skipSnapshotChance 150 | self.subscribeInterval = subscribeInterval 151 | self.subscriptions = subscriptions 152 | self.orderSession = None 153 | self.marketSession = None 154 | self.idGen = IDGenerator() 155 | self.loop = task.LoopingCall(self.subscribe) 156 | self.loop.start(self.subscribeInterval, True) 157 | 158 | def onCreate(self, sessionID): 159 | pass 160 | 161 | def onLogon(self, sessionID): 162 | sid = str(sessionID) 163 | # print "ON LOGON sid", sid 164 | if sid.find(self.MKD_TOKEN) != -1: 165 | self.marketSession = sessionID 166 | self.logger.info("FIXSIM-CLIENT MARKET SESSION %s", self.marketSession) 167 | else: 168 | self.orderSession = sessionID 169 | self.logger.info("FIXSIM-CLIENT ORDER SESSION %s", self.orderSession) 170 | 171 | def onLogout(self, sessionID): 172 | # print "ON LOGOUT" 173 | return 174 | 175 | def toAdmin(self, sessionID, message): 176 | # print "TO ADMIN", message 177 | return 178 | 179 | def fromAdmin(self, sessionID, message): 180 | # print "FROM ADMIN" 181 | return 182 | 183 | def toApp(self, sessionID, message): 184 | # print "TO APP" 185 | return 186 | 187 | def subscribe(self): 188 | if self.marketSession is None: 189 | self.logger.info("FIXSIM-CLIENT Market session is none, skip subscribing") 190 | return 191 | 192 | for subscription in self.subscriptions: 193 | message = self.fixVersion.MarketDataRequest() 194 | message.setField(quickfix.MDReqID(self.idGen.reqID())) 195 | message.setField(quickfix.SubscriptionRequestType(quickfix.SubscriptionRequestType_SNAPSHOT_PLUS_UPDATES)) 196 | message.setField(quickfix.MDUpdateType(quickfix.MDUpdateType_FULL_REFRESH)) 197 | message.setField(quickfix.MarketDepth(0)) 198 | message.setField(quickfix.MDReqID(self.idGen.reqID())) 199 | 200 | relatedSym = self.fixVersion.MarketDataRequest.NoRelatedSym() 201 | relatedSym.setField(quickfix.Product(quickfix.Product_CURRENCY)) 202 | relatedSym.setField(quickfix.SecurityType(quickfix.SecurityType_FOREIGN_EXCHANGE_CONTRACT)) 203 | relatedSym.setField(quickfix.Symbol(subscription.symbol)) 204 | message.addGroup(relatedSym) 205 | 206 | group = self.fixVersion.MarketDataRequest.NoMDEntryTypes() 207 | group.setField(quickfix.MDEntryType(quickfix.MDEntryType_BID)) 208 | message.addGroup(group) 209 | group.setField(quickfix.MDEntryType(quickfix.MDEntryType_BID)) 210 | message.addGroup(group) 211 | 212 | self.sendToTarget(message, self.marketSession) 213 | 214 | def onMarketDataSnapshotFullRefresh(self, message, sessionID): 215 | skip_chance = random.choice(range(1, 101)) 216 | if self.skipSnapshotChance > skip_chance: 217 | self.logger.info("FIXSIM-CLIENT onMarketDataSnapshotFullRefresh skip making trade with random choice %d", skip_chance) 218 | return 219 | 220 | fix_symbol = quickfix.Symbol() 221 | message.getField(fix_symbol) 222 | symbol = fix_symbol.getValue() 223 | 224 | snapshot = Snapshot(symbol) 225 | 226 | group = self.fixVersion.MarketDataSnapshotFullRefresh.NoMDEntries() 227 | fix_no_entries = quickfix.NoMDEntries() 228 | message.getField(fix_no_entries) 229 | no_entries = fix_no_entries.getValue() 230 | 231 | for i in range(1, no_entries + 1): 232 | message.getGroup(i, group) 233 | price = quickfix.MDEntryPx() 234 | size = quickfix.MDEntrySize() 235 | currency = quickfix.Currency() 236 | quote_id = quickfix.QuoteEntryID() 237 | 238 | group.getField(quote_id) 239 | group.getField(currency) 240 | group.getField(price) 241 | group.getField(size) 242 | 243 | quote = Quote() 244 | quote.price = price.getValue() 245 | quote.size = size.getValue() 246 | quote.currency = currency.getValue() 247 | quote.id = quote_id.getValue() 248 | 249 | fix_entry_type = quickfix.MDEntryType() 250 | group.getField(fix_entry_type) 251 | entry_type = fix_entry_type.getValue() 252 | 253 | if entry_type == quickfix.MDEntryType_BID: 254 | snapshot.addBid(quote) 255 | elif entry_type == quickfix.MDEntryType_OFFER: 256 | snapshot.addAsk(quote) 257 | else: 258 | raise RuntimeError("Unknown entry type %s" % str(entry_type)) 259 | 260 | self.makeOrder(snapshot) 261 | 262 | def makeOrder(self, snapshot): 263 | self.logger.info("FIXSIM-CLIENT Snapshot received %s", str(snapshot)) 264 | quote = snapshot.getRandomQuote() 265 | 266 | self.logger.info("FIXSIM-CLIENT make order for quote %s", str(quote)) 267 | order = self.fixVersion.NewOrderSingle() 268 | order.setField(quickfix.HandlInst(quickfix.HandlInst_AUTOMATED_EXECUTION_ORDER_PUBLIC_BROKER_INTERVENTION_OK)) 269 | order.setField(quickfix.SecurityType(quickfix.SecurityType_FOREIGN_EXCHANGE_CONTRACT)) 270 | 271 | order.setField(quickfix.OrdType(quickfix.OrdType_PREVIOUSLY_QUOTED)) 272 | order.setField(quickfix.ClOrdID(self.idGen.orderID())) 273 | order.setField(quickfix.QuoteID(quote.id)) 274 | 275 | order.setField(quickfix.SecurityDesc("SPOT")) 276 | order.setField(quickfix.Symbol(snapshot.symbol)) 277 | order.setField(quickfix.Currency(quote.currency)) 278 | order.setField(quickfix.Side(quote.side)) 279 | 280 | order.setField(quickfix.OrderQty(quote.size)) 281 | order.setField(quickfix.FutSettDate("SP")) 282 | order.setField(quickfix.Price(quote.price)) 283 | order.setField(quickfix.TransactTime()) 284 | order.setField(quickfix.TimeInForce(quickfix.TimeInForce_IMMEDIATE_OR_CANCEL)) 285 | self.sendToTarget(order, self.orderSession) 286 | 287 | 288 | def onExecutionReport(self, message, sessionID): 289 | self.logger.info("FIXSIM-CLIENT EXECUTION REPORT %s", str(message)) 290 | 291 | def dispatchFromApp(self, msgType, message, beginString, sessionID): 292 | if msgType == '8': 293 | self.onExecutionReport(message, sessionID) 294 | elif msgType == 'W': 295 | self.onMarketDataSnapshotFullRefresh(message, sessionID) 296 | 297 | -------------------------------------------------------------------------------- /Chapter8/fixsim/fixsim/sim.py: -------------------------------------------------------------------------------- 1 | import quickfix 2 | import copy 3 | import uuid 4 | import random 5 | import datetime 6 | import yaml 7 | from twisted.internet import task 8 | 9 | 10 | class MarketDataError(quickfix.Exception): 11 | pass 12 | 13 | 14 | class FixSimError(Exception): 15 | pass 16 | 17 | 18 | def instance_safe_call(fn): 19 | def wrapper(self, *args, **kwargs): 20 | try: 21 | return fn(self, *args, **kwargs) 22 | except quickfix.Exception as e: 23 | raise e 24 | except Exception as e: 25 | self.logger.exception(str(e)) 26 | 27 | return wrapper 28 | 29 | 30 | class FixSimApplication(quickfix.Application): 31 | def __init__(self, fixVersion, logger): 32 | super(FixSimApplication, self).__init__() 33 | self.fixVersion = fixVersion 34 | self.logger = logger 35 | 36 | @instance_safe_call 37 | def sendToTarget(self, message, sessionID): 38 | if sessionID is None: 39 | raise FixSimError("Invalid Fix Session") 40 | 41 | self.logger.info("FixSimApplication:SEND TO TARGET %s", message) 42 | quickfix.Session.sendToTarget(message, sessionID) 43 | 44 | @instance_safe_call 45 | def fromApp(self, message, sessionID): 46 | print "FROM APP" 47 | fixMsgType = quickfix.MsgType() 48 | beginString = quickfix.BeginString() 49 | message.getHeader().getField(beginString) 50 | message.getHeader().getField(fixMsgType) 51 | msgType = fixMsgType.getValue() 52 | 53 | self.logger.info("FixSimApplication.fromApp: Message type %s", str(msgType)) 54 | self.dispatchFromApp(msgType, message, beginString, sessionID) 55 | 56 | 57 | class IncrementID(object): 58 | def __init__(self): 59 | self.__value = 0 60 | 61 | def generate(self): 62 | self.__value += 1 63 | return str(self.__value) 64 | 65 | 66 | def float_range(first, last, step): 67 | if last < 0: 68 | raise ValueError("last float is negative") 69 | 70 | result = [] 71 | while True: 72 | if first >= last: 73 | return result 74 | 75 | result.append(first) 76 | first += step 77 | 78 | 79 | def create_logger(config): 80 | import logging 81 | import logging.handlers 82 | 83 | def syslog_logger(): 84 | logger = logging.getLogger('FixClient') 85 | logger.setLevel(logging.DEBUG) 86 | handler = logging.handlers.SysLogHandler() 87 | logger.addHandler(handler) 88 | return logger 89 | 90 | def file_logger(fname): 91 | logger = logging.getLogger('FixClient') 92 | logger.setLevel(logging.DEBUG) 93 | handler = logging.handlers.RotatingFileHandler(fname) 94 | logger.addHandler(handler) 95 | return logger 96 | 97 | logcfg = config.get('logging', None) 98 | if not logging: 99 | return syslog_logger() 100 | 101 | target = logcfg['target'] 102 | if target == 'syslog': 103 | logger = syslog_logger() 104 | elif target == 'file': 105 | filename = logcfg['filename'] 106 | logger = file_logger(filename) 107 | else: 108 | raise FixSimError("invalid logger " + str(target)) 109 | 110 | logger.addHandler(logging.StreamHandler()) 111 | return logger 112 | 113 | 114 | def load_yaml(path): 115 | with open(path, 'r') as stream: 116 | cfg = yaml.load(stream) 117 | 118 | return cfg 119 | 120 | 121 | def create_fix_version(config): 122 | # ONLY FIX44 FOR NOW 123 | fix_version = config.get('fix_version', 'FIX44') 124 | if fix_version != 'FIX44': 125 | raise FixSimError("Unsupported fix version %s" % str(fix_version)) 126 | 127 | import quickfix44 128 | 129 | return quickfix44 130 | -------------------------------------------------------------------------------- /Chapter9/TradingStrategyDualMA.py: -------------------------------------------------------------------------------- 1 | # Python program to get average of a list 2 | from collections import deque 3 | 4 | def average(lst): 5 | return sum(lst) / len(lst) 6 | 7 | class TradingStrategyDualMA: 8 | def __init__(self, ob_2_ts, ts_2_om, om_2_ts): 9 | self.orders = [] 10 | self.order_id = 0 11 | 12 | self.position = 0 13 | self.pnl = 0 14 | self.cash = 10000 15 | 16 | self.paper_position = 0 17 | self.paper_pnl = 0 18 | self.paper_cash = 10000 19 | 20 | self.current_bid = 0 21 | self.current_offer = 0 22 | self.ob_2_ts = ob_2_ts 23 | self.ts_2_om = ts_2_om 24 | self.om_2_ts = om_2_ts 25 | self.long_signal=False 26 | self.total=0 27 | self.holdings=0 28 | self.small_window=deque() 29 | self.large_window=deque() 30 | self.list_position=[] 31 | self.list_cash=[] 32 | self.list_holdings = [] 33 | self.list_total=[] 34 | 35 | self.list_paper_position = [] 36 | self.list_paper_cash = [] 37 | self.list_paper_holdings = [] 38 | self.list_paper_total = [] 39 | 40 | 41 | def create_metrics_out_of_prices(self,price_update): 42 | self.small_window.append(price_update) 43 | self.large_window.append(price_update) 44 | if len(self.small_window)>50: 45 | self.small_window.popleft() 46 | if len(self.large_window)>100: 47 | self.large_window.popleft() 48 | if len(self.small_window) == 50: 49 | if average(self.small_window) >\ 50 | average(self.large_window): 51 | self.long_signal=True 52 | else: 53 | self.long_signal = False 54 | return True 55 | return False 56 | 57 | def buy_sell_or_hold_something(self, book_event): 58 | if self.long_signal and self.paper_position<=0: 59 | self.create_order(book_event,book_event['bid_quantity'],'buy') 60 | self.paper_position += book_event['bid_quantity'] 61 | self.paper_cash -= book_event['bid_quantity'] * book_event['bid_price'] 62 | elif self.paper_position>0 and not self.long_signal: 63 | self.create_order(book_event,book_event['bid_quantity'],'sell') 64 | self.paper_position -= book_event['bid_quantity'] 65 | self.paper_cash -= -book_event['bid_quantity'] * book_event['bid_price'] 66 | 67 | self.paper_holdings = self.paper_position * book_event['bid_price'] 68 | self.paper_total = (self.paper_holdings + self.paper_cash) 69 | # print('total=%d, holding=%d, cash=%d' % 70 | # (self.total, self.holdings, self.cash)) 71 | 72 | self.list_paper_position.append(self.paper_position) 73 | self.list_paper_cash.append(self.paper_cash) 74 | self.list_paper_holdings.append(self.paper_holdings) 75 | self.list_paper_total.append(self.paper_holdings+self.paper_cash) 76 | 77 | self.list_position.append(self.position) 78 | self.holdings=self.position*book_event['bid_price'] 79 | self.list_holdings.append(self.holdings) 80 | self.list_cash.append(self.cash) 81 | self.list_total.append(self.holdings+self.cash) 82 | 83 | def create_order(self,book_event,quantity,side): 84 | self.order_id+=1 85 | ord = { 86 | 'id': self.order_id, 87 | 'price': book_event['bid_price'], 88 | 'quantity': quantity, 89 | 'side': side, 90 | 'action': 'to_be_sent' 91 | } 92 | self.orders.append(ord.copy()) 93 | 94 | 95 | def signal(self, book_event): 96 | if book_event['bid_quantity'] != -1 and \ 97 | book_event['offer_quantity'] != -1: 98 | self.create_metrics_out_of_prices(book_event['bid_price']) 99 | self.buy_sell_or_hold_something(book_event) 100 | 101 | 102 | def execution(self): 103 | orders_to_be_removed=[] 104 | for index, order in enumerate(self.orders): 105 | if order['action'] == 'to_be_sent': 106 | # Send order 107 | order['status'] = 'new' 108 | order['action'] = 'no_action' 109 | if self.ts_2_om is None: 110 | print('Simulation mode') 111 | else: 112 | self.ts_2_om.append(order.copy()) 113 | if order['status'] == 'rejected' or order['status']=='cancelled': 114 | orders_to_be_removed.append(index) 115 | if order['status'] == 'filled': 116 | orders_to_be_removed.append(index) 117 | pos = order['quantity'] if order['side'] == 'buy' else -order['quantity'] 118 | self.position+=pos 119 | self.holdings = self.position * order['price'] 120 | self.pnl-=pos * order['price'] 121 | self.cash -= pos * order['price'] 122 | 123 | for order_index in sorted(orders_to_be_removed,reverse=True): 124 | del (self.orders[order_index]) 125 | 126 | 127 | def handle_input_from_bb(self,book_event=None): 128 | if self.ob_2_ts is None: 129 | print('simulation mode') 130 | self.handle_book_event(book_event) 131 | else: 132 | if len(self.ob_2_ts)>0: 133 | be=self.handle_book_event(self.ob_2_ts.popleft()) 134 | self.handle_book_event(be) 135 | 136 | def handle_book_event(self,book_event): 137 | if book_event is not None: 138 | self.current_bid = book_event['bid_price'] 139 | self.current_offer = book_event['offer_price'] 140 | self.signal(book_event) 141 | self.execution() 142 | 143 | def lookup_orders(self,id): 144 | count=0 145 | for o in self.orders: 146 | if o['id'] == id: 147 | return o, count 148 | count+=1 149 | return None, None 150 | 151 | def handle_response_from_om(self): 152 | if self.om_2_ts is not None: 153 | self.handle_market_response(self.om_2_ts.popleft()) 154 | else: 155 | print('simulation mode') 156 | 157 | def handle_market_response(self, order_execution): 158 | print(order_execution) 159 | order,_=self.lookup_orders(order_execution['id']) 160 | if order is None: 161 | print('error not found') 162 | return 163 | order['status']=order_execution['status'] 164 | self.execution() 165 | 166 | def get_pnl(self): 167 | return self.pnl + self.position * (self.current_bid + self.current_offer)/2 168 | 169 | -------------------------------------------------------------------------------- /Chapter9/eventbasedbacktester.py: -------------------------------------------------------------------------------- 1 | from chapter7.LiquidityProvider import LiquidityProvider 2 | from chapter9.TradingStrategyDualMA import TradingStrategyDualMA 3 | from chapter7.MarketSimulator import MarketSimulator 4 | from chapter7.OrderManager import OrderManager 5 | from chapter7.OrderBook import OrderBook 6 | from collections import deque 7 | 8 | import pandas as pd 9 | from pandas_datareader import data 10 | import matplotlib.pyplot as plt 11 | 12 | 13 | def call_if_not_empty(deq, fun): 14 | while (len(deq) > 0): 15 | fun() 16 | 17 | 18 | class EventBasedBackTester: 19 | def __init__(self): 20 | self.lp_2_gateway = deque() 21 | self.ob_2_ts = deque() 22 | self.ts_2_om = deque() 23 | self.ms_2_om = deque() 24 | self.om_2_ts = deque() 25 | self.gw_2_om = deque() 26 | self.om_2_gw = deque() 27 | 28 | 29 | self.lp = LiquidityProvider(self.lp_2_gateway) 30 | self.ob = OrderBook(self.lp_2_gateway, self.ob_2_ts) 31 | self.ts = TradingStrategyDualMA(self.ob_2_ts, self.ts_2_om,\ 32 | self.om_2_ts) 33 | self.ms = MarketSimulator(self.om_2_gw, self.gw_2_om) 34 | self.om = OrderManager(self.ts_2_om, self.om_2_ts,\ 35 | self.om_2_gw, self.gw_2_om) 36 | 37 | 38 | def process_data_from_yahoo(self,price): 39 | 40 | order_bid = { 41 | 'id': 1, 42 | 'price': price, 43 | 'quantity': 1000, 44 | 'side': 'bid', 45 | 'action': 'new' 46 | } 47 | order_ask = { 48 | 'id': 1, 49 | 'price': price, 50 | 'quantity': 1000, 51 | 'side': 'ask', 52 | 'action': 'new' 53 | } 54 | self.lp_2_gateway.append(order_ask) 55 | self.lp_2_gateway.append(order_bid) 56 | self.process_events() 57 | order_ask['action']='delete' 58 | order_bid['action'] = 'delete' 59 | self.lp_2_gateway.append(order_ask) 60 | self.lp_2_gateway.append(order_bid) 61 | 62 | def process_events(self): 63 | while len(self.lp_2_gateway)>0: 64 | call_if_not_empty(self.lp_2_gateway,\ 65 | self.ob.handle_order_from_gateway) 66 | call_if_not_empty(self.ob_2_ts, \ 67 | self.ts.handle_input_from_bb) 68 | call_if_not_empty(self.ts_2_om, \ 69 | self.om.handle_input_from_ts) 70 | call_if_not_empty(self.om_2_gw, \ 71 | self.ms.handle_order_from_gw) 72 | call_if_not_empty(self.gw_2_om, \ 73 | self.om.handle_input_from_market) 74 | call_if_not_empty(self.om_2_ts, \ 75 | self.ts.handle_response_from_om) 76 | 77 | 78 | 79 | eb=EventBasedBackTester() 80 | 81 | 82 | def load_financial_data(start_date, end_date,output_file): 83 | try: 84 | df = pd.read_pickle(output_file) 85 | print('File data found...reading GOOG data') 86 | except FileNotFoundError: 87 | print('File not found...downloading the GOOG data') 88 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 89 | df.to_pickle(output_file) 90 | return df 91 | 92 | goog_data=load_financial_data(start_date='2001-01-01', 93 | end_date = '2018-01-01', 94 | output_file='goog_data.pkl') 95 | 96 | 97 | for line in zip(goog_data.index,goog_data['Adj Close']): 98 | date=line[0] 99 | price=line[1] 100 | price_information={'date' : date, 101 | 'price' : float(price)} 102 | eb.process_data_from_yahoo(price_information['price']) 103 | eb.process_events() 104 | 105 | 106 | plt.plot(eb.ts.list_paper_total,label="Paper Trading using Event-Based BackTester") 107 | plt.plot(eb.ts.list_total,label="Trading using Event-Based BackTester") 108 | plt.legend() 109 | plt.show() 110 | 111 | -------------------------------------------------------------------------------- /Chapter9/forloopbacktester.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import pandas as pd 3 | import numpy as np 4 | from pandas_datareader import data 5 | import matplotlib.pyplot as plt 6 | import h5py 7 | from collections import deque 8 | 9 | 10 | def load_financial_data(start_date, end_date,output_file): 11 | try: 12 | df = pd.read_pickle(output_file) 13 | print('File data found...reading GOOG data') 14 | except FileNotFoundError: 15 | print('File not found...downloading the GOOG data') 16 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 17 | df.to_pickle(output_file) 18 | return df 19 | 20 | goog_data=load_financial_data(start_date='2001-01-01', 21 | end_date = '2018-01-01', 22 | output_file='goog_data.pkl') 23 | 24 | # print(goog_data.index) 25 | # for i in goog_data: 26 | # print(i) 27 | # 28 | # import sys 29 | # sys.exit(0) 30 | 31 | # Python program to get average of a list 32 | def average(lst): 33 | return sum(lst) / len(lst) 34 | 35 | class ForLoopBackTester: 36 | def __init__(self): 37 | self.small_window=deque() 38 | self.large_window=deque() 39 | self.list_position=[] 40 | self.list_cash=[] 41 | self.list_holdings = [] 42 | self.list_total=[] 43 | 44 | self.long_signal=False 45 | self.position=0 46 | self.cash=10000 47 | self.total=0 48 | self.holdings=0 49 | 50 | def create_metrics_out_of_prices(self,price_update): 51 | self.small_window.append(price_update['price']) 52 | self.large_window.append(price_update['price']) 53 | if len(self.small_window)>50: 54 | self.small_window.popleft() 55 | if len(self.large_window)>100: 56 | self.large_window.popleft() 57 | if len(self.small_window) == 50: 58 | if average(self.small_window) >\ 59 | average(self.large_window): 60 | self.long_signal=True 61 | else: 62 | self.long_signal = False 63 | return True 64 | return False 65 | 66 | def buy_sell_or_hold_something(self,price_update): 67 | if self.long_signal and self.position<=0: 68 | print(str(price_update['date']) + 69 | " send buy order for 10 shares price=" + str(price_update['price'])) 70 | self.position += 10 71 | self.cash -= 10 * price_update['price'] 72 | elif self.position>0 and not self.long_signal: 73 | print(str(price_update['date'])+ 74 | " send sell order for 10 shares price=" + str(price_update['price'])) 75 | self.position -= 10 76 | self.cash -= -10 * price_update['price'] 77 | 78 | self.holdings = self.position * price_update['price'] 79 | self.total = (self.holdings + self.cash) 80 | print('%s total=%d, holding=%d, cash=%d' % 81 | (str(price_update['date']),self.total, self.holdings, self.cash)) 82 | 83 | self.list_position.append(self.position) 84 | self.list_cash.append(self.cash) 85 | self.list_holdings.append(self.holdings) 86 | self.list_total.append(self.holdings+self.cash) 87 | 88 | 89 | naive_backtester=ForLoopBackTester() 90 | for line in zip(goog_data.index,goog_data['Adj Close']): 91 | date=line[0] 92 | price=line[1] 93 | price_information={'date' : date, 94 | 'price' : float(price)} 95 | is_tradable = naive_backtester.create_metrics_out_of_prices(price_information) 96 | if is_tradable: 97 | naive_backtester.buy_sell_or_hold_something(price_information) 98 | 99 | 100 | 101 | plt.plot(naive_backtester.list_total,\ 102 | label="Holdings+Cash using Naive BackTester") 103 | plt.legend() 104 | plt.show() 105 | -------------------------------------------------------------------------------- /Chapter9/goog.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "GOOG" 2 | ( 3 | dt timestamp without time zone NOT NULL, 4 | high numeric NOT NULL, 5 | low numeric NOT NULL, 6 | open numeric NOT NULL, 7 | close numeric NOT NULL, 8 | volume numeric NOT NULL, 9 | adj_close numeric NOT NULL 10 | CONSTRAINT "GOOG_pkey" PRIMARY KEY (dt) 11 | ); -------------------------------------------------------------------------------- /Chapter9/goog_data.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter9/goog_data.h5 -------------------------------------------------------------------------------- /Chapter9/goog_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | """OHLC data feed.""" 3 | import cgitb 4 | import psycopg2 5 | 6 | conn = psycopg2.connect(database='test') # set the appropriate credentials 7 | cursor = conn.cursor() 8 | 9 | SQL = '''SELECT 10 | dt,high,low,open,close,volume, adj_close 11 | FROM "GOOG" 12 | WHERE dt BETWEEN '2016-11-08' AND '2016-11-09' 13 | ORDER BY dt 14 | LIMIT 100;''' 15 | 16 | 17 | def query_ticks(date_from=None, date_to=None, period=None, limit=None): 18 | """Dummy arguments for now. Return OHLC result set.""" 19 | cursor.execute(SQL) 20 | ohlc_result_set = cursor.fetchall() 21 | 22 | return ohlc_result_set 23 | 24 | 25 | def format_as_csv(ohlc_data, header=False): 26 | """Dummy header argument. Return CSV data.""" 27 | csv_data = 'dt,o,h,l,c,vol\n' 28 | 29 | for row in ohlc_data: 30 | csv_data += ('%s, %s, %s, %s, %s, %s\n' % 31 | (row[0], row[1], row[2], row[3], row[4], row[5] + row[6])) 32 | 33 | return csv_data 34 | 35 | if __name__ == '__main__': 36 | cgitb.enable() 37 | 38 | ohlc_result_set = query_ticks() 39 | csv_data = format_as_csv(ohlc_result_set) 40 | 41 | print('Content-Type: text/plain; charset=utf-8\n') 42 | print(csv_data) 43 | 44 | cursor.close() -------------------------------------------------------------------------------- /Chapter9/hd5pandareader.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | import pandas as pd 3 | import numpy as np 4 | from pandas_datareader import data 5 | import matplotlib.pyplot as plt 6 | import h5py 7 | 8 | def load_financial_data(start_date, end_date,output_file): 9 | try: 10 | df = pd.read_pickle(output_file) 11 | print('File data found...reading GOOG data') 12 | except FileNotFoundError: 13 | print('File not found...downloading the GOOG data') 14 | df = data.DataReader('GOOG', 'yahoo', start_date, end_date) 15 | df.to_pickle(output_file) 16 | return df 17 | 18 | goog_data=load_financial_data(start_date='2001-01-01', 19 | end_date = '2018-01-01', 20 | output_file='goog_data.pkl') 21 | 22 | 23 | goog_data.to_hdf('goog_data.h5','goog_data',mode='w',format='table',data_columns=True) 24 | 25 | h = h5py.File('goog_data.h5') 26 | 27 | print(h['goog_data']['table']) 28 | print(h['goog_data']['table'][:]) 29 | for attributes in h['goog_data']['table'].attrs.items(): 30 | print(attributes) 31 | -------------------------------------------------------------------------------- /Chapter9/kdb_data.py: -------------------------------------------------------------------------------- 1 | from pyq import q 2 | from datetime import date 3 | 4 | #googdata:([]dt:();high:();low:();open:();close:();volume:(),adj_close:()) 5 | 6 | q.insert('googdata', (date(2014,01,2), 555.263550, 550.549194, 554.125916, 552.963501, 3666400.0, 552.963501)) 7 | q.insert('googdata', (date(2014,01,3), 554.856201, 548.894958, 553.897461, 548.929749, 3355000.0, 548.929749)) 8 | 9 | q.googdata.show() 10 | High Low Open Close Volume Adj Close 11 | Date 12 | 2014-01-02 555.263550 550.549194 554.125916 552.963501 3666400.0 552.963501 13 | 2014-01-03 554.856201 548.894958 553.897461 548.929749 3355000.0 548.929749 14 | 15 | # f:{[s]select from googdata where date=d} 16 | 17 | x=q.f('2014-01-02') 18 | print(x.show()) 19 | 20 | 2014-01-02 555.263550 550.549194 554.125916 552.963501 3666400.0 552.963501 21 | -------------------------------------------------------------------------------- /Chapter9/omstimeout.py: -------------------------------------------------------------------------------- 1 | from chapter9.simulatedclock import SimulatedRealClock 2 | import threading 3 | from time import sleep 4 | from datetime import datetime, timedelta 5 | 6 | class TimeOut(threading.Thread): 7 | def __init__(self,sim_real_clock,time_to_stop,fun): 8 | super().__init__() 9 | self.time_to_stop=time_to_stop 10 | self.sim_real_clock=sim_real_clock 11 | self.callback=fun 12 | self.disabled=False 13 | def run(self): 14 | while not self.disabled and\ 15 | self.sim_real_clock.getTime() < self.time_to_stop: 16 | sleep(1) 17 | if not self.disabled: 18 | self.callback() 19 | 20 | 21 | class OMS: 22 | def __init__(self,sim_real_clock): 23 | self.sim_real_clock = sim_real_clock 24 | self.five_sec_order_time_out_management=\ 25 | TimeOut(sim_real_clock, 26 | sim_real_clock.getTime()+timedelta(0,5), 27 | self.onTimeOut) 28 | def send_order(self): 29 | self.five_sec_order_time_out_management.disabled = False 30 | self.five_sec_order_time_out_management.start() 31 | print('send order') 32 | def receive_market_reponse(self): 33 | self.five_sec_order_time_out_management.disabled=True 34 | def onTimeOut(self): 35 | print('Order Timeout Please Take Action') 36 | 37 | if __name__ == '__main__': 38 | print('case 1: real time') 39 | simulated_real_clock=SimulatedRealClock() 40 | oms=OMS(simulated_real_clock) 41 | oms.send_order() 42 | for i in range(10): 43 | print('do something else: %d' % (i)) 44 | sleep(1) 45 | 46 | print('case 2: simulated time') 47 | simulated_real_clock=SimulatedRealClock(simulated=True) 48 | simulated_real_clock.\ 49 | process_order({'id' : 1,\ 50 | 'timestamp' : '2018-06-29 08:15:27.243860'}) 51 | oms = OMS(simulated_real_clock) 52 | oms.send_order() 53 | simulated_real_clock. \ 54 | process_order({'id': 1, \ 55 | 'timestamp': '2018-06-29 08:21:27.243860'}) 56 | -------------------------------------------------------------------------------- /Chapter9/simulatedclock.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | class SimulatedRealClock: 4 | def __init__(self,simulated=False): 5 | self.simulated = simulated 6 | self.simulated_time = None 7 | def process_order(self,order): 8 | self.simulated_time= \ 9 | datetime.strptime(order['timestamp'], '%Y-%m-%d %H:%M:%S.%f') 10 | def getTime(self): 11 | if not self.simulated: 12 | return datetime.now() 13 | else: 14 | return self.simulated_time 15 | 16 | realtime=SimulatedRealClock() 17 | print(realtime.getTime()) 18 | simulatedtime=SimulatedRealClock(simulated=True) 19 | simulatedtime.process_order({'id' : 1, 'timestamp' : '2018-06-29 08:15:27.243860'}) 20 | print(simulatedtime.getTime()) 21 | -------------------------------------------------------------------------------- /Chapter9/test.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter9/test.h5 -------------------------------------------------------------------------------- /Chapter9/test2.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Algorithmic-Trading/df2c7862b8c63595dd066071801556016f29cda8/Chapter9/test2.h5 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Learn Algorithmic Trading 5 | 6 | Learn Algorithmic Trading 7 | 8 | This is the code repository for [Learn Algorithmic Trading ](https://www.packtpub.com/in/data/learn-algorithmic-trading-fundamentals-of-algorithmic-trading?utm_source=github&utm_medium=repository&utm_campaign=9781789348347), published by Packt. 9 | 10 | **Build and deploy algorithmic trading systems and strategies using Python and advanced data analysis** 11 | 12 | ## What is this book about? 13 | It’s now harder than ever to get a significant edge over competitors in terms of speed and efficiency when it comes to algorithmic trading. Relying on sophisticated trading signals, predictive models and strategies can make all the difference. This book will guide you through these aspects, giving you insights into how modern electronic trading markets and participants operate. 14 | 15 | 16 | This book covers the following exciting features: 17 | Understand the components of modern algorithmic trading systems and strategies 18 | Apply machine learning in algorithmic trading signals and strategies using Python 19 | Build, visualize and analyze trading strategies based on mean reversion, trend, economic releases and more 20 | Quantify and build a risk management system for Python trading strategies 21 | Build a backtester to run simulated trading strategies for improving the performance of your trading bot 22 | Deploy and incorporate trading strategies in the live market to maintain and improve profitability 23 | 24 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/178934834X) today! 25 | 26 | https://www.packtpub.com/ 28 | 29 | ## Instructions and Navigations 30 | All of the code is organized into folders. For example, Chapter02. 31 | 32 | The code will look like the following: 33 | ``` 34 | import pandas as pd 35 | from pandas_datareader import data 36 | ``` 37 | 38 | **Following is what you need for this book:** 39 | This book is for software engineers, financial traders, data analysts, and entrepreneurs. Anyone who wants to get started with algorithmic trading and understand how it works; and learn the components of a trading system, protocols and algorithms required for black box and gray box trading, and techniques for building a completely automated and profitable trading business will also find this book useful. 40 | 41 | With the following software and hardware list you can run all code files present in the book (Chapter 1-10). 42 | ### Software and Hardware List 43 | | Chapter | Software required | OS required | 44 | | -------- | ------------------------------------ | ----------------------------------- | 45 | | All | Python 2.7+ | Windows, Mac OS X, and Linux (Any) | 46 | 47 | 48 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781789348347_ColorImages.pdf). 49 | 50 | ## Errata 51 | 52 | * Page 144 (Bullet pont 4, line 9 of code): **MIN_PROFIT_TO_CLOSE = 10** _should be_ **MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE** 53 | 54 | 55 | ### Related products 56 | * Mastering Python for Finance - Second Edition [[Packt]](https://www.packtpub.com/in/big-data-and-business-intelligence/mastering-python-finance-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789346466) [[Amazon]](https://www.amazon.com/dp/1789346460) 57 | 58 | * Hands-On Machine Learning for Algorithmic Trading [[Packt]](https://www.packtpub.com/in/big-data-and-business-intelligence/hands-machine-learning-algorithmic-trading?utm_source=github&utm_medium=repository&utm_campaign=9781789346411) [[Amazon]](https://www.amazon.com/dp/178934641X) 59 | 60 | 61 | ## Get to Know the Authors 62 | **Sebastien Donadio** 63 | Sebastien Donadio is the Chief Technology Officer at Tradair, responsible for leading the technology. He has a wide variety of professional experience, including being head of software engineering at HC Technologies, partner and technical director of a high-frequency FX firm, a quantitative trading strategy software developer at Sun Trading, working as project lead for the Department of Defense. He also has research experience with Bull SAS, and an IT Credit Risk Manager with Société Générale while in France. He has taught various computer science courses for the past ten years in the University of Chicago, NYU and Columbia University. His main passion is technology but he is also a scuba diving instructor and an experienced rock-climber. 64 | 65 | **Sourav Ghosh** 66 | Sourav Ghosh has worked in several proprietary high-frequency algorithmic trading firms over the last decade. He has built and deployed extremely low latency, high throughput automated trading systems for trading exchanges around the world, across multiple asset classes. He specializes in statistical arbitrage market-making, and pairs trading strategies for the most liquid global futures contracts. He works as a Senior Quantitative Developer at a trading firm in Chicago. He holds a Masters in Computer Science from the University of Southern California. His areas of interest include Computer Architecture, FinTech, Probability Theory and Stochastic Processes, Statistical Learning and Inference Methods, and Natural Language Processing. 67 | 68 | 69 | ### Suggestions and Feedback 70 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 71 | 72 | 73 | ### Download a free PDF 74 | 75 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
76 |

https://packt.link/free-ebook/9781789348347

--------------------------------------------------------------------------------