Conclusions and Further Research
In our attempt to solve the vexing valuation problem for natural gas storage, we deploy new methods within stochastic optimization within the structure of an operationally acute model coupled with intuitive price centric policies.
The model we describe is purposefully structured to accurately account for the various operating characteristics of real storage facilities. This includes the explicit modeling of operational properties taking into account the variations in pressure within the storage facility and the energy costs associated with the use of the compressor during injection. Both of these operational components are usually absent within other models. However, like the Approximate Dynamic Programming approach discussed in Chapter 2, our model lacks the full evolution of the forward curve, where we confine our decisions to the current spot price and month ahead forward price. Further research could be pursued through navigating the computational challenges of natural gas storage as a high dimensional stochastic optimization problem.
CHAPTER 7: Conclusion and Further Research
94
Opportunity for further work within our framing of the natural gas storage valuation problem would involve the further development of the operational policies. We acknowledge that industry professionals who are responsible for making the operational decisions for natural gas storage employ indicators outside of the price evolution of the natural gas market. These may include gross production reports regarding natural gas, total volume within U.S. storage, and weather, all of which would offer a richer and more complex experience with the storage valuation problem.
An exciting aspect of this work is the opportunity to utilize a new approach when solving the storage valuation problem. The Knowledge Gradient is still a new field of research within stochastic optimization with new methods and extensions rapidly appearing. One of these emerging ideas is the Knowledge Gradient with Non- Parametric estimation which embarked on its maiden voyage upon our goal to determine an accurate and computationally efficient method for valuating natural gas storage. Through the use of KGNP, policies were obtained that displayed comparable results to the value of gas storage reported by the natural gas industry. However, the creativity within the policy development aspects of this work was stunted by the dimensional limitation of the KGNP algorithm which could only cope with the manipulation of two parameters. Should the KGNP increase its dimension threshold, it will be an even greater asset in solving stochastic optimization problems such as this one.
CHAPTER 7: Conclusion and Further Research
95
Based on observing appropriate responses to various testing, as well as receiving comparable results to current industry valuations of natural gas storage, we have added to the literature an instrument that successfully deploys the strict use of natural gas prices in policy development for the operational decisions surrounding a natural gas storage facility.
Appendix: Code
96
Appendix: Code
//Natural Gas Storage Model and Pricing Simulations //By Jennifer Schoppe with Professor Hugo Simao
import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Random; import java.util.StringTokenizer; public class ModelSimTesting {
//function will read in simulated price data that was computed in MatLab
public static double[] readExternalFile(String fname) { int numRecs = 0; // the return variable double[]prices_array = new double[2175]; int i = 1;
try {
// setup buffered reader to read in the file one record at a time File filePtr = new File(fname);
FileReader fileReader = new FileReader(filePtr);
BufferedReader bufReader = new BufferedReader(fileReader); // read the file, record by record
i++;
String nextRec = bufReader.readLine();
while (nextRec != null) {
// parse record: three fields, separated by 1 or more tabs StringTokenizer st = new StringTokenizer(nextRec, "\t"); // make sure that the parsed record did contain two fields
if (st.countTokens() == 1) { double spot = Double.parseDouble(st.nextToken().trim()); prices_array[numRecs] = spot; numRecs++; } else {
// issue an error message
System.out.println("Cannot parse rec # "+ i +": "+ nextRec);
System.out.flush(); }
// get the next record i++;
nextRec = bufReader.readLine(); }
// close the input files bufReader.close(); fileReader.close(); }
catch (IOException e) {
System.out.println("Caught exception " + e + " while reading rec #" + i + " of file " + fname);
System.out.println("Exiting!"); System.out.flush();
System.exit(-1); }
Appendix: Code
97
return prices_array;
}
public static double[][] pairs(double step){
//produces array of all possible choices for the percents
double [][] pairs = new double [2][361]; int count = 0;
double upper = 1; for(int i =0; i<19;i++){
double lower = 1; for(int j = 0;j<19;j++){
pairs [0][count] = upper; pairs [1][count] = lower; count++;
lower = lower + step; }
upper = upper + step; }
return pairs;
}
public static double[][] simulate(double[]prices_array) { double [][] prices = new double [2][360];
ArrayList<Double> spot = new ArrayList<Double>(); ArrayList<Double> ExpS = new ArrayList<Double>();
for (int i = 0; i<prices_array.length; i++){
spot.add(prices_array[i]); }
//mean reversion speed calculated using MATLAB regression
double alpha = .0063;
//Performing Exponential Smoothing
int N = 2175; double Beta = .002;
//initialize starting smoothing price and price vector
double E = 0;
for (int i = 0; i< 5; i++ ){ double e = spot.get(i);
E = E + e; }
ExpS.add(E);
for (int i = 1; i < spot.size(); i++) {
ExpS.add(spot.get(i-1)*Beta + (1-Beta)*ExpS.get(i-1)); }
//Spot Price Simulation
double dt = 1;
for (int x = 0;x<12;x++){
for (int y =0; y<30;y++){
Random generator = new Random();
double dz = generator.nextGaussian(); //Volatility calculation //volatility window int m = 360; //year int n = spot.size(); double Sum = 0;
for(int i = n-m; i<n; i++){ double sum = 0;
for(int j = n-m; j<n; j++){
sum = sum + (Math.log(spot.get(j))- Math.log(spot.get(j-1)));
Appendix: Code
98
}
Sum = Sum + Math.pow((Math.log(spot.get(i))- Math.log(spot.get(i-1))-(1/m)*sum),2); }
double sig = Math.sqrt((1/(double)(m-1))*Sum); double sigHat = sig*Math.sqrt(2*alpha)/Math.sqrt(1-
Math.exp(-2*alpha));
int g = spot.size();
double S_old = (spot.get(g-1)); double E_old = (ExpS.get(g-1));
double dxt = alpha*(Math.log(E_old)-Math.log(S_old))*dt +
sigHat*dz; spot.add(S_old + dxt);
prices[0][x*30+y] = S_old + dxt;
ExpS.add((S_old+dxt)*Beta + (1-Beta)*E_old);
double delta = 30-y;
double mu = Math.log(S_old)*Math.exp(-alpha*delta) + Math.log(E_old)*(1-Math.exp(-alpha*delta)); double sd = (Math.pow(sigHat,2)/(2*alpha))*(1-Math.exp(- 2*alpha*delta)); double z = generator.nextGaussian(); double y_t = mu + sd*z; prices[1][x*30+y] = Math.exp(y_t); } } return prices; }
public static double[]CapChange(double Spot, double Forward, double rateI, double rateW, double Imax, double Wmax, double commitment, double contracts, double
history,double upper, double lower) {
//Output: spot injection/withdrawal and forward months injection/withdrawal decision
double[]amount = new double[2];
//decision for spot
double x = 0;
double y = commitment;
double change = 100*((Spot-history)/history);
//for gain in price value
if (change>0) { if (change>= upper){ x = -rateW; } else { x = 0; } } else { if(Math.abs(change)>=lower) { x = rateI; } else { x = 0; } } double z = 0;
//constraints for spot decision
double check1 = 0; double check2 = 0; while(check1==0 || check2==0){ if (x>0){ if (x + y/30 > Imax) { x = Imax - y/30; check1 = 1; } else{
Appendix: Code 99 check1 = 1; } if (y>0){ if((1/rateI)*(x+y/30)>1) { x = rateI - y/30; check2 = 1; } else{ check2 = 1; } } else { if ((1/rateI)*x + (1/rateW)*Math.abs(y/30) >1) { x = (1-(1/rateW)*Math.abs(y/30))*rateI; check2 = 1; } else { check2 = 1; } } } else {
if (Math.abs(x + y/30) > Wmax) {
x = -Wmax - y/30; check1 = 1; } else{ check1 = 1; } if (y>0){ if(((1/rateW)*Math.abs(x)+(1/rateW)*(y/30))>1) { x = -(1-(1/rateI)*y/30)*rateW; check2 = 1; } else{ check2 = 1; } } else { if (Math.abs((1/rateW)*x+(1/rateW)*(y/30)) >1) { x = -rateW*(1+(1/rateW)*(y/30)); check2 = 1; } else { check2 = 1; } } } }
// constraints for forward decision
if (contracts >0) { if (z > 0) { if(contracts + z > Imax) { z = Imax -contracts; } } else { if (Math.abs(z)>Wmax) { z = -Wmax; } } } else { if (z>0) { if (z >Imax) { z = Imax; } } else { if (Math.abs(z+contracts)>Wmax) { z = -(Wmax-Math.abs(contracts));
Appendix: Code 100 } } } amount[0]= x; amount[1]= z; //amount[0]= x; //amount[1]= z; return amount; }
public static double Capacity(double Current, double loss,double s, double f) {
//Output: new capacity of storage facility
double X = 0; if (s>0 && f>0){
X = Current + loss*s + loss*f/30;
return X; } else if (s>0 && f<0){ X = Current + loss*s + f/30; return X; } else if (s==0 && f==0){ X = Current; return X; } else if (s<0 && f>0){ X = Current + s + loss*f/30; return X; } else { X = Current + s + f/30; return X; } }
public static double Pressure(double max, double min, double CapMax, double Cap) {
//Output: pressure within storage facility
//Inputs: max pressure, min pressure, max capacity, current capacity
double p = ((max-min)/CapMax)*Cap + min; return p;
}
public static double InjRate(double max, double min, double CapMax, double Cap) {
//Output: fuel loss rate for injection into storage facility //Inputs: max rate, min rate, max pressure, current pressure
double rate = -((max-min)/CapMax)*Cap + max; return rate;
}
public static double WithRate(double max, double min, double CapMax, double Cap) {
//Output: fuel loss rate for withdrawal from storage facility //Inputs: max rate, min rate, max pressure, current pressure
double rate = ((max-min)/CapMax)*Cap + min; return rate;
}
public static double Compression(double max, double pmax, double p) {
//Output: horsepower of compressor for injection
//Inputs: max horsepower of compressor, max pressure, current pressure
double rate = (max/pmax)*p; return rate;
}
public static double InjSpotCost(double compress, double rate, double amount, double spot) {
//Output: spot injection cost
//Inputs: compression, Injection rate, amount to be injected, spot price
double cost = Math.abs((compress*42.44*60)*((1/rate)*24*amount)
*spot/1000000);
Appendix: Code
101
}
public static double WithSpotCost(double amount) {
//Output: withdrawal cost
//Inputs: withdrawal rate, storage cost, current pressure
double cost = Math.abs(.01*amount*1000000); return cost; //in Bcf
}
public static double InjForCost(double compress, double rate, double amount, double forward) {
//Output: spot injection cost
//Inputs: compression, Injection rate, amount to be injected, spot price
double cost = Math.abs((compress*42.44*60)*((1/rate)*24*(amount/30))
*forward/1000000);
return cost; //in Bcf
}
public static double WithForCost(double amount) {
//Output: withdrawal cost
//Inputs: withdrawal rate, storage cost, current pressure
double cost = Math.abs(.01*(amount/30)*1000000); return cost; //in Bcf
}
public static double Reward(double spot, double forward, double Cost_I_Spot,double
Cost_W_Spot,double Cost_I_Forward,double Cost_W_Forward,double s,double f,double loss){
// Output: daily reward
double R = 0; if (s==0 && f==0){ R = 0; return R; } else if (s>=0 && f<=0){
R = -((loss*spot)*s*1000000 + Cost_I_Spot) +(forward)*Math.abs(f) *1000000 - Cost_W_Forward ;
return R;
}
else if(s>=0 && f>=0) {
R = -((loss*spot)*s*1000000 + Cost_I_Spot +(loss*forward)*f*1000000 + Cost_I_Forward) ;
return R;
}
else if (s<=0 && f>=0){
R = (spot)*Math.abs(s)*1000000 - Cost_W_Spot -((loss*forward) *f*1000000 + Cost_I_Forward) ;
return R;
}
else {
R = (spot)*Math.abs(s)*1000000 - Cost_W_Spot +(forward)*Math.abs(f) *1000000 - Cost_W_Forward ;
return R;
} }
public static double model(double Capacity, double upper, double lower){
// Output: produces Annual Value for Natural Gas String fname =
"\\C:\\Users\\Jennifer\\Documents\\Thesis\\PriceData.txt\\";
double[] prices_array = readExternalFile(fname); double [][] prices = new double [2][360];
prices = simulate(prices_array); FileOutputStream fout;
try
{
Appendix: Code
102
fout = new FileOutputStream
("\\C:\\Users\\Jennifer\\Documents\\Thesis\\Simulations.txt\\");
// Print a line of text
for(int i = 0; i < 360; i++){
new PrintStream(fout).print(prices[0][i] + "\t"); new PrintStream(fout).print(prices[1][i] + "\n");
}
// Close our output stream fout.close();
}
// Catches any error conditions
catch (IOException e)
{
System.err.println ("Unable to write to file"); System.exit(-1);
}
//establish parameters for model
double X_max = Capacity; //maximum level of working gas that can be put in
a storage facility
double rate_I_max = .3; //maximum injection rate for storage facility double rate_I_min = 0; //minimum injection rate for storage facility double rate_W_max = .5; //maximum withdrawal rate for storage facility double rate_W_min = 0; //minimum withdrawal rate for storage facility double p_max = .85; //maximum pressure within the storage facility double p_min = .2; //minimum pressure within the storage facility double compress_max = 25000; //max horsepower for compressor double loss_I = .985; //fuel rate loss for injection
double d = 3500; //depth of storage facility double gamma = 0; //risk free discount factor double history = 0; //for swing trading
//establish initial variables
double[] X = new double[361]; //initial capacity of storage facility double[][] x_t = new double[2][361];//initialization of spot decision double[] max_With = new double[361];//initialization of max Withdrawal
Capacity
double[] max_Inj = new double[361];//initialization of maximum Injection
Capacity
double[] rate_I = new double[361]; //injection rate for storage facility double[] rate_W = new double[361]; //withdrawal rate for storage facility double[] p = new double[361]; //initialization of pressure
double[] cost_I_spot = new double[361]; double[] cost_W_spot = new double[361]; double[] cost_I_forward = new double[361]; double[] cost_W_forward = new double[361];
double[] compress = new double[361];//horsepower of compressor
double[] reward = new double[361]; //initializing reward, daily revenue
//initialize variables
double Spot_t = 0;//initialization of spot price double Forward_t = 0;//initialization of forward price
double contracts = 0; //number of futures contracts to be fulfilled double price = 0; //price of contracts to be fulfilled
double forward_commitment = 0; //amount injected or withdrawn due to
forward contract commitments from prior month
double price_commitment = 0;
double Value = 0; //initializing yearly revenue int Count = 0; //initializing counter
Appendix: Code 103 //day 0 values X[0] = 0; x_t[0][0] = 0; x_t[1][0] = 0; max_With[0] = 0;
max_Inj[0]= X_max - max_With[0]; rate_I[0] = rate_I_max; rate_W[0] = rate_W_min; p[0] = p_min; cost_I_spot[0] = 0; cost_W_spot[0] = 0; cost_I_forward[0] = 0; cost_W_forward[0] = 0; compress[0] = 0; reward[0] = 0;
double[] spot = new double[360]; double[] forward = new double[360]; for (int i = 0; i<360; i++){
spot[i] = prices[0][i]; forward[i] = prices[1][i];
//System.out.print("spot = " + spot[i]+"\t"); //System.out.print("forward = "+forward[i]+ "\n"); }
//loop of year long operation of storage facility
double AnnualProfit = 0;
for (int i = 1; i <= 360; i++) {
//keeps track of where we are in months
if (Count == 31){ contracts = 0; Count = 1; } else { Count++; }
max_With[i] = X[i-1] + x_t[0][i-1]; max_Inj[i] = X_max - max_With[i];
X[i] = Capacity(X[i-1], loss_I, x_t[0][i-1],forward_commitment); p[i] = Pressure(p_max, p_min, X_max, X[i]);
rate_I[i] = InjRate(rate_I_max, rate_I_min, X_max, X[i]); rate_W[i] = WithRate(rate_W_max, rate_W_min, X_max, X[i]); compress[i]= Compression(compress_max, p_max, p[i]);
cost_I_spot[i] = InjSpotCost(compress[i], rate_I[i], x_t[0][i- 1],spot[i-1]);
cost_W_spot[i] = WithSpotCost(x_t[0][i-1]);
cost_I_forward[i] = InjForCost(compress[i], rate_I[i], forward_commitment,price_commitment);
cost_W_forward[i] = WithForCost(forward_commitment}; reward[i] = Reward(spot[i-1], price_commitment,
cost_I_spot[i],cost_W_spot[i], cost_I_forward[i],
cost_W_forward[i], x_t[0][i-1],forward_commitment,loss_I); Value = Value + reward[i];
if (i<4) {
history = 0;
for(int j =0;j<i;j++){
history = history + spot[j]; } history = history/(double)4; } else { history = spot[i-4]; }
double[] decisions = new double[2];
Appendix: Code 104 rate_W[i],max_Inj[i],max_With[i], forward_commitment, contracts, history,upper,lower); x_t[0][i] = decisions[0]; x_t[1][i] = decisions[1]; if (Count <30) { if(x_t[1][i] != 0) { if (contracts*x_t[1][i]<=0){ price_commitment = forward[i-1]; contracts = contracts + x_t[1][i]; } else { price_commitment = (price_commitment* Math.abs(contracts) + forward[i-1]* Math.abs(x_t[1][i]))/Math.abs(contrac ts+x_t[1][i]);
contracts = contracts + x_t[1][i]; } } } else { forward_commitment = contracts/30; }
AnnualProfit = AnnualProfit + reward[i]; }
FileOutputStream fout2;
try
{
// Open an output stream fout2 = new FileOutputStream
("\\C:\\Users\\Jennifer\\Documents\\Thesis\\ModelOutput.txt\\");
for (int i = 0; i < 360; i++){
// Print a line of text
new PrintStream(fout2).print(x_t[0][i]+"\t"); new PrintStream(fout2).print(x_t[1][i]+"\t"); //System.out.print(price_commitment+ "\t"); new PrintStream(fout2).print(X[i]+"\t"); new PrintStream(fout2).print(forward_commitment+ "\t"); new PrintStream(fout2).print(max_Inj[i]+"\t"); new PrintStream(fout2).print(max_With[i]+"\t"); new PrintStream(fout2).print(rate_I[i]+"\t"); new PrintStream(fout2).print(rate_W[i]+"\t"); new PrintStream(fout2).print(cost_I_spot[i]+"\t"); new PrintStream(fout2).print(cost_W_spot[i]+"\t"); new PrintStream(fout2).print(cost_I_forward[i]+"\t"); new PrintStream(fout2).print(cost_W_forward[i]+"\t"); new PrintStream(fout2).print(reward[i]+"\n"); } fout2.close(); }
// Catches any error conditions
catch (IOException e)
{
System.err.println ("Unable to write to file"); System.exit(-1);
Appendix: Code
105
}
//System.out.println("Annual Value = " + Value);
return AnnualProfit; } /* * * * @param args */
public static void main(String[] args) {
// TODO Auto-generated method stub //only needed for testing
double AnnualProfit = model(.01,.015, Capacity);
System.out.println(AnnualProfit); }
Bibliography
106
Bibliography
Barut, A. Emre, and W.B. Powell. Optimal Learning for Sequential Sampling with Non-
Parametric Estimation (Working Paper). 2010.
Brown, Robert G., and Richard F. Meyer. The Fundamental Theorem of Exponential
Smoothing. 1960.
Byers, Joe Wayne. "Commodity Storage Valuation: A Linear Optimization based on traded instruments." 2006.
CASTLE Laboratory. 1997-2009. http://www.castlelab.princeton.edu/ (accessed 1
2010, April).
CMEGroup. 2010. http://www.cmegroup.com/trading/energy/natural-gas/natural-
gas-last-day.html (accessed 04 02, 2010).
Dietert, Jeff A., and David A. Pursell. Underground Natural Gas Storage. Simmons & Company International, 2000.
Energy Information Administration. August 2004.
http://www.eia.doe.gov/oil_gas/info_glance/natural_gas.html (accessed November 3, 2009).
Enstor. "Gas Dailys: Historical Daily Spot Prices 1999-2009." 2010.
Bibliography
107
Eydeland, Alexander, and Krzysztof Wolyniec. Energy and Power Risk Management:
New Developements in Modeling, Pricing, and Hedging. Hoboken: John Wiley &
Sons, 2003.
Frazier, P., W.B. Powell, and S. Dayanik. A Knowledge Gradient Policy for Correlated
Normal Rewards. 2009.
—. A Knowledge Gradient Policy for Sequential Information Collection. 2008.
Gupta, S.S., and K.J. Miescke. Bayesian Look Ahead One-stage Sampling Allocations for
Selection of the Best Population. 1996.
Hardle, Wolfgang, Marlene Muller, Stefan Sperlich, and Axel Werwatz.
Nonparametric and Semiparametric Models. Berlin: Springer, 2004.
Lai, Guoming, Francois Margot, and Nicola Secomandi. An Approximate Dynamic
Programming Approach to Benchmark Practiced-based Heuristics for Natural Gas Storage Valuation. 2008.
Merrill, Arthur. Filtered Waves. New York: Analysis Press, 1977.
Mes, Martijn R.K., W.B. Powell, and P. Frazier. Hierarchical Knowledge Gradient for
Sequential Sampling. 2009.
Nadaraya, E.A. "On Estimating Regression." Theory of Probability and its
Applications, 1964.
Bibliography
108
NaturalGas.org. 2004. http://www.naturalgas.org/naturalgas/storage.asp#depleted
(accessed November 12, 2009).
Powell, W.B., and P. Frazier. ""Optimal Learning"." TutORials in Operations Research, 2008: 213-246.
Powell, Warren B., and Ilya Ryzhov. Optimal Learning. Hoboken: Wiley, 2010.
Puterman, M.L. Markov Decision Processes. Wiley, 1994.
U.S. Department of Energy. 2009.
http://www.energy.gov/energysources/naturalgas.htm (accessed November 22, 2009).