/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.livedata.normalization;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.MutableFudgeMsg;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.livedata.server.FieldHistoryStore;
/**
* Calculates a best estimate of the current value of a security.
*/
public class MarketValueCalculator implements NormalizationRule {
private static final double TOLERANCE = 0.00001;
/**
* The maximum relative bid/ask spread to use their midpoint.
*/
private static final double MAX_ACCEPTABLE_SPREAD_TO_USE_MIDPOINT = 0.20;
/**
* Calculates a best estimate of the current value of a security.
* <p>
* This value is normally the midpoint of BID and ASK if both BID and ASK are available.
* If the spread between BID and ASK is very large (more than 5 percent),
* LAST is used in preference of the midpoint, if available and if
* it is within the BID/ASK boundaries. Otherwise, the midpoint
* is used anyway.
* <p>
* If no BID and ASK are available, LAST is used.
*
* @param msg the message to normalize, not null
* @param securityUniqueId the data provider's unique ID of the security, not null
* @param fieldHistory the distributor-specific field history which the rule may choose to update, not null
* @return {@code msg} with {@link MarketDataRequirementNames#MARKET_VALUE} added,
* with the value calculated as described above
*/
@Override
public MutableFudgeMsg apply(MutableFudgeMsg msg, String securityUniqueId, FieldHistoryStore fieldHistory) {
// TODO: Review behaviours under different market providers.
// Tickers that are not actually assets, like indices, provide an important case.
FudgeMsg lkv = fieldHistory.getLastKnownValues();
Double bid = msg.getDouble(MarketDataRequirementNames.BID);
if (bid == null) {
bid = lkv.getDouble(MarketDataRequirementNames.BID);
}
Double ask = msg.getDouble(MarketDataRequirementNames.ASK);
if (ask == null) {
ask = lkv.getDouble(MarketDataRequirementNames.ASK);
}
// If we have seen bid & ask in the past, use bid & ask midpoint.
if (bid != null && ask != null) {
// Too big of a spread for midpoint to be meaningful?
if (Math.abs(bid) > TOLERANCE && (getRelativeSpread(bid, ask) > MAX_ACCEPTABLE_SPREAD_TO_USE_MIDPOINT)) {
// Try to resort to last, though if this fails use midpoint anyway.
Double last = msg.getDouble(MarketDataRequirementNames.LAST);
if (last != null) {
// Ok, last was found. But let's make sure that it's within the bid/ask boundaries.
if (last < bid) {
msg.add(MarketDataRequirementNames.MARKET_VALUE, bid);
} else if (last > ask) {
msg.add(MarketDataRequirementNames.MARKET_VALUE, ask);
} else {
msg.add(MarketDataRequirementNames.MARKET_VALUE, last);
}
return msg;
}
}
double marketValue = (bid + ask) / 2.0;
msg.add(MarketDataRequirementNames.MARKET_VALUE, marketValue);
return msg;
}
// Use a "MID" if we've been given one (should this take priority before the BID/ASK sum?) // TODO: Review - Consider Indices in Activ...
Double mid = msg.getDouble(MarketDataRequirementNames.MID);
if (mid != null) {
msg.add(MarketDataRequirementNames.MARKET_VALUE, mid);
return msg;
}
// Use "LAST" if we've been given one
Double last = msg.getDouble(MarketDataRequirementNames.LAST);
if (last != null) {
msg.add(MarketDataRequirementNames.MARKET_VALUE, last);
return msg;
}
// Use "CLOSE" if we've been given one
Double close = msg.getDouble(MarketDataRequirementNames.CLOSE);
if (close != null) {
msg.add(MarketDataRequirementNames.MARKET_VALUE, close);
return msg;
}
// Fall back to last known market value
return lastKnownMarketValue(msg, fieldHistory);
}
private double getRelativeSpread(Double bid, Double ask) {
return Math.abs(ask - bid) / Math.abs(ask);
}
/**
* Tries to populate MARKET_VALUE from the history.
*/
private MutableFudgeMsg lastKnownMarketValue(
MutableFudgeMsg msg,
FieldHistoryStore fieldHistory) {
FudgeMsg lkv = fieldHistory.getLastKnownValues();
Double lastKnownMarketValue = lkv.getDouble(MarketDataRequirementNames.MARKET_VALUE);
if (lastKnownMarketValue == null) {
return msg;
}
msg.add(MarketDataRequirementNames.MARKET_VALUE, lastKnownMarketValue);
return msg;
}
}