Peak of the day - internship at Deep Blue Capital

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