Peak of the day - internship at Deep Blue Capital Nout Jan Dogterom June 10, 2016 Bachelor thesis Supervisor: dr. Robin de Vilder dr. Asma Khedher Victor Harmsen, MSc Korteweg-de Vries Instituut voor Wiskunde Faculteit der Natuurwetenschappen, Wiskunde en Informatica Universiteit van Amsterdam Abstract For this thesis, an internship was followed at Deep Blue Capital. Here, events of stocks exceeding their daily maximum up to that point were investigated with the goal of devising a trading strategy based on such events. Additional demands on the events yielded promising trading strategies. Alongside, the occurence of events and the performance of the algorithm following events were compared between historical data and data generated according to theoretical models. Title: Peak of the day - internship at Deep Blue Capital Author: Nout Jan Dogterom, [email protected], 10386580 Supervisor: dr. Robin de Vilder dr. Asma Khedher Victor Harmsen, MSc Date: June 10, 2016 Korteweg-de Vries Instituut voor Wiskunde Universiteit van Amsterdam Science Park 904, 1098 XH Amsterdam http://www.science.uva.nl/math 2 Contents 1. Introduction 1.1. Stock trading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. My internship . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 5 2. Mathematical framework of trading 2.1. Background and framework concepts . . . . . . . . . . . . . . . . . . . . 2.2. Data dredging and how not to . . . . . . . . . . . . . . . . . . . . . . . . 2.3. Mathematical models for stock price returns . . . . . . . . . . . . . . . . 6 6 9 10 3. Algorithm and performance analysis 14 3.1. My first algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.2. Historical sample data performance . . . . . . . . . . . . . . . . . . . . . 15 3.3. Theoretical sample data performance . . . . . . . . . . . . . . . . . . . . 21 4. Conclusion 25 5. Popular summary 26 Bibliografie 28 A. Internship 29 B. Matlab code 38 3 1. Introduction This chapter includes the general subject, some background and my motivation. 1.1. Stock trading Stocks have been around for ages, and ever since they have been they have offered all sorts of monetary opportunities waiting for speculators to take them. Ever since its conception, this practice has involved statistics. After all, history has shown us time and time again that we never know for sure what’ll happen next. Uncertainty is certainly involved. Historically however, stocks and derivatives trading always remained an inherently human process: every trade required two parties to agree. Many qualities besides statistical rigor could lead to great success (and, obviously, bankruptcy). An outsider might still equate stock day trading with men shouting frantically through telephones. These men are a dying breed. Subsequent and ongoing inventions and improvements of computers, the internet and (big) data analysis have only very recently combined to allow for a new framework in which stocks may be traded: via computers executing orders without direct human intervention. This algorithmic approach has clear benefits. A computer can keep track of many more time series much more accurately than any human ever could. They require no reimbursement and can work day and night. And, importantly, armed with the right statistical knowledge it may make many more, much more calculated choices than the shouting men ever could. The floors no longer flooded. Source: [1] 4 1.2. My internship The practice of letting a superiorly programmed computer invade a human game with such high potential payoff has fascinated me since from well back into my childhood. As the focus in this area lies strongly on quantitive analysis and programming, it in part fueled my choice for a mathematics bachelor. During an internship at Deep Blue Capital, a trading company owned in part by my internship supervisor Robin de Vilder, I could work on the subject myself for the first time. The internship consisted of approximately 30 days of full time office work. The overall goal was to try to create a viable trading algorithm, and compare its results between actual data and data generated from theoretical models. Reflection with Robin and Victor Harmsen during an introductory talk yielded a topic to be investigated. Consider the time series of intraday prices of a stock. What might we expect to happen after the stock attains its highest daily value so far? It might stray from its short-term mean even more or gradually fall back, displaying mean-reversing behaviour. There might be a certain degree of predictability involved in the stock’s price following one such event, as we know short-term mean reversion is often present in financial data. My goal was to find further conditions, under which stocks display a statistically significant degree of predictability. This could lead to insight in supply and demand of a stock, and in short-term future prices of the stock. This in turn might lead to a (profitable) trading strategy centered around extrema breaches. Alongside, event occurence was compared between real life financial data and some time series models used to analyze or predict real life data. 5 2. Mathematical framework of trading Before a high-level description of the research can be given, it is useful to introduce some of the concepts involved. The first section focusses on the ideas and data manipulations used in the processing of the data used and by the algorithm. Some attention is paid to data dredging in the second section. The third section treats several theoretical time series models sometimes used to model or generate financial data. 2.1. Background and framework concepts The main goal of the internship was to create a Trading algorithm, an algorithm specifying how to trade stocks. An algorithm is broadly defined as a fixed set of steps translating some input into some output. A cooking recipe provides an intuitive example fit for any parent to understand. After programming the idea into a script a computer may execute it and, in this case, trade the way a human might. Trading algorithms may use current, as well as past, stock prices and trade volumes. These are organised in so-called time series, countable sets of sequentially observed data, in this case stock prices at specified times. They are of the form zt = {z0 , z1 , . . . }. Note the time series convention of denoting the whole series by zt , not z. During the internship, raw data came in the form of two kinds of time series: interday data with one value specified for every day, whereas in intraday data the fluctuation occuring during a day is specified (say, one price for each minute). In time series analysis a certain stock y’s price interday time series as a whole is denoted as ŷt , and its intraday time series is denoted as yt . Within the series set t, usually an integer, takes values corresponding to the time of day in some form. For example, the amount of minutes past 8 a.m. may be the number used. Intraday time series are denoted yt , and (first) differences ∆yt = yt − yt−1 . Simultaneously, we may regard the movement of a stock y’s price as a stochastic process, denoted also by y = (δt )t∈{1,...,n} . Stock prices may also be regarded as continuoustime stochastic processes, but this is not very relevant for this thesis as all our data was discrete of nature. The discretization, implicitly declared here, stems from our sampling of the price (once each minute). Note the difference in notation between the different schools of science: yt for a time series analist versus y = (δt )t∈{1,...,n} for a mathematician studying stochastic processes. The former sees the series simply as data, while the latter sees the data as a discrete sampling of a realization of the process in which we’re interested. Both notations are 6 used in this thesis. Where data is concerned or theory is firmly in the time series domain, we’ll adopt the time series notation, and likewise stochastic processes will follow their own notation. An obvious first demand to any ‘good’ trading algorithm is that it should be profitable, at least in expectation. The algorithms look for situations where some aspect of market behaviour may be predicted in expectance, and try to capitalize on what they think is likely to take place. At Deep Blue, markets are continuously monitored to exclude stocks displaying sudden significant movements the algorithms could incorrectly pick up on. Positions in stocks not deemed unsafe are not judged individually before being taken, however. The algorithm should self-sufficiently judge many stock price movements covering several orders of magnitude in price. Generalizing the stock time series information using the operations below allows the algorithm to treat all stocks in the same manner. For example, suppose at time t∗ we may either invest our capital in a stock x with xt∗ = 10 or in a stock y with yt∗ = 100. Suppose it turns out that ∆xt∗ +1 = ∆yt∗ +1 = 1. Though these increases are identical, investing the same quantity in the 10 dollar stock would yield us a profit ten times larger! To correct for the different monetary values, we restrict ourselves to looking at returns time series. For a stock y at time t∗ , we denote these by yt∗ − yt∗ −1 . ry,t∗ = yt∗ −1 This concept may be applied both intraday and interday. A stock’s past return time series will yield its current price from some start price, but this is a calculation involving many multiplications. Having this calculation additive is much more convenient. The log returns of a series accomplish this. The idea of log returns is based on the well-known approximation log(1 + x) ≈ x, for small |x|. If we apply this to a return yt∗ −yt∗ −1 yt∗ −1 yields yt∗ − yt∗ −1 yt∗ − yt∗ −1 ≈ log 1 + yt∗ −1 yt∗ −1 yt∗ −1 yt∗ − yt∗ −1 = log + yt∗ −1 yt∗ −1 yt∗ = log . yt∗ −1 Hence we define the log return for a stock y at time t∗ as yt∗ lry,t∗ = log . yt∗ −1 This works beautifully, because stock returns are usually very small (< 0.1). Even interday returns rarely exceed 0.1. Errors stemming from the use of log returns rather than ordinary returns are negligible. The log return of a stock y between two points in 7 P time t and t + n is simply nk=1 lry,t+k . It can also be calculated directly as log(yt+n /yt ), yielding a nearly identical result because of the approximation. This is powerful, as now log returns between any two points in time can easily be calculated. Additionally, these time series display normalized and additive behaviour! The algorithm is concerned with the running daily maximum of a stock. This is, for a certain stock on a certain day at a certain time, defined as the highest price the stock has attained on that day up until that time. Note that this can be obtained at any time in the day as the current highest value so far, whereas the (ordinary) daily maximum can only be given after a day has ended. After all, stock price might exceed the running daily maximum in the remainder of a day. There is another issue that we must adress. We are interested in stocks attaining their extrema, but these events might simply be caused by general market turmoil. In this situation the extremum in stock price is not caused by performance of the underlying asset, so behaviour might be different. Take for example a stock surpassing its running daily maximum. Often, stocks display mean reversing behaviour, where deviations from a short-term mean are corrected, returning the price to this (lower) mean. If the index is also doing well however, the stock might simply adjust for that and settle in its new equilibrium level instead of reversing. To exclude these cases, we focus not just on time series of stock prices, but on special corrected time series with the attribute market neutral. Market neutral log returns of a stock y with respect to an index i at time t∗ are defined by mlry,i,t∗ = lry,t∗ − βlri,t∗ . The parameter β measures the correlation between the stock and the index. It stems from the Capital Asset Pricing Model. A β of 1 indicates the stock is perfectly correlated with the index over the most recent period. A somewhat high correlation between stock and index prices is to be expected, as the index price is calculated as the weighed average of the different stocks it includes. This includes the stock at hand. In practice, indices cannot be traded. Instead, a future reflecting the level of the index is used. A future is a standardized forward trade contract. A forward contract is an agreement to trade an asset at a certain price at a certain time, as opposed to right now. We may use ordinary least squares (OLS) regression to find the value of β. Regression is concerned with finding the best, usually linear, fit through some data points. By doing so dependencies between variables can be discovered, and models made. Ordinary least squares regression finds a linear fit. So in this case, the regression we make is lry,t = α + βlri,t + t . The series t form form the distances from the data points to the line. These data points are called the residuals. The name OLS stems from the usage of the sum of the squared residuals, 2t . These are minimized over the regression parameters to find the best fit, in this case over α and β. This yields the β value we’re after. In our regression, we let the length of the part of the time series reflect the period over which we want to know the correlation between y and i. So going from lry,t to mlry,i,t we correct the returns of the stock by a part explained by the returns of the index. We regard our market neutral stock relative to the index, rather than relative to the whole world. 8 Ordinary least squares theory states[5] that we may write β= Cov(lr̂y,t , lr̂i,t ) . Var(lr̂i,t ) It is customary to have β reflect the current state of the stock, so only the last 3 months’ worth of data points are to be included here. This amounts to around 60 ordinary workdays. In calculating our β’s, we require at least 45 days on which both future and stock have a well-defined closing price. Remember that for our international flock of stocks, closing times vary and the corresponding future value at the time needs to be retrieved manually. The first 60 days have their β set to the value calculated on the 61st day as an approximation, as not enough data might be available for them. Note that β depends both on day and stock, and we should write βi,t where t reflects its interday place in time. The process of calculating the β in Matlab from price time series is displayed in Appendix B. Another small subtlety is that of prices. Throughout the analysis, it may be assumed that a stock has a single, well-defined price at any time. This is not precisely the case, however. Each traded stock has a bid price and an ask price. The bid price is the highest current buy offer. This is the price we could sell at should we sell right now. The ask price is the highest currect sell offer. This is the price we could buy at should we want to buy at this instant. There is usually a small gap between the prices, with the mid price, the average of the bid and ask prices, inbetween. The mid price is the stock’s price if we disregard the bid-ask aspect. Should we decide to buy a stock immediately at a certain time or price, we’ll have to pay the ask price. Otherwise, prices might have changed by the time our offer fills. Likewise, should we want to sell a stock immediately, we will only receive the current bid price. At the end of a deal, however, we have some of our assets to trade and we may ask for or bid at the mid price. For example, Suppose we want to set up a short future position from t = 50 to t = 120. At t = 50 we’ll have to sell the future, receiving only the bid price at that time. At t = 120, we gradually buy back our futures at the then-current mid price. 2.2. Data dredging and how not to Throughout this internship, historical financial data is used to formulate and test a trading strategy. When comparing strategies, a good first measure is to check their performance on this historical sample to see how they would fare when given ‘actual’ data. It is an alluring continuation of this train of thought to maximize our algorithm over our sample in order to find the best algorithm. This is a statistical fallacy, however. The overall performance of an algorithm is dependent on its general ability to set up certain trades following certain events, but when testing on historical data it is also heavily dependent on the specific realized data at hand. Comparing many algorithms’ 9 performance on the same set of data dramatically increases the chance of the top perfrorming algorithms’ performance being a result of this specific data, rather than its quality. This process of overfitting the data is known as data dredging. The key difference between data dredging and sound statistical testing is that rather than first formulating a hypothesis and then using the data to test it, many hypotheses are (implicitly) considered trying different strategies and parameters against the data, and those apparently significant are returned. Every test has a so-called significance however, a necessery accepted percentage of false positives. Because many hypotheses are considered, some are bound to appear significant. When nonetheless trying to extract effectiveness information from past data, the prefered method is to keep part of the data seperate. This is our holdout data, useful for the out-of-sample test. If our in-sample testing reveals a certain result, we now have means to check if the conclusion was not overly data-based. It is essential to always have holdout data ready to check results. A subtle addition here is that every time holdout data is turned to, it is also mined! 2.3. Mathematical models for stock price returns Many theoretical models have been proposed for time series, in attempts to capture and predict movements. Stock return time series are tougher to model than for example the time series of the Netherlands population. This is due to their high kurtosis and clustered volatility. The Dutch population time series follows its general trend more closely, as can be seen in figure 2.1. Figure 2.1.: The Dutch population size through time[2], plotted in log scale A simple model yt = αt + t , with t the stochastic error term and α some real constant, should suffice to explain (and predict!) the yearly growth in this case. The parameter α can be obtained from the data by OLS regression. We do see a clear acceleration in population, perhaps coinciding with the industrial revolution. A simple model could easily describe the distinction between pre- and post-revolution life and population growth. For example, we might estimate alpha for both periods seperately and use the value corresponding to the period we’re interested in. 10 Compare this graph to the historical value of the Dow Jones index shown in figure 2.2. A more complex model is needed to generate data similar to the Dow values. It should come as no surprise that description, generation and prediction of such data is much harder, especially in the long term. A sizeable population of bankrupt traders can attest. Figure 2.2.: Dow Jones Industrial Average values through time, plotted in log scale In this section, such beefier models are considered, or merely dipped, into. Our aim for this thesis is to compare model samples and historical data using our algorithm. Both quantity (investment size) and quality (return) of trades matter here. The algorithm relates to running maximum breaches, using the short-term mean reversion present in economic data to achieve a degree of predictability. Therefore my hypothesis is that model trade quality will depend strongly on whether mean reversion is present in the model. One of the simplest models, no stranger in mathematicians’ circles, is called the Wiener process. The Wiener process is the model used to describe Brownian motion, it was not made to fit financial data! Brownian motion was famously first observed by Robert Brown in 1827, looking at tiny particles bouncing through a microscope. We are interested in the one-dimensional analogue. A Wiener process in our case is a continuous-time, real-valued stochastic process W . It is characterized by the following properties[3]: 1. W0 = 0 2. The function t → Wt is continuous in t with probability 1 3. The process has stationary, independent increments 4. The increment Wt+s − Ws is distributed N (0, t) The Wiener process can be viewed as the limit of rescaled simple (discrete) random walks [3]. This allows us to generate samples with a finite time step. In discrete form, the model takes on the form √ ∆W = ∆Wt ' ∆t, 11 where ∼ N (0, 1), and values of ∆Wt are independent for any two time intervals ∆t. As there are no parameters, this model will not get it right often. Its (abundant) uses lie outside finance. It does serve as a basis for other models, which is why it is included here. Adding two parameters, which can be fitted to better model diverse (financial) data, results in the generalized Wiener process[4] denoted GW . These parameters are the mean change per unit time, the drift rate, and the variance per unit time, the variance rate. This gives the opportunity to include a trend a ∈ R into the model, as well as adjustable volatility controlled by b ∈ R. In analogue to the Wiener process, it is described algebraically by √ ∆GW = a∆t + b ∆t, where ∼ N (0, 1) independently for any two values of ∆t. The generalized Wiener process seems a reasonable candidate to model financial time series, to some extent. One key aspect of stock returns is not present yet, however. Returns in the generalized Wiener are dependent of price level, where this is never the case for stocks. Instead of a constant trend (and constant rise in expected value) we desire constant expected returns. This is achieved by the geometric Brownian motion process GBB, an expansion of the generalized Wiener process. It is described by √ √ ∆y = a∆t + b ∆t. ∆GGB = aGGB∆t + bGGB ∆t, or y The geometric Brownian motion is widely used to model stock price behaviour[4]. The Black-Scholes-Merton option pricing equation is based on this stock price model. However, this model does properly not reflect the excessive volatiliy which is at times present in financial data. Another class of model entirely, coming much more from a time series than from a stochastic process-angle, is the so-called autoregressive process, or AR(1)[5]. In models with an autoregressive component, new values are explained in part by a function of the previous values. It is described for a stock y at time t∗ by yt∗ = αyt∗ −1 + t∗ , where t∗ = ∼ N (0, σ 2 ) independently, with α ∈ R and σ 2 > 0 parameters. A constant may be included on the right side to model a trend, yielding yt = αyt−1 + β + t for β ∈ R. The parameter σ 2 describes the volatility, and α prescribes how fast shocks die out. Note that for α = 1 the shocks do not die out, and we end up with a Wiener process. Also note that this simple model does display mean reversion! As with the Wiener process, the AR is a relatively simple process not really fit to model complex time series such as those generated in finance. It is included here for comparison’s sake and ease of use. An extension, used to model stock returns is GARCH, for General autoregressive conditional heteroskedasticity[6]. The distinctive feat of this model is its clustered volatility, allowing us to model the changing volatility occuring in financial time series. This is achieved by including the previously fixed variance of t 12 into the model. Mathematically, it is stated for a stock y at time t∗ , as yt∗ = µ + t∗ t∗ ∼ N (0, σt2∗ ) σt2∗ = a0 + a1 2t∗ −1 + a2 σt2∗ −1 , with parameters µ ∈ R representing the long-term equilibrium, and a0 , a1 , a2 ∈ R≥0 . These three must be positive as the variance σt2 cannot be negative for any value of t. Both the previous outcome and the previous parameter value are incorporated in a new volatility parameter value. This generates a situation where extreme returns (high or low) in the recent past are indicative of future extreme returns. The same holds true for small returns. This is what is meant by clustered volatility. We use Mathematica to generate series of data from the models. Wherever parameters are involved, these need to be estimated based on our dataset. The generalized Wiener and geometric Brownian processes require only sample drift and variance rates. Sample drift requires a linear fit on the data, and variance is also estimated in the usual way. For AR processes, linear regression can also be used to estimate the parameters of the model[5]. However, parameter estimation can also be an involved process. Parameters in the GARCH model are not directly observed, such as the variance in the geometric Brownian. Neither can they be calculated directly using some closed form expression involving the data, as the AR process’ parameter φ can be by ordinary least squares. Instead, calculation relies on maximum likelihood. Given data and a mathematical model with parameters, maximum likelihood seeks to maximize the likelihood function (probability) L(θ|It ) of the data occuring, over the parameters. Here θ is a vector including all parameters of the model, and It is the set of information at time t. This optimization yields the parameters most likely to yield the data we have. The calculation is different for every model. For an introduction to the GARCH case, see [6]. The likelihood in terms of the GARCH parameters, assuming normal conditional returns up until time t, can be written 1 − e L(α0 , α1 , α2 , µ|r1 , r2 , . . . , rt ) = p 2πσt2 (rt −µ)2 2σt2 − 1 p e 2 2πσt−1 (rt−1 −µ)2 2 2σt−1 ... p 1 2πσ12 e − (r1 −µ)2 2 2σ1 . To make the optimization easier, it is customary to maximize the log of the likelihood l(θ|It ) instead. It may be written t t t 1X 1 X (ri − µ)2 . l(α0 , α1 , α2 , µ|r1 , r2 , . . . , rt ) = − log(2π) − log(σi2 ) − 2 2 i=1 2 i=1 σi2 2 We substitute σi2 = α0 + α1 2i + α2 σi−1 in the latter equation and proceed to take derivatives to a parameter. We set the derivative equal to 0 to solve for an expression for that parameter. Note that the initial volatility value σ12 also needs to be estimated, though for time series containing a lot of data its exact value will not matter much. The optimizations are not always easy and linear. For this project, we rely on Mathematica to execute them. 13 3. Algorithm and performance analysis The performance of the algorithm I constructed is evaluated. First, it is briefly introduced. For a more elaborate explanation on how it was conceived, see the internship chapter in the appendix. In the second section we analyze performance on the historical high frequency data sample. In the third section we compare this to performance on data generated according to theoretical models. 3.1. My first algorithm The algorithm spends most of its time monitoring current and recent past prices for every stock, looking for what I’ve come to simply call events. It enters a position if and only if it encounters an event in the data. Because of the specification of an event, by my research stocks tend to react with a price fall following an event. This price fall is capitalized on in the span of just around an hour and a half, depending on how fast the stock sells off when the position is closed. We consider a point (k, i, j) in our time × date × stock-space to be an event for fixed parameters if • The stock j is, at this time i, at its running daily maximum, e. g. price(k, i, j) > price(p, i, j) ∀p ∈ {0, . . . , k − 1}. • The stock j has a return of at least 2% over the 30 minutes before time k. • At time k, bid-ask spread of stock j is at most 0.1%, or 10 basis points. Dropping the demand of fixed parameters, we may change the minimum rise demand, as well as the time in which this is to take place and the maximum spread at the current time. We may also change how long we enter our position after an event, the default from observation of reaction curves is set at 100 minutes. We expect the collection of events to remain largely constant under slight variation of the parameters due to overlap. This serves as a check against data dredging. The demand of positive return guarantees us only stocks volatile on the short term are included. Without this demand, returns following an event were on average much smaller as very slow stocks happening to surpass their running daily maximum were also included. The demand of low spread asserts that the market to be entered is in equilibrium, with the price not stemming from a sudden drop in supply or demand as 14 often happens early in the morning. This constraint is more practical of nature, as we could not obtain the stocks at the mid price deemed so attractive if we tried, if the spread is too large. 3.2. Historical sample data performance Using standard parameters and our (out-of-sample!) data set the algorithm yields the following, promising, euro, profit-to-day graph over all data: Figure 3.1.: Cumulative profit using the algorithm with default parameters The largest single loss, clearly visible in the image above on the top right, takes place on day 382 and indeed seems no more than an unlucky bet: Shorting resulted in a negative return of 10%, one of the most extreme in all of the dataset, over one of the largest investments. This investment still shrivels in size compared to the total investment, so the largest loss seems in check. A rule of thumb for profit graphs states that the underlying algorithm is promising only if the steepest decline in the graph is less than half of the total height of the graph. Our graph satisfies this rule. In our sample of around 509 minutes of trading each day, 387 days and 1126 stocks, we have a total of 2.22 · 108 possible times for events to take place. In these, our algorithm found 3513 matches. This amounts to a 0.0016% chance for a stock to partake an event at any given time. Due to the large amounts of data available, enough events may still be found. 15 Our bookkeeping variables allow us to check for occurances of skewness in them. We want to make sure , one day, stock or time does not have have a controlling impact on the shape of the profit curve. In this case, odds of reproducibility would be low as we probably data dredged and the algorithm would not work on a different data set. We prefer a generally uniform distribution of profits and event occurence over the bookkeeping variables. Events are distributed through the days, intraday time and stocks according to figures 3.2, 3.3 and 3.4, respectively. Figure 3.2.: Number of events occuring per sample trading day 16 Figure 3.3.: Number of events occuring per sample trading time Figure 3.4.: Number of events occuring per sample stock Note that in figure 3.3 a skewness is present towards the morning. This can be 17 explained by the fact that in the morning it is much more likely for a stock to breach its running daily maximum, a prerequisite for an event. This suggests other measures of short-term average exceeding combined with the other event prerequisites might also yield great results, while making more use of the later parts of trading days. Distribution of events through days and stocks is not completely uniform either. This is to be expected, however. Financial markets are infamous for their sudden and extreme movements. Profit is bound not to be made at a constant rate, unless we further split our investments. The levels of skewness seem acceptable. We also have access to the distributions of our investments and returns. Both displayed below in figures 3.5 and 3.6, respectively. Their respective means are 3.10 · 104 and 4.87 · 10−4 . Figure 3.5.: Histogram of all realized investments The shape of the histogram in figure 3.5 is easily explained. For the majority of our trades, less than the set maximum of 100000 euros can be invested in the stock. Because for every euro invested in the stock another β euros have to be invested in the future, the total invested per trade may exceed 100000 euros. Some high-volatility stocks have β > 1.5 on some days, resulting in a total investment exceeding 250000 euros. 18 Figure 3.6.: Histogram of all realized log returns x The most striking aspect of the returns is their average. This should approximately equal the average return, total profit divided by total investment. Instead, it is only half. This might be explained by the correlation between small investments and relatively extreme returns. Both occur in quiet markets: without much action going on, ask and bid volumes are small. This also means demand or supply might drop suddenly. For example, a single buy offer might make up most of the ask volume. This results in dramatically changed buy or ask prices without the other budging much. The mid price does notice this, and responds to it. This causes artificial volatility the algorithm picks up, resulting in small trades. On a side note, the series of returns has a kurtosis of over 11. Contrary to what the histogram might suggest, a normal distribution is an abysmal model for them. The total investment amounts to 1.09 · 108 . This is spread out over 387 days however, and even within those days not all trades occur simultaneously. As can be seen in figure 3.7, displaying the total amount invested per sample day, the highest amount invested at any point is 1.50 · 106 . 19 Figure 3.7.: Total investment per sample trading day Another possible issue is that our results might stem from data dredging, having not made the clear distinction between holdout and sample data. Results from data dredging tend to be effective only on the data sample they were generated on. Standard parameters (30 minutes, 2 percentage increase, 10 basis points spread, 100 minutes before selling) may also be the result of data dredging. For example because 30 minutes on this sample generates a much larger overall profit than 28 minutes. This distorts our view of the real-world profitability of the algorithm, as it is likely that in this hypothetical case the larger profit at 30 minutes stems from the precise data and is not significant. Three checks have been made to assure our algorithm is not the result of data dredging. First, the final data was introduced when the whole algorithm save for the precise rise condition (p% in m minutes, the parameters p and m being the subjects here) had been conceived. It is thus significant that this algorithm works on the new data. We used our rise condition to brush up the overall profit slightly, settling on 30 minutes and 2%. Second, deviations from these precise parameter values were checked for profitability as well. Due to the large overlap in events found (for example between 30 minutes and 45 minutes), results are fairly similar for different parameter values. This implies that the general profitability is not dependent on the precise parameter values. This would be a possible symptom of data dredging. Lastly, the old dataset was fed back into the algorithm once the new dataset was used to check and slightly optimize it. This yielded a promising average return of 40 basis points over only 84 trades. 40 basis points is higher than the expected performance as judged by the larger sample, but this is still a promising rather than a sinister indication. 20 3.3. Theoretical sample data performance In order to best compare results between the historical and the theoretical data, the metrics used to compare performance with the theoretical data are also calculated for the historical data. Event quantity is measured by the observed probability of an event occurring (mentioned last section, at 0.0016% for the historical data). The (average) response to an event, and specifically the average of all returns 100 minutes after every event, measure event quality. This last number will be different from our profit figure because trades are not weighed by the amount of shares available. It will also not equal our average return size, because it is calculated slightly differently. The historical data has an average response of −7.5 basis points 100 minutes after the events. Overall and average response to an event are shown below. Figure 3.8.: Reaction to an event for every event in the historical data Figure 3.9.: Average reaction to an event in the historical data 21 In the generalized Wiener data sample, 2512 events occur in 509000 moments where an event could take place, an observed event probability of 0.49%. The unweighed average return over a long position taken after each event is −0.27 basis points. Figure 3.10.: Reaction to an event for every event in the generalized Wiener data Figure 3.11.: Average reaction to an event in the generalized Wiener daa In the geometric Brownian data sample, 1749 events occur, again out of 509000 that could have. This amounts to an observed event probability of 0.34%. The unweighed average return over a long position taken after each event is 2.3 basis points. 22 Figure 3.12.: Reaction to an event for every event in the geometric Brownian data Figure 3.13.: Average reaction to an event in the geometric Brownian data In the GARCH(1,1) data sample, 2615 events occur out of again 509000 possible event occurances, an observed event probability of 0.51%. The unweighed average return over a long position taken after each event is 1.16 basis points 23 Figure 3.14.: Reaction to an event for every event in the GARCH(1,1) data Figure 3.15.: Average reaction to an event in the GARCH(1,1) data For data generated by all three models, the algorithm clearly does not work well. Events occur much more frequently in all theoretical data (by a factor of magnitude 103 , conservatively estimated), but no clear-cut response follows an event in any of the theoretical models. This once again proves computers do not completely define intraday stock prices yet: clear marks of human sentiment can be found in the historical data, exploited by the algorithm but not present in the models taught to our students of econometrics. Because of the large size of the data set, it is unlikely that this basic response is explained by chance alone. 24 4. Conclusion By not being able to disprove it using the checks in the previous chapter, we have asserted that it is possible to make a profitable trading strategy centered around stocks exceeding their running daily maximum. Though averagely a mean reversing trend is visible in historical stock price data following a running daily maximum pass, this trend is too weak to actively generate a profit trading. Essential additional requirements before entering a trade were found to be a steep price increase preceding the maximum breach, and a limit to the spread at the time of purchasing. Other additions were also considered but did not improve the performane of the algorithm on our test samples. These include demanding at least n times the maximum is exceeded in m minutes, demanding there be a new running minimum in the m minutes preceding the running daily maximum breach, applying the algorithm to running minima, changing the maximum demand to be only the maximum of the past 3 hours, or a more ‘continuous’ version of the maximum, taking the position a few minutes after the maximum, and cutting our losses by selling as soon as a certain loss is reached. The algorithm saw many more opportunities to enter positions in data generated according to theoretical models, but the profit from all those trades seems completely unpredictable. The algorithm does not reliably generate any profits, or losses indicating that ‘shorting the algorithm’, setting up the opposite position following an event, might yield positive results. The structure of how the market tends to react to running maximum breaches is thus not included in the models, eventhough mean reversion sometimes is in other forms. Looking back, the main point of improvement in this project would be stricter surveillance of what constitutes in sample data, and consequently what exactly the holdout data was and when it was to be used. Though no evidence nullifying the conclusions of this research was found during the research itself, guarding the holdout data more carefully would have improved the significance of the conclusions. From this point, the best way to estimate the quality of the algorithm is to program it into a real time paper trading simulator. This would be my next step in research, if I had more time on my hands. A larger, fresh data set would also shed further light on its profitability. Additional variations of the algorithm could also be devised. 25 5. Popular summary As possibly the epitome of human progress, we have endowed computers with the ability to trade stocks for us. Computers do not get tired, never demand a pay raise, keep much more detailed track of many more stocks and, importantly, never let emotions get in their way. But, given the opportunity, by which measure would you have your digital trader army trade? For this thesis I was given an internship at Deep Blue Capital and the opportunity to specify one such algorithm. No actual positions were entered by my algorithm, but the algorithm was implemented in Matlab and used on a historical data set consisting of prices of over 1100 stocks, over the period August 2014 - February 2016. This helped formulating and testing it, but obviously offers no guarantees. In order to let our computers trade, we need to specify what sort of positions it should enter in certain situations. The computer then scans stock price developments around the world (or whatever part we desire) looking for said situations. My internship counselor Robin de Vilder gave me a broad class of situations after which a certain degree of predictability might be present. These situations are the times at which a stock attains the highest value it had so far during the day. We call this highest value so far the running daily maximum. In such ‘extreme’ situations, a phenomenon called mean reversion tends to come into play. It tells us here that stocks at their running daily maximum tend to revert to their short-term average price, dropping in price in the process. To assure that stocks are indeed out of equilibrium, we also demand that in the time prior to a stock attaining its running daily maximum, it rises relatively sharply. Before entering a position we also check that the highest price the stock is bought for and the lowest price the stock is sold at, the so-called bid and ask prices, do not differ overly much. this might indicate market turmoil or prices which are overly volatile due to low bid and ask volumes, bad times to buy. Using these rules, the algorithm entered around 3500 hypothetical positions in the sample data. The cumulative profit of these trades over the time period August 2014 - February 2016 is shown in the following figure. 26 Figure 5.1.: Cumulative profit using the algorithm with default parameters Mathematical models have been developed in order to analyze and predict the movement of stock prices. Data was generated using these models, and the algorithm was applied to that data. The algorithm did not yield favorable results with any of the models’ data, even though a certain degree of mean reversion is present in some of the models. The inherent predictability present in the current markets, used by my algorithm, could be concluded to be due to human effects, even while computers dwarf humans in trade volume. 27 Bibliography [1] Wired.com, checked 1 april 2016. http://www.wired.com [2] Wolframalpha.com, checked 1 may 2016. http://www.wolframalpha.com/input/?i=dutch+population+size+through+time [3] S Lalley, Lecture 5: Brownian Motion, Mathematical Finance 345, 2001. http://www.stat.uchicago.edu/ lalley/Courses/390/Lecture5.pdf [4] J C Hull, Options, futures and other derivatives - 8th ed, p284-286, Pearson, 2012. [5] C Heij et al, Econometric methods with applications in business and economics, p558560, Oxford, 2004 [6] R Reider, Volatility forecasting I: GARCH Models, Time Series Analysis and Statistical Arbitrage, 2009. http://cims.nyu.edu/ almgren/timeseries/Vol Forecast1.pdf 28 A. Internship This chapter contains a week-to-week description of my activities at Deep Blue Capital. I recommend those not active in financial mathematics to scan chapter 2 first. Much of the parlance introduced is used here. Week 1 I started at Deep Blue on February 18th, 2016. After brief introduction to the firm and its employees, I set to work. At my disposal was a data set consisting of several attributes (Bid, ask, and mid prices, as well as ask- and bid-volumes) of a number of European stocks. The corresponding index used to calculate β’s cannot be traded directly (none can). We obtain prices corresponding to the index returns from a future representing the index, also included in the data. Furthermore, the data contained a set of daily values for more attributes for all stocks (opening price, closing price, etcetera) also used to calculate β. I started off working towards β-corrected log returns of all stocks, per 5 minutes, using a β calculated seperately for each day and stock. Because the stocks are based in several different countries, the data was rather non-homogeneous, however. All days have measurement points ranging from 8:00 till 18:00, but different stocks start, interrupt and stop trading at different times. The array of prices displays a NaN in the case of such an interruption. Making the dataset homogeneous proved the first challenge. This being the first week I naively tried to hardcode this myself. Mistakes were made, including but not limited to the following. I took the last price of the day for the future instead of the price corresponding to the moment the stock stops trading for the day. I tried to catch all the different trading times for different stocks manually. I hardcoded all sorts of numbers that turned out to be variable, such as the number of data points per day. The gauging of the β’s also took some work. Help from Victor resulted in better Matlab code. This generates a 3d array containing a datapoint for each day for each moment for each stock. A serpate array was made containing a datapoint for each day for each moment for the future. These arrays still contained NaNs. From this we could calculate all β values. One of β’s properties may be used to check empirically if the retrieved values are correct. For a day i, βi minimizes Var(rs − βrf ), taking again the last 59 observations of both series. So for x ∈ [0, 2] we may plot Var(rs − xrf ) and see if the minimum is indeed attained at the value of βi This turned out to be the case for several randomly chosen days. A comparison with β’s supplied by Bloomberg did not show large deviations either. These checks assure results so far were correct. 29 Week 2 Victor gave me a larger data set, with data points for each minute for each stock, taken over more days. The procedure so far was applied to this set, too. Armed with the correct β’s, I could calculate market neutral minute-to-minute log returns. Taking the cumulative sum of these returns yielded the standardized and market neutral stock price development. Several hickups occurred finalizing this, but a Thursday afternoon proved almost enough to iron out the details. Many exceptions had to be taken care of, such as the NaNs still present in the array of corrected series and a stock without valid entries for the first 100 days. I noted that some β’s were negative, from which no apparent problems arose. From the cumulative market neutral log returns of the stocks, we could now set up a boolean-filled matrix of the same size containing for every moment for every day for every stock whether or not the stock passes its running daily maximum at that moment. This matrix is the basis of our investigation. We could repeat the proces for both minima and total extrema, as well as other attributes. But focus was on the maxima. This opened up many opportunities and questions to research. It may be no large surprise that the first question I asked myself, in retrospect, is not a very informative one. I decided to compare the total amount of extrema breaches in a day, to suitable AR(1) and generalized Wiener models. For the Wiener model, I found that the variance rate in the normal does not influence the amount of extrema breaches. In the AR(1)-model yt = αyt−1 +t , fitting α, which can be done via a simple regression of yt on yt−1 , resulted for every day and every stock in an α greater than 0.99. Greater α resulted in fewer extrema breaches, however taking α > 1 will not result in a stationary process and our data had fewer breaches still, so the model seemed unfit to model extrema breaches in financial time series. In the generalized Wiener model, adding a trend to the normal increased the amount of extrema breaches. However, even with no trend, the amount of extrema was much larger than that in the data. Week 3 Figures were made of the running day maxima, for both simulated model and the data. This was done using a single self-written function that simply takes a time series, computes the running maxima and plots them. Because the AR-parameters depend on the stock chosen, the stock highest in my data folder, with number 101, was picked to allow for comparison. A resulting average plot was generated too, for both model and simulations. A selection is shown below. 30 Figure A.1.: A plot of daily cumulative market neutral log returns for stock 101 for every day Figure A.2.: A plot of around 400 simulations, generated using brownian motion Figure A.3.: A plot of the running maxima of the previously shown BM-generated series 31 Figure A.4.: A plot of the average of the running maxima of the stock Note that figure A.4 does not just display the square root-like shape we might expect. Instead, there seem extra maximum breaches in the late afternoon. Models additionally required to display this behaviour may need an additional component reflecting afternoon market behaviour. Another question we may ask, using last week’s boolean matrix, is what a stock tends to do after it reaches a maximum. To visualize this, we plot the average of the cumulative returns for a certain amount of minutes after a maximum breach, for every maximum breach in our data set. Doing this for several consecutive minutes, we deduce an ‘average response’, to a maximum breach for all stocks. The result seems surprisingly continuous. Figure A.5.: A plot of the average reaction to a maximum breach, against the amount of minutes since the breach This also holds for the event of a minimum breach, though with a smaller rebound: 32 Figure A.6.: A plot of the average reaction to a minimum breach, against the amount of minutes since the breach As a followup topic, on Thursday a start was made towards generating such plots for more complex, compound events such as (at least) n maximum breaches in the last m minutes. Several results were gathered for small values of m and n. Week 4 More time was spent debugging the previously mentioned script. Sadly, once running the program showed there is little money to be made using the strategy it implemented, regardless of the combination of m and n. Because so many combinations exist, outliers are bound to be present so a single combination yielding a positive result was not enough to compensate for the general lack of structure and probably the result of data dredging. First steps were made towards a more promising idea: demanding a certain price rise in addition to attainment of a daily maximum (extremum) before a moment is to be marked as an event. For example, we might demand that a stock attain its running daily maximum and was at least 2% lower an hour earlier. This idea quickly became the main focus for my trading algorithm, though other algorithms using maximum breakthroughs were considered in weeks 5. Week 5 Another take at the problem is attempted, marking a (time, day, stock)-pair as an event only if the stock is now at an extremum and attained its opposite extremum within a certain amount of minutes ago. For an example, we might set an event to be any stock exceeding its running minimum while having exceeded its running maximum within the last hour. Again, sadly, no fortunes flow from this strategy, as no clear average behaviour follows the event. To find out what might explain seemingly significant results from the algorithms looking at past events, I made it possible to plot the series from the event till 90 minutes 33 after the event for every event, rather than just the average. This way, an outlier influencing the average at a certain time after the event may easily be spotted. Strongly correlated events were filtered from the results by letting the algorithm skip to the next day immediately after an event has been found. For example, demanding 8 rises of the running maximum in the last 10 minutes will find 4 seperate events in a stock exceeding its maximum 12 times in a row! This way, extra significance is put on the movement of the stock after roughly the first time the event took place, as the other events strongly correlate with it. This might cause seemingly significant effects in the average response, making it a very unwanted effect. In order to remove it, a stock is not considered for 40 minutes after an event took place. To keep track of the distribution of the events over days, stocks and time of days, different lists are created keeping track where each event lies in each of these three dimensions. A histogram shows the vast majority of running maximum breakthroughs occurs in the first twenty percent of a day. Because spreads are much higher during the opening hour, roughly, a check for low bid-ask spread is added to the event demands. Events occuring in the last hour of trading also turned out to skew our results, and were excluded. In these cases, the exchange would close before we could close our position. To still have some price for the stock, we kept the price of the now-untradable stock at the price it had right before trading stopped. This might lead to impossible trade orders, hence the exclusion. An attempt was made to pour all working code into a single Matlab function requiring only the data set, so the algorithm could be tested other sets. No other data was available yet, however. Week 6 I could only work on my internship for a single afternoon due to Good Friday and some of my final exams. The events where a running extremum breakthrough was foreshadowed by its opposing running maximum breakthrough also turned out not to harbour a great deal of trading opportunities. A data set was offered by Victor to test the function mentioned above, but it didn’t suffice as it only contained 14:00-15:30 of every trading day. Part of this chapter was written, to prevent order and details from being forgotten. Week 7 This week marked the start of April, during which I worked at Deep Blue almost full time rather than a day and a half a week, as had previously been the case. Simultaneously, the general idea of this thesis crystallized. I was given a larger dataset by Victor, containing minute-to-minute data of 1126 stocks over the course of 387 days. The prepared function to analyze the data at once turned out not to work. It could not handle the data size increase efficiently. However, it contained a lot of analysis which was not required in the algorithm eventually judged most promising. Removing this resulted in a script that translates the 27GB in data 34 to workable matrices in around 20 minutes. This script only needs to be run once after every Matlab restart. Versions of the trading algorithm all run in under 20 seconds. Because companies fail, merge or change structure, a reasonable amount of incompleteness was expected in the data. This turned out to be the case, with 220 stocks missing β values for more than 20 days. The solution is simple however, using all stockday pairs for which a β can be found rather than excluding irregular stocks. This yields us the most valid days, excluding all invalid ones. By changing the algorithm to not look 90 seperate minutes after an event but only the 90th one, I was able to squeeze all data into a single matrix. This allowed the algorithm to work all data at once rather than a sixteenth per run. Because mid prices behave curiously when bid-ask spreads become large, we decide to add a requirement of ”small spread” on all our potential events. This yields the final specification of an event, as discussed in the Results chapter. Week 8 Due time for a test run of the algorithm. I decided to close the historical deals in two ways, to double check. Via the cumulative returns array of all stocks, and directly by buying and selling at the then current prices. This filtered several sign bugs, but debugging well into week 8 revealed that in cases where a price is left undefined inbetween event and closing time the first method skips an inbetween return. This leaves us prefering the second method of setting up the historical deal. It is displayed in Matlab code appendix A. At this point, save for some extentions, work on the algorithm was done. I decided to apply the algorithm to data sampled from theoretical models to compare performance. The algorithm in its final shape based on minima breaches was created, but worked much worse than the maximum-based version. A quick test was performed by applying the final algorithm with standard parameters to the previous, smaller minute-to-minute data set. The average sample return per trade amounted to 36 basis points over 84 events in the data. This is better than I expect the algorithm to perform on average, but a positive indication. Week 9 Both Asma and Robin were met with, and their initial feedback on the thesis was incorporated. Because in its current setup the algorithm trades mainly in the morning, two attempts were made to replace the ‘running daily maximum exceeding’ demand for an event by a more continuous version. First, the running maximum was required only to be of the past 3 hours. This yielded a weaker average response, evaporating possible profits. The second idea is perhaps best explained by figure A.7. We replace the running maximum by a ‘falling maximum’, dropping by 0.1% every minute. This way, more events take place later in the day. 35 Figure A.7.: a stock’s daily falling maximum Compared to the standard version of the algorithm these changes only add about 100 events and no notable profit, discouraging their addition. Instead of entering a position right at the maximum, a version I implemented waited 5 more minutes to see if another maximum took place. This attempt to weed out the cases where the stock goes up instead of down did not work out. Another version concerned itself with just the stock returns, rather than their market neutral transforms. It performed significantly less well on the sample data, indicating market neutral is the way to go. Victor also generated the results of applying the algorithm to the data set. By backtracing individual events several explanations for the differences between our results were found. I looked back 29 rather than 30 minutes and sold after 100 rather than 90 minutes, for example. We also used slightly different β values. The algorithm survived the test. Week 10 Versions of the algorithm were created that close the position at either a certain profit or loss. The first case prevents a possibly worse loss, the second case cashes in before the profit potentially evaporates. This led to returns such as those displayed in A.8. 36 Figure A.8.: Returns when positions are closed whenever 2% loss is reached All of these versions had lower return and profit than the original algorithm. Striving to keep the algorithm as minimal as possible to avoid data dredging, I decided not to add the changes to the algorithm. Furthermore, sample data of the three discussed theoretical models was generated. Adapted versions of the algorithm were written to analyze the generated sample data. 37 B. Matlab code Data and structure setup The following script was run to gather the data from a folder of tables of past values. Data is gathered into matrices which allow for a relatively high-level description of the algorithm. import_list = dir(’/Users/Nout/Documents/Stage’); % Importing all symbols by name from the folder symbols = []; for d = 1 : length(import_list) name = import_list(d).name; if length(strfind(name, ’day’)) > 0 pos = strfind(name, ’-’); symbols = [symbols, str2num(name(1 : pos - 1))]; end end clearvars import_list name pos; % Remove the futures (id’s 29 and 45) from the symbols list symbols(find(symbols == 29)) = []; symbols(find(symbols == 45)) = []; % Import intraday future data to classify days and times fu_sym = 45; fu1m = importdata(strcat(int2str(fu_sym), ’-data_1min.dat’)); fu1m = fu1m.data; dates = unique(floor(fu1m(:, 1))); nr_t = length(find(floor(fu1m(:, 1)) == dates(1))); nr_d = length(dates); nr_s = length(symbols); 38 % Generate a matrix for each required interday attribute. 15 at most. T_closes = zeros(nr_d, nr_s) * NaN; price_closes = zeros(nr_d, nr_s) * NaN; for j = 1 : nr_s st_day = importdata(strcat(int2str(symbols(j)), ’-data_day.dat’)); if isfield(st_day,’data’) st_day = st_day.data; [l, b] = size(st_day); if l == nr_d && b == 15 T_closes(:, j) = st_day(:, 4); price_closes(:, j) = st_day(:, 5); end end end % Generating future close prices. Checking an interval to evade NaNs fu_closes = zeros(nr_d, nr_s) * NaN; indices2 = find(fu1m(:, 2) > 0); for i = 1 : nr_d for j = 1 : nr_s indices = find(abs(T_closes(i, j) - fu1m(:, 1)) < 2 / (24 * 60)); indices = intersect(indices, indices2); if ~isempty(indices) X = [fu1m(indices, 2), abs(T_closes(i, j) - fu1m(indices, 1))]; X = sortrows(X, 2); fu_closes(i, j) = X(1, 1); end end end clearvars indices indices2 X st_day 39 % Generating stock prices and quantities for each day and moment sts_mids = zeros(nr_t, nr_d, nr_s) * NaN; sts_bids = zeros(nr_t, nr_d, nr_s) * NaN; sts_asks = zeros(nr_t, nr_d, nr_s) * NaN; sts_bid_vs = zeros(nr_t, nr_d, nr_s) * NaN; sts_ask_vs = zeros(nr_t, nr_d, nr_s) * NaN; for j = 1:nr_s st1m = importdata(strcat(int2str(symbols(j)), ’-data_1min.dat’)); if isfield(st1m, ’data’) st1m = st1m.data; [l, b] = size(st1m); if l == nr_t * nr_d && b == 6 for i = 1 : nr_d sts_mids(:, i, j) = st1m((i - 1) * nr_t + 1 : i * nr_t, 2); sts_bids(:, i, j) = st1m((i - 1) * nr_t + 1 : i * nr_t, 3); sts_asks(:, i, j) = st1m((i - 1) * nr_t + 1 : i * nr_t, 4); sts_bid_vs(:, i, j) = st1m((i - 1) * nr_t + 1 : i * nr_t, 5); sts_ask_vs(:, i, j) = st1m((i - 1) * nr_t + 1 : i * nr_t, 6); end end end end % Generating future prices and quantities for each day and moment fu_mids = zeros(nr_t, nr_d) * NaN; fu_bids = zeros(nr_t, nr_d) * NaN; fu_asks = zeros(nr_t, nr_d) * NaN; fu_bid_vs = zeros(nr_t, nr_d) * NaN; fu_ask_vs = zeros(nr_t, nr_d) * NaN; for i = 1 : nr_d fu_mids(:,i) = fu_bids(:,i) = fu_asks(:,i) = fu_bid_vs(:,i) fu_ask_vs(:,i) end fu1m((i fu1m((i fu1m((i = fu1m((i = fu1m((i 1) * 1) * 1) * - 1) - 1) nr_t + nr_t + nr_t + * nr_t * nr_t clearvars st1m 40 1 1 1 + + : : : 1 1 i i i : : * * * i i nr_t, 2); nr_t, 3); nr_t, 4); * nr_t, 5); * nr_t, 6); % Generating interday log returns to calculate betas. logreturnssts = zeros(nr_d - 1, nr_s); logreturnsfu = zeros(nr_d - 1, nr_s); for i = 1 : nr_d - 1 logreturnssts(i, :) = log(price_closes(i + 1, :) ./ price_closes(i, :)); logreturnsfu(i, :) = log(fu_closes(i + 1, :) ./ fu_closes(i, :)); end % Generating betas. We require 45 valid days, that is no NaNs in the log % returns, in the last 60 days. beta is NaN if this is not the case. betas = zeros(nr_d, nr_s); for i = 61 : nr_d for j = 1 : nr_s st_3mnth = logreturnssts(i - 60 : i - 2, j); fu_3mnth = logreturnsfu(i - 60 : i - 2, j); nans_st = isnan(st_3mnth); nans_fu = isnan(fu_3mnth); nans = nans_st + nans_fu; st_3mnth(find(nans)) = []; fu_3mnth(find(nans)) = []; if length(st_3mnth) > 45 temp_cov = cov(st_3mnth, fu_3mnth); betas(i,j) = temp_cov(2,1)/temp_cov(2,2); else betas(i,j) = NaN; end end end for i = 1 : 60 betas(i, :) = betas(61, :); end clearvars nans nans_fu nans_st st_3mnth fu_3mnth temp_cov 41 % Generating intraday log returns and spreads. intraday_logreturnsst = zeros(nr_t - 1, nr_d, nr_s); intraday_logreturnsfu = zeros(nr_t - 1, nr_d); intraday_spreadsst = log(sts_asks ./ sts_bids); for k = 1 : nr_t - 1 intraday_logreturnsst(k, :, :) = log(sts_mids(k + 1, :, :) ./ sts_mids(k, :, :)); intraday_logreturnsfu(k, :) = log(fu_mids(k + 1, :) ./ fu_mids(k, :)); end % Generating market neutral series using intraday log returns. NaNs are % converted to zeros here, so the cumulative sums aren’t messed up. marneut = zeros(nr_t - 1, nr_d, nr_s); for i = 1 : nr_d for j = 1 : nr_s marneut(:, i, j) = intraday_logreturnsst(:, i, j) - betas(i, j) * intraday_logreturnsfu(:, i); for k = 1 : nr_t - 1 if isnan(marneut(k,i,j)) marneut(k, i, j) = 0; end end end end % Generating cumulative market neutral series, to locate maximum breaches. marneut_csum = zeros(nr_t - 1, nr_d, nr_s); for i = 1 : nr_d for j = 1 : nr_s marneut_csum(:,i,j) = cumsum(marneut(:,i,j)); end end 42 % Generating running daily maxima. maximum_bool = zeros(nr_t - 1, nr_d, nr_s); for i = 1 : nr_d for j = 1 : nr_s dagmax = 0; for k = 2 : nr_t - 1 price = marneut_csum(k, i, j); if price > dagmax maximum_bool(k, i, j) = 1; dagmax = price; end end end end clearvars price dagmin dagmax b d i j k l fu1m 43 Trading algorithm The algorithm below uses the output of the previous script, checking for events and responding appropriately. % Event = at least min_change in last mins_before and at most max_spread in % current bid-ask spread. At an occerance we short st - beta * fu for % mins_later minutes. mins_before = 30; min_change = 0.02; max_spread = 0.001; mins_after = 100; % ---- setting bookkeeping variables ----investments = []; logret_invests = []; total_investment = 0; total_profit = 0; profit_per_day = zeros(nr_d, 1); picked_per_day = zeros(nr_d, 1); picked_per_time = zeros(nr_t - 1, 1); picked_per_stock = zeros(nr_s, 1); 44 % Browse all time x day x stock scouting events. for i = 1 : nr_d for j = 1 : nr_s k = 1; while k < nr_t - 120 if maximum_bool(k, i, j) == 1 t_passed = min(k - 1, mins_before - 1); change_last_minutes = marneut_csum(k, i, j) - marneut_csum(k - t_passed, i, j); % Check for event if change_last_minutes > min_change && intraday_spreadsst(k + 1, i, j) < max_spread %-------- SETTING UP THE DEAL -------% Look mins_after minutes after the transgression. Buy % the available amount of stucks, limited at 100k euro. nr_available = min(sts_bid_vs(k + 1, i, j), floor(100000 / sts_bids(k + 1, i, j))); % Short log return stock sell_price_st = sts_bids(k + 1, i, j); buy_price_st = sts_mids(k + mins_after + 1, i, j); logret_invest_st = log(sell_price_st/buy_price_st); % Long log return future sell_price_fu = fu_asks(k + mins_after + 1, i); buy_price_fu = fu_mids(k + 1, i); logret_invest_fu = log(sell_price_fu/buy_price_fu); % Weighed total log return logret_invest = logret_invest_st + betas(i, j) * logret_invest_fu; invest_size = nr_available * sell_price_st * (1 + abs(betas(i, j))); invest_profit = invest_size * logret_invest; 45 %----- MAKING THE DEAL IF NO NANS OCCUR ----if ~isnan(logret_invest) % Updating bookkeeping variables: picked_per_time(k) = picked_per_time(k) + 1; picked_per_stock(j) = picked_per_stock(j) + 1; picked_per_day(i) = picked_per_day(i) + 1; investments = [investments; invest_size]; logret_invests = [logret_invests; logret_invest]; total_investment = total_investment + invest_size; total_profit = total_profit + invest_profit; profit_per_day(i) = profit_per_day(i) + invest_profit; end k = k + max(40, mins_before); end end k = k + 1; end end % Print i to check progress i end % Quick summary matches_in_data = length(logret_invests) realistic_profits = total_profit/total_investment*10^4 % Generate profit graph plot(cumsum(profit_per_day)) clearvars mins_before min_change max_spread mins_after change_last_minutes ... t_passed nr_available sell_price_fu sell_price_st buy_price_fu buy_price_st ... logret_invest_st logret_invest_fu logret_invest invest_size invest_profit ... k i j 46
© Copyright 2026 Paperzz