pickstock.gms : Stock selection problem with MIRO

Description

Optimization model to pick a small subset of the stocks together with
some weights, such that this portfolio has a similar behavior to our
overall Dow Jones index.


Category : GAMS Data Utilities library


Main file : pickstock.gms   includes :  pickstock.gms  dowjones2016.csv

$title Stock Selection Optimization
* Optimization model to pick a small subset of the stocks together with
* some weights, such that this portfolio has a similar behavior to our
* overall Dow Jones index.

Set       date                 'date'
          symbol               'stock symbol';
$onExternalInput
Parameter price(date<,symbol<) 'Price';
Scalar    maxstock             'maximum number of stocks to select'  /  2 /
          trainingdays         'number of days for training'         / 99 /;
$setNames "%gams.input%" fp fn fe
$if not set fileName $set fileName %fp%dowjones2016.csv
$call.checkErrorLevel gamstool csvread "%fileName%" gdxout=stockdata.gdx id=price Index=1,2 Values=3 UseHeader=y > %system.nullfile% 2>&1
$gdxIn stockdata
$load price
$offExternalInput

Alias (d,date), (s,symbol);
Parameter
    avgprice(symbol)          'average price of stock'
    weight(symbol)            'weight of stock'
    contribution(date,symbol) 'contribution of stock on date'
    index(date)               'Dow Jones index';

Parameter
    fund(date)                'Index fund report parameter'
    error(date)               'Absolute error';

Set td(date)    'training days'
    ntd(date)   'none-training days';
    
* input validataion
set error01(date, symbol);

error01(date, symbol) = price(date, symbol) < 0;

file log / miro.log /;
put log '------------------------------------'/;
put log '        Data validation'/;
put log '------------------------------------'/;
if(card(error01),
  put log 'price:: No negative prices allowed!'/;
  loop(error01(date, symbol),
      put log / ' Symbol ' symbol.tl:4 ' has negative price at the date: ' date.tl:0;
    );
  abort "Data errors detected."
);
putclose log;
    
avgprice(s)       = sum(d, price(d,s))/card(d);
weight(symbol)    = avgprice(symbol)/sum(s, avgprice(s));
contribution(d,s) = weight(s)*price(d,s);
index(d)          = sum(s, contribution(d,s));

Variable
    p(symbol)   'is stock included?'
    w(symbol)   'what part of the portfolio'
    slpos(date) 'positive slack'
    slneg(date) 'negative slack'
    obj         'objective';

Positive variables w, slpos, slneg;
Binary variable p;

Equation
    deffit(date)    'fit to Dow Jones index'
    defpick(symbol) 'can only use stock if picked'
    defnumstock     'few stocks allowed'
    defobj          'absolute violation (L1 norm) from index';

deffit(td)..  sum(s, price(td,s)*w(s)) =e= index(td) + slpos(td) - slneg(td);

defpick(s)..  w(s) =l= p(s);

defnumstock.. sum(s, p(s)) =l= maxstock;

defobj..      obj =e= sum(td, slpos(td) + slneg(td));

Model pickStock /all/;

option optCR=0.01;

td(d) = ord(d)<=trainingdays;
ntd(d) = not td(d);

solve pickStock min obj using mip;

fund(d)  = sum(s, price(d, s)*w.l(s));
error(d) = abs(index(d)-fund(d));

Set fHdr      'fund header'            / dj 'dow jones','index fund'  /
    errHdr    'stock symbol header'    / 'absolute error train', 'absolute error test' /;
    
$onExternalOutput
Scalar error_train                     'Absolute error in entire training phase'
       error_test                      'Absolute error in entire testing phase'
       error_ratio                     'Ratio between error test and error train'
Parameter
       stock_weight(symbol)            'weight'   
       dowVSindex(date,fHdr)           'dow jones vs. index fund'     
       abserror(date,errHdr)           'absolute error'
table dowVSindex;
table abserror;
Singleton Set
firstDayTraining(date)   'first date of training period'
lastDayTraining(date)    'last date of training period' ;
$offExternalOutput

stock_weight(s)                        = w.l(s);
dowVSindex(d,'dj')                     = index(d);
dowVSindex(d,'index fund')             = fund(d);
abserror(td, 'absolute error train')   = error(td);
abserror(ntd,'absolute error test')    = error(ntd);
lastDayTraining(td)                    = td.pos=card(td);
firstDayTraining(td)                   = td.pos=1; 
error_train                            = obj.l;
error_test                             = sum(ntd, error(ntd));
if(error_train > 0,
   error_ratio = error_test/error_train;
else
   error_ratio = inf;);

* parameter including all stocks and dow jones index
$onExternalOutput
Parameter priceMerge(date,*) 'Price (stocks & dow jones)';
$offExternalOutput
priceMerge(d,symbol)        = price(d,symbol);
priceMerge(d,'DowJones')    = index(d);