Swiss army knife MATLAB programs for quantitative finance
From LiteratePrograms
Making efficient use of MATLAB for quantitative finance requires mastery of several important topics. This article provides a basic introduction to these topics.
Contents |
Functional Object Programming in MATLAB
MATLAB functions are able to store embedded function (like the set_n function below), and to return a structure containing handles to those functions. Here is the simplest functional object programmed like this:
<<firstoo.m>>= function z = firstoo( n) % FIRSTOO - my first real MATLAB object % z=firstoo(2) % z.add_n(3) % z.get_n() % z.set_n(7) % z.get_n() this.n = n; z = struct('get_n',@get_n,'add_n',@add_n, 'set_n', @set_n); function a = get_n() a=this.n; end function u = set_n( m) this.n = m; end function u = add_n(u) u = u +this.n; end end
Optional Arguments
A good management of optional arguments is very useful, our goal here is to be able to call functions like MATLAB built-in ones, id est:
my_function( mandatory_arg1, ..., mandatory_argN, ...
optional_arg1_name, optional_arg1_value, ..., optional_argK_name, optional_argK_value);
for instance:
my_function(100, 2, 'color', 'black', 'figure-handle', figure);
we will build a MATLAB function to help us using such optional arguments in our MATLAB functions. It is called options and will be used like this inside any of our other functions:
function z = any_function(mandatory_arg1, ..., mandatory_argN, varargin)
% ANY_FUNCTION - oneliner help
opt = options({optional_name1, default_value1, ..., optional_nameK, default_valueK}, varargin);
% to access an optional value:
my_value = opt.get(optional_name);
Here are the main elements of the options function:
- an initialization step
- a get method
- a set method
The methods are published with the functional object method:
function_outputs = struct('get',@get_v,'set',@set_v);
The data are stored in the object into the variable named this.
<<options.m>>= function z = options(default, overwrite) % OPTIONS - sandbox for optionnal parameters management % % use: % opt = options({'alpha',1,'beta',2},{'alpha',8,'gamma',3}) % opt.get('alpha') % opt.set('alpha',3) % opt.get('alpha') % opt.get() %%** Optional parameters management %<* Initialization of the structure overwrite parameters if needed %< Internal variable this = struct('value',{values}); %> %< Publication of external methods z = struct('get',@get_v,'set',@set_v); %> %>* %%** Internals %<* Get a value get value %>* %<* Set a value set value %>* end
Overwriting of the parameters. The names and the default values for parameters are given to the function into a cellarray as first argument. It's second argument is another cellarray containing another list of parameter name, parameter value. If the parameter is into the default list, it's value is updated, otherwise the parameter and the associated value are created.
<<overwrite parameters if needed>>= values = default; if nargin>1 for i=1:2:length(overwrite)-1 idx = strmatch(overwrite{i},values(1:2:end-1),'exact'); if isempty(idx) %< New parameter % Added to the list of parameters values{end+1} = overwrite{i}; values{end+1} = overwrite{i+1}; %> else %< Parameter exists % the new value replace the old one values{idx(1)*2} = overwrite{i+1}; %> end end end
The get method. Our options object will have a get method. It can be used in three ways:
- simply to get the value of an existing parameter: opt.get('parameter-name'),
- to get the value of a parameter if it exists, and a default value if it does not exist: opt.get(parameter-name, default_value)
- to get the list of all parameters and thier value: opt.get().
The last use of options is usefull when you want to use your options to call another function:
tmp = my_parameters.get();
another_function(mandatory_parameters, tmp{:});
<<get value>>= function [r, h] = get_v( name, def_value) h = 1:length(this.value); if nargin==0 %< No argument % Return the internal memory r = this.value; %> else idx = strmatch(name, this.value(1:2:end-1),'exact'); if isempty(idx) & nargin>1 %< Default value % Unknown argument but default value is provided. r = def_value; h = 0; %> elseif isempty(idx) & nargin < 2 %< Unknown argument error('options:get:unknown','parameter with name <%s> unknown',name); %> else %< Argument found % Its value (and the associate index) is returned r = this.value{idx(1)*2-1+1}; h = idx(1)*2-1+1; %> end end end
<<set value>>= function set_v(name, value) [v,h] = get_v(name, []); if h>0 this.value{h} = value; else this.value{end+1} = name; this.value{end+1} = value; end end
Interacting with plots
MATLAB plots are dedicated to industrial data plotting. For financial data, it's not so simple, mainly because of the MATLAB date format which is not adapted to its use as xticks.
Store cumization data into figure axes
One way to customize plots in MATLAB is to use their UserData property. Once an axe is created, its UserData property can be access with the get and set MATLAB functions:
<<simple figure>>= figure; plot(cumsum(randn(200,1)),'linewidth',2); legend('A brownian motion'); get(gca,'UserData')
Its default value is empty.
Because the UserData property will possibly used but a lot of different programs to store a lot of different informations, we will use our options object to store heterogeneous data in it.
Using the myuserdata function, we can store and access to heterogeneous informations in this field:
<<myuserdata.m>>= function z = myuserdata(handle, mode, name, value, def_value) % MYUSERDATA - a function to access the UserData field % use: % myuserdata(gca,'set','my-data', rand(10,2)) % v = myuserdata(gca,'get','my-data') ud = get(handle,'UserData'); switch lower(mode) case 'set' if isempty(ud) ud = options({name, value}); else ud.set(name,value); end set(handle,'UserData',ud); case 'get' if isempty(ud) if nargin > 3 z = value; else error('myuserdata:get:unknown','option <%s> not present in the UserData field',name); end else if nargin > 3 z = ud.get(name, value); else z = ud.get(name); end end otherwise error('myuserdata:mode','mode <%s> unknown', mode); end
An example of use is:
myuserdata(gca,'set','test',10); myuserdata(gca,'set','test2',Inf); myuserdata(gca,'get','test') myuserdata(gca,'get','test2')
Real dateticks
The MATLAB function datetick is not very efficient used on real dates. Try for instance:
<<matlab dateticks>>= figure; v=cumsum(randn(200,1));dt=today:today+200-1; plot(dt,v,'linewidth',2) % step 1 datetick % step 2 % zoom manually step 3
You can see that in step 3 all ticks are lost! (simply by zooming into the dotted rectangle)...
It is possible to manage your self your dateticks, using the graphical function text(x,y,'text',properties...) this way:
<<simple self-made datetick>>= figure; plot(dt,v,'linewidth',2); ax = axis; dtx=get(gca,'Xtick'); set(gca,'XtickLabel',[]); text(dtx,repmat(ax(3),length(dtx),1),datestr(dtx,6),'Rotation',60,'HorizontalAlignment','Right')
Even if it does not look better at first glance, you can at least modify a lot of parameters of the ticks (using the usual properties of the text graphical component, and the options of the function datestr).
It's the main fetaure of this mydateticks function. It uses the options' function to store optional arguments (like datestr format or rotation, font size, font name of the labels).
<<mydateticks.m>>= function z = mydateticks( varargin) % MYDATETICKS - simple dateticks management %%** MyDateTicks opt = options({'axe-handle',gca, 'nb-steps',[], 'text-handle', [], ... 'datestr-format',24, 'rotation', 60, 'fontsize',8, ... 'fontname','Arial'},varargin); %<* Initializations this.options = opt; redraw; %>* z = struct('redraw', @redraw); %%** Redraw Redraw end
This redraw function can be called after a zoom on the axe (using the returned handle):
<<Redraw>>= function redraw %<* Get info % From internal state ah = this.options.get('axe-handle'); th = this.options.get('text-handle'); %>* %<* Cleaning % Select good axe, clean xtickslabels axes(ah); ax = axis; dtx = get(ah,'XTick'); set(ah,'XTickLabel',[]); if ~isempty(th) delete(th); end %>* %<* Nb of ticks % It's a function of the number of pixel on screen nb_steps = this.options.get('nb-steps'); if isempty(nb_steps) posf = get(gcf,'position'); posa = get(gca,'position'); w = posf(3)*posa(3); nb_steps = round(w/20); end dv = ax(1):(ax(2)-ax(1))/nb_steps:ax(2); if abs(dv(end)-ax(2))<eps dv = [dv, ax(2)]; end %>* th = text(dv,repmat(ax(3),length(dv),1), ... datestr(dv,this.options.get('datestr-format')), ... 'Rotation',this.options.get('rotation'),... 'HorizontalAlignment','Right', ... 'fontsize',this.options.get('fontsize'), ... 'fontname',this.options.get('fontname')); this.options.set('text-handle',th); end
This function can be used to obtain very fine plots, here is an example in 3 steps:
<<example of mydatetick use>>= figure plot(dt,v,'linewidth',2) z=mydateticks; % step 1 z.redraw('datestr-format', 12) % step 2 z.redraw('datestr-format', 12, 'fontsize',6) % step 3
The important point is to notice that the returned handle (here stored in function z) can be used after a zoom to redraw the dateticks (changing the options if you want).
Structured data manipulations
In Quantitative Finance, there are some structured dataset. It is very important to keep them synchronized.
At this stage (release 2006a, but only since mid 2005), MATLAB offers an object to store such synchronized dataset, it is called a timeseries collection and use mainly the function tscollection. This object is not as optimized at it could be, so here is descibe a more simple way to implement such structured datasets.
The minimal structure to store such data is:
- a title
- a vector of dates (d x 1)
- a matrix of value (d x c)
- a cellarray of column names (1 x c)
It can be initialized like this:
<<creation of my first data structure>>= data = struct('title','Randomized quotes','date',(today:today+199)', ... 'value',100*cumprod(1+[randn(200,1)* 0.10, randn(200,1)* 0.21]/16), ... 'names', {{'Ticker1', 'Ticker2'}})And then it can be plotted like this:
<<plot of my first data structure>>= figure; plot(data.date,data.value,'linewidth',2); title(data.title); axis([min(data.date) max(data.date) min(data.value(:)) max(data.value(:))]); z = mydateticks('datestr-format',25); legend(data.names);
Simple manipulations of structured datasets
Now it's quite easy to use such a dataset.
For instance, to get only some rows preserving the correspondance between the columns:
<<extract rows>>= data.value = data.value(idx,:); data.date = data.date(idx);
Or to get rows between two dates:
<<extract dates>>= if iscell(dates) nate = datenum(dates{1},dformat); if length(dates)>1 nate = [nate, datenum(dates{2},dformat)]; end dates = nate; end if length(dates)<2 dates = [dates, dates]; end idx = find((data.date>=dates(1)) & (data.date<=dates(2))); extract rows
Those two blocks can be used like this:
<<simple dates extraction>>= dformat = 'dd/mm/yyyy'; dates = {'10/06/2006' '14/09/2006'}; extract dates plot of my first data structure
A lot of dataset manipulations can be defined and groupped into a single function, let's call it the zdata function. Features to implement to obtain something usefull are:
- read an excel datasheet and load it into a structured dataset
- extract a column and return a structured dataset with only this column
- get a column values
- extract dates
- save to a mat file
- load from a mat file
<<example4data_structure.m>>= creation of my first data structure plot of my first data structure simple dates extraction plot of my first data structure
Complex manipulations of structured datasets
An interesting feature is to be able to apply functions on structured dataset. Functions that can be applied to a structured dataset are called hooks (like in GNU emacs Lisp). An hook is a functionnal object with a field and three methods:
- names - which is the names of the columns to work on
- func - which is an handle on the function to apply to columns, dates, and optionnal arguments
- datefun - which is an handle on the function to apply to dates and columns
- colfun - which is an handle on the function to apply on column names
Here is a simple function to build hooks:
<<build_hook.m>>= function z = build_hook(varargin) % BUILD_HOOK - build an hook % a hook is a functionnal object with a field an 3 methods: % - names , which is the names of the columns to work on % - func , which is an handle on the function to apply to columns % - datefun, which is an handle on the function to apply to dates % - colfun , which is an handle on the function to apply on column names opt = options({'names', '', 'func', @(v,d)(v), 'datefun', @(d,v)(d), 'colfun', @(x)(x)}, varargin); if isempty(opt.get('names')) error('build_hook:names', 'I need names of columns to work on'); end this.opt = opt; z = @apply; function data = apply(data, varargin) extract structured dataset from column names data.value = feval(this.opt.get('func'),data.value,data.date,varargin{:}); data.date = feval(this.opt.get('datefun'), data.date,data.value); data.names = feval(this.opt.get('colfun'), data.names); end end
The extraction of dataset according to column names should be implemented inside the zdata function; here is a block of code juste dedicated to hook building:
<<extract structured dataset from column names>>= names = this.opt.get('names'); idx = strmatch(names,data.names,'exact'); data.value = data.value(:,idx); data.names = data.names(idx);Hooks can be used that way (the result are the returns of the Ticker2):
<<example of hook building.m>>= my_hook = build_hook('names','Ticker2', ... 'func',@(v,d)(diff(v)./v(1:end-1)), ... 'datefun',@(d,v)(d(1:end-1)), ... 'colfun',@(x)(cellfun(@(n)(['returns of ' n]),x,'UniformOutput',false))) data = my_hook(data) plot of my first data structure
Simple Exponential Brownian Motion
Usefull tools
At first I need some useful tools
<<tokenize.m>>= function t = tokenize(str,sep) % TOKENIZE - string into cellarray using a separator if nargin<2 sep = ';'; end t = cellfun(@(x)(x{1}),regexp(str,sprintf('([^%s]+)%s',sep,sep),'tokens'),'Uniformoutput',false);
Which can be used as in:
>> tokenize(sprintf('v-%d;',1:4),';')
ans =
'v-1' 'v-2' 'v-3' 'v-4'
<<plotstruct.m>>= function h = plotstruct(data, varargin) % PLOTSTRUCT - plot a structured dataset % returns an handle on mydateticks % use: % h = plotstruct(data, options) % options: % - dates : false % - figure : [] % - linewidth: 2 opts = options({'dates', false, 'figure', [], 'linewidth', 2}, varargin); h = []; g = opts.get('figure'); if isempty(g) g = figure; else g = figure(g); end plot(data.date, data.value, 'linewidth', opts.get('linewidth')); legend(data.names); title(data.title); if opts.get('dates') h=mydateticks; end
Simulations
This function (which uses the <<options>> and <<tokenize>> functions) simulate independant exponential brownian motions.
<<brownexp.m>>= function data = brownexp(volatility, nb_points, varargin) % BROWNEXP - simulation of a brownian exponential diffusion % use: % > data = brownexp(volatility, nb_points, options) % options: % - drift : 0 % - start-price : 100 % - time-step : 1/250 % - nb-simulations: 1 % - time-horizon : [] % if time-horizon not empty, it replaces nb_points % % example: % data = brownexp(.20, 250, 'nb-simulations', 5) % plotstruct( data) opt = options({'drift', 0, 'start-price', 100, 'time-step', 1/250, ... 'nb-simulations', 1, 'time-horizon', []}, varargin); if nargin < 2 nb_points = 100; end time_step = opt.get('time-step'); if ~isempty(opt.get('time-horizon')) time_horizon = opt.get('time-horizon'); else time_horizon = time_step*(nb_points-1); end dates = (0:time_step:time_horizon)'; values = opt.get('start-price')*exp( (opt.get('drift') - volatility^2/2) * ... dates+volatility *sqrt(time_step)*cumsum(randn(length(dates),opt.get('nb-simulations')))); data = struct( 'title', 'simulations', 'value', values, 'date', dates, ... 'names', {tokenize(sprintf('sim-%d;',1:size(values,2)),';')});
| Download code |
