mStrategies = new ArrayList<>();
18 |
19 | public boolean add(TradingStrategy strategy) {
20 | return mStrategies.add(strategy);
21 | }
22 |
23 | public int size() {
24 | return mStrategies.size();
25 | }
26 |
27 | @Override public void onStart(TradingContext context) {
28 | mStrategies.forEach(t -> t.onStart(context));
29 | }
30 |
31 | @Override public void onTick() {
32 | mStrategies.forEach(TradingStrategy::onTick);
33 | }
34 |
35 | @Override public void onEnd() {
36 | mStrategies.forEach(TradingStrategy::onEnd);
37 | }
38 |
39 | @Override public String toString() {
40 | return "MultipleStrategy{" +
41 | "mStrategies=" + mStrategies +
42 | '}';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/org/lst/trading/main/strategy/kalman/Cointegration.java:
--------------------------------------------------------------------------------
1 | package org.lst.trading.main.strategy.kalman;
2 |
3 | import org.la4j.Matrix;
4 |
5 | public class Cointegration {
6 | double mDelta;
7 | double mR;
8 | KalmanFilter mFilter;
9 | int mNobs = 2;
10 |
11 | public Cointegration(double delta, double r) {
12 | mDelta = delta;
13 | mR = r;
14 |
15 | Matrix vw = Matrix.identity(mNobs).multiply(mDelta / (1 - delta));
16 | Matrix a = Matrix.identity(mNobs);
17 |
18 | Matrix x = Matrix.zero(mNobs, 1);
19 |
20 | mFilter = new KalmanFilter(mNobs, 1);
21 | mFilter.setUpdateMatrix(a);
22 | mFilter.setState(x);
23 | mFilter.setStateCovariance(Matrix.zero(mNobs, mNobs));
24 | mFilter.setUpdateCovariance(vw);
25 | mFilter.setMeasurementCovariance(Matrix.constant(1, 1, r));
26 | }
27 |
28 | public void step(double x, double y) {
29 | mFilter.setExtractionMatrix(Matrix.from1DArray(1, 2, new double[]{1, x}));
30 | mFilter.step(Matrix.constant(1, 1, y));
31 | }
32 |
33 | public double getAlpha() {
34 | return mFilter.getState().getRow(0).get(0);
35 | }
36 |
37 | public double getBeta() {
38 | return mFilter.getState().getRow(1).get(0);
39 | }
40 |
41 | public double getVariance() {
42 | return mFilter.getInnovationCovariance().get(0, 0);
43 | }
44 |
45 | public double getError() {
46 | return mFilter.getInnovation().get(0, 0);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/lst/trading/main/strategy/kalman/CointegrationTradingStrategy.java:
--------------------------------------------------------------------------------
1 | package org.lst.trading.main.strategy.kalman;
2 |
3 | import org.apache.commons.math3.stat.StatUtils;
4 | import org.lst.trading.lib.model.Order;
5 | import org.lst.trading.lib.model.TradingContext;
6 | import org.lst.trading.lib.series.DoubleSeries;
7 | import org.lst.trading.lib.series.MultipleDoubleSeries;
8 | import org.lst.trading.lib.series.TimeSeries;
9 | import org.lst.trading.lib.util.Util;
10 | import org.lst.trading.main.strategy.AbstractTradingStrategy;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | public class CointegrationTradingStrategy extends AbstractTradingStrategy {
15 | private static Logger log = LoggerFactory.getLogger(CointegrationTradingStrategy.class);
16 |
17 | boolean mReinvest = false;
18 |
19 | String mX, mY;
20 | TradingContext mContext;
21 | Cointegration mCoint;
22 |
23 | DoubleSeries mAlpha;
24 | DoubleSeries mBeta;
25 | DoubleSeries mXs;
26 | DoubleSeries mYs;
27 | DoubleSeries mError;
28 | DoubleSeries mVariance;
29 | DoubleSeries mModel;
30 |
31 | Order mXOrder;
32 | Order mYOrder;
33 |
34 | public CointegrationTradingStrategy(String x, String y) {
35 | this(1, x, y);
36 | }
37 |
38 | public CointegrationTradingStrategy(double weight, String x, String y) {
39 | setWeight(weight);
40 | mX = x;
41 | mY = y;
42 | }
43 |
44 | @Override public void onStart(TradingContext context) {
45 | mContext = context;
46 | mCoint = new Cointegration(1e-4, 1e-3);
47 | mAlpha = new DoubleSeries("alpha");
48 | mBeta = new DoubleSeries("beta");
49 | mXs = new DoubleSeries("x");
50 | mYs = new DoubleSeries("y");
51 | mError = new DoubleSeries("error");
52 | mVariance = new DoubleSeries("variance");
53 | mModel = new DoubleSeries("model");
54 | }
55 |
56 | @Override public void onTick() {
57 | double x = mContext.getLastPrice(mX);
58 | double y = mContext.getLastPrice(mY);
59 | double alpha = mCoint.getAlpha();
60 | double beta = mCoint.getBeta();
61 |
62 | mCoint.step(x, y);
63 | mAlpha.add(alpha, mContext.getTime());
64 | mBeta.add(beta, mContext.getTime());
65 | mXs.add(x, mContext.getTime());
66 | mYs.add(y, mContext.getTime());
67 | mError.add(mCoint.getError(), mContext.getTime());
68 | mVariance.add(mCoint.getVariance(), mContext.getTime());
69 |
70 | double error = mCoint.getError();
71 |
72 | mModel.add(beta * x + alpha, mContext.getTime());
73 |
74 | if (mError.size() > 30) {
75 | double[] lastValues = mError.reversedStream().mapToDouble(TimeSeries.Entry::getItem).limit(15).toArray();
76 | double sd = Math.sqrt(StatUtils.variance(lastValues));
77 |
78 | if (mYOrder == null && Math.abs(error) > sd) {
79 | double value = mReinvest ? mContext.getNetValue() : mContext.getInitialFunds();
80 | double baseAmount = (value * getWeight() * 0.5 * Math.min(4, mContext.getLeverage())) / (y + beta * x);
81 |
82 | if (beta > 0 && baseAmount * beta >= 1) {
83 | mYOrder = mContext.order(mY, error < 0, (int) baseAmount);
84 | mXOrder = mContext.order(mX, error > 0, (int) (baseAmount * beta));
85 | }
86 | //log.debug("Order: baseAmount={}, residual={}, sd={}, beta={}", baseAmount, residual, sd, beta);
87 | } else if (mYOrder != null) {
88 | if (mYOrder.isLong() && error > 0 || !mYOrder.isLong() && error < 0) {
89 | mContext.close(mYOrder);
90 | mContext.close(mXOrder);
91 |
92 | mYOrder = null;
93 | mXOrder = null;
94 | }
95 | }
96 | }
97 | }
98 |
99 | @Override public void onEnd() {
100 | log.debug("Kalman filter statistics: " + Util.writeCsv(new MultipleDoubleSeries(mXs, mYs, mAlpha, mBeta, mError, mVariance, mModel)));
101 | }
102 |
103 | @Override public String toString() {
104 | return "CointegrationStrategy{" +
105 | "mY='" + mY + '\'' +
106 | ", mX='" + mX + '\'' +
107 | '}';
108 | }
109 |
110 | public DoubleSeries getAlpha() {
111 | return mAlpha;
112 | }
113 |
114 | public DoubleSeries getBeta() {
115 | return mBeta;
116 | }
117 |
118 | public DoubleSeries getXs() {
119 | return mXs;
120 | }
121 |
122 | public DoubleSeries getYs() {
123 | return mYs;
124 | }
125 |
126 | public DoubleSeries getError() {
127 | return mError;
128 | }
129 |
130 | public DoubleSeries getVariance() {
131 | return mVariance;
132 | }
133 |
134 | public DoubleSeries getModel() {
135 | return mModel;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/org/lst/trading/main/strategy/kalman/KalmanFilter.java:
--------------------------------------------------------------------------------
1 | package org.lst.trading.main.strategy.kalman;
2 |
3 |
4 | import org.la4j.LinearAlgebra;
5 | import org.la4j.Matrix;
6 | import org.la4j.matrix.DenseMatrix;
7 |
8 | /**
9 | * n: Number of states
10 | * m: Number of sensors
11 | *
12 | * https://www.udacity.com/wiki/cs373/kalman-filter-matrices
13 | *
14 | * Steps:
15 | *
16 | * PREDICT X'
17 | *
18 | * First, we predict our next x:
19 | *
20 | * x' = Fx + u
21 | *
22 | * UPDATE P'
23 | *
24 | * We also update the covariance matrix according to the next prediction:
25 | *
26 | * P' = FP(transp F)
27 | *
28 | * UPDATE Y
29 | *
30 | * y becomes the difference between the move and what we expected:
31 | *
32 | * y = z - Hx
33 | *
34 | * UPDATE S
35 | *
36 | * S is the covariance of the move, adding up the covariance in move space of the position and the covariance of the measurement:
37 | *
38 | * S = HP(transp H) + R
39 | *
40 | * CALCULATE K
41 | *
42 | * Now I start to wave my hands. I assume this next matrix is called K because this is the work of the Kalman filter. Whatever is happening here, it doesn't depend on u or z. We're computing how much of the difference between the observed move and the expected move to add to x.
43 | *
44 | * K = P (transp H) (inv S)
45 | *
46 | * UPDATE X'
47 | *
48 | * We update x:
49 | *
50 | * x' = x + Ky
51 | *
52 | * SUBTRACT P
53 | *
54 | * And we subtract some uncertainty from P, again not depending on u or z:
55 | *
56 | * P' = P - P(transp H)(inv S)HP
57 | */
58 | public class KalmanFilter {
59 | private final int mStateCount; // n
60 | private final int mSensorCount; // m
61 | // state
62 |
63 | /**
64 | * stateCount x 1
65 | */
66 | private Matrix mState; // x, state estimate
67 |
68 | /**
69 | * stateCount x stateCount
70 | *
71 | * Symmetric.
72 | * Down the diagonal of P, we find the variances of the elements of x.
73 | * On the off diagonals, at P[i][j], we find the covariances of x[i] with x[j].
74 | */
75 | private Matrix mStateCovariance; // Covariance matrix of x, process noise (w)
76 |
77 | // predict
78 |
79 | /**
80 | * stateCount x stateCount
81 | *
82 | * Kalman filters model a system over time.
83 | * After each tick of time, we predict what the values of x are, and then we measure and do some computation.
84 | * F is used in the update step. Here's how it works: For each value in x, we write an equation to update that value,
85 | * a linear equation in all the variables in x. Then we can just read off the coefficients to make the matrix.
86 | */
87 | private Matrix mUpdateMatrix; // F, State transition matrix.
88 |
89 | /**
90 | * stateCount x stateCount
91 | *
92 | * Error in the process, after each update this uncertainty is added.
93 | */
94 | private Matrix mUpdateCovariance; // Q, Estimated error in process.
95 |
96 | /**
97 | * stateCount x 1
98 | *
99 | * The control input, the move vector.
100 | * It's the change to x that we cause, or that we know is happening.
101 | * Since we add it to x, it has dimension n. When the filter updates, it adds u to the new x.
102 | *
103 | * External moves to the system.
104 | */
105 | private Matrix mMoveVector; // u, Control vector
106 |
107 |
108 | // measurement
109 |
110 | /**
111 | * sensorCount x 1
112 | *
113 | * z: Measurement Vector, It's the outputs from the sensors.
114 | */
115 | private Matrix mMeasurement;
116 |
117 | /**
118 | * sensorCount x sensorCount
119 | *
120 | * R, the variances and covariances of our sensor measurements.
121 | *
122 | * The Kalman filter algorithm does not change R, because the process can't change our belief about the
123 | * accuracy of our sensors--that's a property of the sensors themselves.
124 | * We know the variance of our sensor either by testing it, or by reading the documentation that came with it,
125 | * or something like that. Note that the covariances here are the covariances of the measurement error.
126 | * A positive number means that if the first sensor is erroneously low, the second tends to be erroneously low,
127 | * or if the first reads high, the second tends to read high; it doesn't mean that if the first sensor reports a
128 | * high number the second will also report a high number
129 | */
130 | private Matrix mMeasurementCovariance; // R, Covariance matrix of the measurement vector z
131 |
132 | /**
133 | * sensorCount x stateCount
134 | *
135 | * The matrix H tells us what sensor readings we'd get if x were the true state of affairs and our sensors were perfect.
136 | * It's the matrix we use to extract the measurement from the data.
137 | * If we multiply H times a perfectly correct x, we get a perfectly correct z.
138 | */
139 | private Matrix mExtractionMatrix; // H, Observation matrix.
140 |
141 | // no inputs
142 | private Matrix mInnovation;
143 | private Matrix mInnovationCovariance;
144 |
145 | public KalmanFilter(int stateCount, int sensorCount) {
146 | mStateCount = stateCount;
147 | mSensorCount = sensorCount;
148 | mMoveVector = Matrix.zero(stateCount, 1);
149 | }
150 |
151 | private void step() {
152 | // prediction
153 | Matrix predictedState = mUpdateMatrix.multiply(mState).add(mMoveVector);
154 | Matrix predictedStateCovariance = mUpdateMatrix.multiply(mStateCovariance).multiply(mUpdateMatrix.transpose()).add(mUpdateCovariance);
155 |
156 | // observation
157 | mInnovation = mMeasurement.subtract(mExtractionMatrix.multiply(predictedState));
158 | mInnovationCovariance = mExtractionMatrix.multiply(predictedStateCovariance).multiply(mExtractionMatrix.transpose()).add(mMeasurementCovariance);
159 |
160 | // update
161 | Matrix kalmanGain = predictedStateCovariance.multiply(mExtractionMatrix.transpose()).multiply(mInnovationCovariance.withInverter(LinearAlgebra.InverterFactory.SMART).inverse());
162 | mState = predictedState.add(kalmanGain.multiply(mInnovation));
163 |
164 | int nRow = mStateCovariance.rows();
165 | mStateCovariance = DenseMatrix.identity(nRow).subtract(kalmanGain.multiply(mExtractionMatrix)).multiply(predictedStateCovariance);
166 | }
167 |
168 | public void step(Matrix measurement, Matrix move) {
169 | mMeasurement = measurement;
170 | mMoveVector = move;
171 | step();
172 | }
173 |
174 | public void step(Matrix measurement) {
175 | mMeasurement = measurement;
176 | step();
177 | }
178 |
179 |
180 | public Matrix getState() {
181 | return mState;
182 | }
183 |
184 | public Matrix getStateCovariance() {
185 | return mStateCovariance;
186 | }
187 |
188 | public Matrix getInnovation() {
189 | return mInnovation;
190 | }
191 |
192 | public Matrix getInnovationCovariance() {
193 | return mInnovationCovariance;
194 | }
195 |
196 | public void setState(Matrix state) {
197 | mState = state;
198 | }
199 |
200 | public void setStateCovariance(Matrix stateCovariance) {
201 | mStateCovariance = stateCovariance;
202 | }
203 |
204 | public void setUpdateMatrix(Matrix updateMatrix) {
205 | mUpdateMatrix = updateMatrix;
206 | }
207 |
208 | public void setUpdateCovariance(Matrix updateCovariance) {
209 | mUpdateCovariance = updateCovariance;
210 | }
211 |
212 | public void setMeasurementCovariance(Matrix measurementCovariance) {
213 | mMeasurementCovariance = measurementCovariance;
214 | }
215 |
216 | public void setExtractionMatrix(Matrix h) {
217 | this.mExtractionMatrix = h;
218 | }
219 |
220 | public Matrix getUpdateMatrix() {
221 | return mUpdateMatrix;
222 | }
223 |
224 | public Matrix getUpdateCovariance() {
225 | return mUpdateCovariance;
226 | }
227 |
228 | public Matrix getMeasurementCovariance() {
229 | return mMeasurementCovariance;
230 | }
231 |
232 | public Matrix getExtractionMatrix() {
233 | return mExtractionMatrix;
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/test/java/io/codera/quant/config/GuiceJUnit4Runner.java:
--------------------------------------------------------------------------------
1 | package io.codera.quant.config;
2 |
3 | import com.google.inject.Guice;
4 | import org.junit.runners.BlockJUnit4ClassRunner;
5 | import org.junit.runners.model.InitializationError;
6 |
7 | public class GuiceJUnit4Runner extends BlockJUnit4ClassRunner {
8 |
9 | public GuiceJUnit4Runner(Class> klass) throws InitializationError {
10 | super(klass);
11 | }
12 |
13 | @Override
14 | public Object createTest() throws Exception {
15 | Object object = super.createTest();
16 | Guice.createInjector(new TestConfig()).injectMembers(object);
17 | return object;
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/test/java/io/codera/quant/config/TestConfig.java:
--------------------------------------------------------------------------------
1 | package io.codera.quant.config;
2 |
3 | import com.google.inject.AbstractModule;
4 | import com.google.inject.Provides;
5 |
6 | import com.ib.client.OrderType;
7 | import com.ib.controller.ApiController;
8 | import io.codera.quant.context.IbTradingContext;
9 | import io.codera.quant.context.TradingContext;
10 | import io.codera.quant.strategy.IbPerMinuteStrategyRunner;
11 | import io.codera.quant.strategy.StrategyRunner;
12 | import java.sql.SQLException;
13 |
14 | /**
15 | * Test configuration
16 | */
17 | public class TestConfig extends AbstractModule {
18 |
19 | private static final String HOST = "localhost";
20 | private static final int PORT = 7497;
21 |
22 | @Override
23 | protected void configure() {
24 | bind(StrategyRunner.class).to(IbPerMinuteStrategyRunner.class);
25 | }
26 |
27 | @Provides
28 | ApiController apiController() {
29 | ApiController controller =
30 | new ApiController(new IbConnectionHandler(), valueOf -> {
31 | }, valueOf -> {});
32 | controller.connect(HOST, PORT, 0, null);
33 | return controller;
34 | }
35 |
36 | @Provides
37 | TradingContext tradingContext(ApiController controller) throws SQLException, ClassNotFoundException {
38 | return new IbTradingContext(
39 | controller,
40 | new ContractBuilder(),
41 | OrderType.MKT,
42 | 2
43 | );
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/io/codera/quant/strategy/meanrevertion/ZScoreTest.java:
--------------------------------------------------------------------------------
1 | package io.codera.quant.strategy.meanrevertion;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.google.inject.Inject;
5 |
6 | import io.codera.quant.config.GuiceJUnit4Runner;
7 | import io.codera.quant.util.MathUtil;
8 | import io.codera.quant.context.TradingContext;
9 | import java.io.IOException;
10 | import java.net.URISyntaxException;
11 | import java.util.List;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.lst.trading.lib.series.DoubleSeries;
15 | import org.lst.trading.lib.util.yahoo.YahooFinance;
16 |
17 | import static org.junit.Assert.assertEquals;
18 |
19 |
20 | /**
21 | * Tests for {@link ZScore}
22 | */
23 | @RunWith(GuiceJUnit4Runner.class)
24 | public class ZScoreTest {
25 | @Inject
26 | private TradingContext tradingContext;
27 | private static final List SYMBOLS = ImmutableList.of("GLD", "USO");
28 | private static final int LOOKBACK = 20;
29 | private static final int MINUTES_OF_HISTORY = LOOKBACK * 2 - 1;
30 |
31 | // This test requires a working IB TWS.
32 | // Test returns inconsistent result, due to probably a bug in historical data retrieval timing
33 | @Test
34 | public void getTest() throws Exception {
35 | for(String symbol : SYMBOLS) {
36 | tradingContext.addContract(symbol);
37 | }
38 | DoubleSeries firstSymbolHistory =
39 | tradingContext.getHistoryInMinutes(SYMBOLS.get(0), MINUTES_OF_HISTORY);
40 | DoubleSeries secondSymbolHistory =
41 | tradingContext.getHistoryInMinutes(SYMBOLS.get(1), MINUTES_OF_HISTORY);
42 |
43 | ZScore zScore =
44 | new ZScore(firstSymbolHistory.toArray(), secondSymbolHistory.toArray(), LOOKBACK, new
45 | MathUtil());
46 | System.out.println(zScore.get(114.7, 10.30));
47 | }
48 |
49 | @Test
50 | public void getTestUnit() throws IOException, URISyntaxException {
51 | YahooFinance finance = new YahooFinance();
52 | DoubleSeries gld =
53 | finance.readCsvToDoubleSeriesFromResource("GLD.csv", SYMBOLS.get(0));
54 |
55 | DoubleSeries uso =
56 | finance.readCsvToDoubleSeriesFromResource("USO.csv", SYMBOLS.get(1));
57 | ZScore zScore = new ZScore(gld.toArray(), uso.toArray(), LOOKBACK, new MathUtil());
58 | assertEquals("Failed", -1.0102216127916113, zScore.get(58.33, 66.35), 0);
59 | assertEquals("Failed", -0.9692409006953596, zScore.get(57.73, 67), 0);
60 | assertEquals("Failed", -0.9618287583543594, zScore.get(57.99, 66.89), 0);
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/test/resources/GLD.csv:
--------------------------------------------------------------------------------
1 | Date, Adj Close,,,,,
2 | 2012-03-26,63.65,,,,,
3 | 2012-03-27,62.96,,,,,
4 | 2012-03-28,65.09,,,,,
5 | 2012-03-29,65.16,,,,,
6 | 2012-03-30,66.55,,,,,
7 | 2012-03-31,66.46,,,,,
8 | 2012-04-01,67.48,,,,,
9 | 2012-04-02,67.99,,,,,
10 | 2012-04-03,67.56,,,,,
11 | 2012-04-04,69.68,,,,,
12 | 2012-04-05,70.38,,,,,
13 | 2012-04-06,71.03,,,,,
14 | 2012-04-07,71.12,,,,,
15 | 2012-04-08,67.41,,,,,
16 | 2012-04-09,68.61,,,,,
17 | 2012-04-10,68.15,,,,,
18 | 2012-04-11,67.46,,,,,
19 | 2012-04-12,65.58,,,,,
20 | 2012-04-13,65.3,,,,,
21 | 2012-04-14,66.38,,,,,
22 | 2012-04-15,64.06,,,,,
23 | 2012-04-16,64.7,,,,,
24 | 2012-04-17,65.1,,,,,
25 | 2012-04-18,65.11,,,,,
26 | 2012-04-19,64.23,,,,,
27 | 2012-04-20,62.56,,,,,
28 | 2012-04-21,63.5,,,,,
29 | 2012-04-22,63.29,,,,,
30 | 2012-04-23,62.55,,,,,
31 | 2012-04-24,62.28,,,,,
32 | 2012-04-25,60.91,,,,,
33 | 2012-04-26,60.45,,,,,
34 | 2012-04-27,60.03,,,,,
35 | 2012-04-28,55.92,,,,,
36 | 2012-04-29,55.62,,,,,
37 | 2012-04-30,57.32,,,,,
38 | 2012-05-01,57.68,,,,,
39 | 2012-05-02,56.36,,,,,
40 | 2012-05-03,57.3,,,,,
--------------------------------------------------------------------------------
/src/test/resources/USO.csv:
--------------------------------------------------------------------------------
1 | Date, Adj Close,,,,,
2 | 2012-03-26,69.54,,,,,
3 | 2012-03-27,68.7,,,,,
4 | 2012-03-28,69.62,,,,,
5 | 2012-03-29,71.5,,,,,
6 | 2012-03-30,72.34,,,,,
7 | 2012-03-31,70.22,,,,,
8 | 2012-04-01,68.32,,,,,
9 | 2012-04-02,68,,,,,
10 | 2012-04-03,67.88,,,,,
11 | 2012-04-04,68.4,,,,,
12 | 2012-04-05,69.77,,,,,
13 | 2012-04-06,70.3,,,,,
14 | 2012-04-07,69.11,,,,,
15 | 2012-04-08,66.63,,,,,
16 | 2012-04-09,66.7,,,,,
17 | 2012-04-10,65.7,,,,,
18 | 2012-04-11,66.74,,,,,
19 | 2012-04-12,65.57,,,,,
20 | 2012-04-13,66.28,,,,,
21 | 2012-04-14,67.7700000000000,,,,,
22 | 2012-04-15,66.1000000000000,,,,,
23 | 2012-04-16,67.8000000000000,,,,,
24 | 2012-04-17,67.7500000000000,,,,,
25 | 2012-04-18,68.3200000000000,,,,,
26 | 2012-04-19,67.7300000000000,,,,,
27 | 2012-04-20,66.8200000000000,,,,,
28 | 2012-04-21,68.9000000000000,,,,,
29 | 2012-04-22,68.7400000000000,,,,,
30 | 2012-04-23,68.7600000000000,,,,,
31 | 2012-04-24,67.2800000000000,,,,,
32 | 2012-04-25,67.1200000000000,,,,,
33 | 2012-04-26,68.0700000000000,,,,,
34 | 2012-04-27,67.0800000000000,,,,,
35 | 2012-04-28,65.1800000000000,,,,,
36 | 2012-04-29,65.7900000000000,,,,,
37 | 2012-04-30,66.1600000000000,,,,,
38 | 2012-05-01,66.2500000000000,,,,,
39 | 2012-05-02,65.5400000000000,,,,,
40 | 2012-05-03,65.2900000000000,,,,,
--------------------------------------------------------------------------------