Metamodeling with Perl and AMPL

by Christian Hicks and Dessislava Pachamanova





Listing One 



%kMonthHash = ("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,  "May" => 5,

               "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, 

               "Nov" => 11, "Dec" => 12);

%kConvertHash = ("3month" => 90, "6month" => 180, "1year" => 360, 

               "2year" => 720, "5year" => 1800, "10year" => 3600, 

               "30year" => 10800);

sub read_file

{

    my($inFile) = @_;

    my($state, $temp, $period, $yield);

    open(INPUT, $inFile) || die("Cannot open $inFile");

    $state = 0;

    while (<INPUT>)

    {

        if ($state == 0)

        {

            #if (/U.S. Treasury yield curve/)

            if (/\*U.S. Treasuries\*/)

            {

                $state = 1;

            }

        }

        elsif ($state == 1)

        {

            #READ IN CURRENT DATE

            # Sun, 4 Jul 1999, 11:32am EDT

            if (/[A-Z][a-z][a-z],\s*([0-9]+)\s*([A-Z][a-z][a-z])\s*([0-9]+),/)

            {

              $gDay = $1;

              #assign a numerical value to the month according to %kMonthHash 

              $gMonth = $kMonthHash{$2};

              $gYear = $3;

              $state = 2;

            }

            else

            {

              die("cannot parse date");

            }

        }

        elsif ($state == 2)

        {

            #if (/Bills/)

            if (/Prc Chg/)

            {

                $state = 3;

            }

        }

        elsif (($state == 3) || ($state == 5))

        {

            if ((/Notes/) || (/\[U.S. Treasury Yield Curve\]/))

            {

                $state++;

            }

            elsif (/^(\S+)\s+[^(]+\(([^)]+)\)/) #READ IN RELEVANT DATA

            {

                $period = $kConvertHash{$1};

                push(@gPeriodList, $period);

                $yield = $2;

                push(@gYieldList, $yield);

            }

            elsif (/\s*[+-][0-9+-]+\s*$/)

            {

                # do nothing

            }

            else

            {

                die("cannot parse data: $_");

            }

        }

        elsif ($state == 4)

        {

            #if (/Bonds\s+Coupon\s+Mat\s+Date/)

            if (/Prc Chg/)

            {

                $state = 5;

            }

        }

    }

    close(INPUT);

}







Listing Two



use Math::Ampl;

use strict;



#SPECIFICATION OF INPUT DATA:

my($NumStocks) = 3;

#vector of expected returns

my(@ExpectedReturnsList) = (0.08, 0.09, 0.12);

#vector of standard deviations for each stock

my(@StdDeviationsList) = (0.15, 0.20, 0.22);

#vector of target portfolio returns 

my(@TargetReturnsList) = (0.08, 0.085, 0.09, 0.095, 0.10, 0.105, 

                                              0.11, 0.115, 0.12);

#RESULTS STORAGE:

#hash table to store optimal portfolio standard deviation results after 

#solving all optimization problems

my(%OptimalStdDeviationsHash) = (); 

#hash table to store optimal portfolio holdings after solving the 

#portfolio optimization problem for each value of TargetReturn

my(%OptimalHoldingsHash) = (); 



#OUTPUT FILES:

my($kOutputFile) = "efficientFrontier.out"; #to store Latex table 

my($kFrontierGraphFile) = "efficientFrontier.jgr"; #to store graph



#DECLARATION OF AMPL PROBLEM INSTANCE:

my($gProblem);



#optimization model problem to be solved: minimize portfolio variance 

#subject to constraints on portfolio expected return



#optimization model file to be pased to AMPL using PerlAmpl



sub setup_ampl_mod

{

    my($mod);

    $mod =<<EODATA;

    

    param NumStocks;

    param ExpectedReturns{1..NumStocks};

    param StdDeviations{1..NumStocks};

    param TargetReturn;

    

    var holdings{1..NumStocks};



    minimize portfolio_variance:

    sum{i in 1..NumStocks}

             (StdDeviations[i]*StdDeviations[i]*holdings[i]*holdings[i]);



    subject to portfolio_target_return:

    sum{i in 1..NumStocks} (ExpectedReturns[i]*holdings[i]) >= TargetReturn;



    subject to portfolio_total:

    sum{i in 1..NumStocks}(holdings[i]) = 1;

EODATA

        $gProblem->Input_Mod_Append($mod);

}

#optimization data file to be passed to AMPL using PerlAmpl

sub setup_ampl_dat

{

    #target portfolio return passed for this instance of the problem

    my($inTargetReturn) = @_; 

    my($iStock);



    $gProblem->Input_Dat_Add_Param_Item("NumStocks", $NumStocks);

    $gProblem->Input_Dat_Add_Param_Item("TargetReturn", $inTargetReturn);

    for($iStock = 1; $iStock <= $NumStocks; $iStock++)

    {

        $gProblem->Input_Dat_Add_Param_Item("ExpectedReturns", 

                                    $iStock,$ExpectedReturnsList[$iStock-1]);

        $gProblem->Input_Dat_Add_Param_Item("StdDeviations", 

                                     $iStock,$StdDeviationsList[$iStock-1]);

    }

}

#request for AMPL to keep track of variables of interest



sub setup_ampl_display

{

    $gProblem->Input_Display_Clear;

    $gProblem->Input_Display_Add("solve_result");

    $gProblem->Input_Display_Add("portfolio_variance");

    $gProblem->Input_Display_Add("holdings");

}

#script for running the problem in AMPL and obtaining the results

sub solve_problem

{

    #target portfolio return passed for this instance of the problem

    my($inTargetReturn) = @_;

    my($solved);



    $gProblem->Input_Mod_Clear; 

    $gProblem->Input_Dat_Clear;

    setup_ampl_dat($inTargetReturn);

    setup_ampl_mod();

    setup_ampl_display();

    $solved = $gProblem->Solve;

    return $solved;

}

#PRINT GRAPH OF EFFICIENT FRONTIER DYNAMICALLY

#the output is a .jgr file which can then be converted to a .ps file



sub print_graph

{

    my($graph);

    my($targetReturn); #portfolio target return, read from list

    my($portfolioVariance); #optimal result from optimization problem

    my($portfolioStdDeviation); #to be computed from portfolio variance

    my(@CurrentHoldings); #obtained from AMPL output  

    my($iStock); #counter



    open(GRAPH, ">$kFrontierGraphFile") || 

        die ("Cannot open file \"$kFrontierGraphFile\" for graph: $!");

    $graph = "newcurve marktype none linetype solid linethickness 1";

    $graph .= " label : Efficient Frontier\n"; 

    $graph .= "\tpts ";

    foreach $targetReturn (@TargetReturnsList)

    {

        solve_problem($targetReturn);

        $portfolioVariance = 

            $gProblem-> Output_Display_Get_Value("portfolio_variance");

        $portfolioStdDeviation = sqrt($portfolioVariance);

        $graph .= " $portfolioStdDeviation $targetReturn ";

#store optimal standard deviations if necessary

        $OptimalStdDeviationsHash{"$targetReturn"} = $portfolioStdDeviation;

#if necessary, store also the optimal holdings for each value of TargetReturn

        for($iStock = 1; $iStock <= $NumStocks; $iStock++)

        {

            $CurrentHoldings[$iStock-1] =

                $gProblem->Output_Display_Get_Value("holdings",$iStock);

            $OptimalHoldingsHash{"$targetReturn,$iStock"} = 

                $CurrentHoldings[$iStock-1];

        }

    }

    print GRAPH "newgraph\n xaxis\n label : Standard Deviation\n";

    print GRAPH "yaxis \n ";

    print GRAPH "label : Expected Portfolio Return\n";

    print GRAPH "$graph\n\n";

    print GRAPH "legend defaults\n x 0.17 y 0.10\n";

    close GRAPH;

}

#PRINT A TABLE WITH RESULTS IN LATEX TABLE FORMAT

sub print_table

{

    my($portfolioStdDeviation);

    my($targetReturn);



    open(OUTPUT, ">$kOutputFile") || 

        die ("Cannot open file \"$kOutputFile\" with results: $!"); 

    printf OUTPUT "%s %s ", "Expected Return", "&";

    printf OUTPUT "%s %s \n", "Standard Deviation", "\\\\";



    foreach $targetReturn (@TargetReturnsList)

    {

        $portfolioStdDeviation = $OptimalStdDeviationsHash{$targetReturn};

        printf OUTPUT "%2.3f %s ", $targetReturn, "&";

        printf OUTPUT "%2.3f %s \n",  $portfolioStdDeviation, "\\\\";   

    }

    close OUTPUT;

}

#MAIN

#initialization

Math::Ampl::Initialize($kAmplDir, $kAmplBin, $kTempDir, 1);

$gProblem =  new Math::Ampl;



print_graph();

print_table();





Listing Three



#Generates one path, and returns a List of Lists indexed by [time

#period, asset number]. The entries equal single-period returns for

#each stock. Single-period returns are multivariate normal random variables.



sub create_scenario

{

    #pass number of stocks in portfolio and number of time periods ahead

    my($inNumStocks, $inNumPeriods) = @_; 



    my(@SimulatedReturnsList); #single time period simulated returns

    my(@ScenarioLoL); #list of lists of asset returns for each time period

    my($iT); #time period counter 



    for ($iT = 0; $iT < $inNumPeriods; $iT++)

    {

        @SimulatedReturnsList = 

                          random_multivariate_normal($inNumStocks,

                               @ExpectedReturnsList, @CovarianceMatrixList);

        #add simulated returns for time period iT to scenario path

        @ScenarioLoL[$iT] = [ @SimulatedReturnsList ];

    }

    return (@ScenarioLoL);

}















6



