package org.marketcetera.trade;
import java.text.SimpleDateFormat;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.marketcetera.util.misc.ClassVersion;
/**
* Class to represent a currency instrument.
*
* leftCCY is the typical "traded" ccy ( in relation to the price )
* in EURUSD this is EUR while the price represents the number of USD per EUR
*
* while rightCCY is the currency in which PL is realized
*
* @author richard.obrien
*
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@ClassVersion("$Id: Currency.java 16901 2014-05-11 16:14:11Z colin $")
public class Currency extends Instrument implements Comparable<Currency>{
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* validation/set of permissible tenors
* OUTRIGHTS:
* TOD = Tomorrow (T+0)
* ON = Overnight (T+0)
* TN = Tomorrow Next (T+1)
* SP = Spot -- T+1 in CAD,PHP,RUB ... rest are T+2
* SN = Spot Next (Spot+1)
* nD = n days until settlement
* nW = n weeks until settlement
* nM = n months until settlement
* nY = n years until settlement
* nIMM = n'th nearest IMM date. an IMM date is the 3rd Wednesday
* of March, June, September and December months
*
*
*/
// when metc upgrades to newest guava, replace with ImmutableSet.of(...)
/*protected static Set<String> TENORSET = new HashSet<String>();
static{
String[] TENORARRAY =
{"TOD","ON","TN","SP","SN",
"1D","2D","3D","4D","5D","6D","7D","8D","9D","10D",
"1W","2W","3W",
"1M","2M","3M","4M","5M","6M","7M","8M","9M","10M","11M","12M",
"1Y","2Y","3Y",
"1IMM","2IMM","3IMM","4IMM"};
TENORSET.addAll(Arrays.asList(TENORARRAY));
}*/
/*public static Set<String> getTenorSet()
{
return Collections.unmodifiableSet(TENORSET);
}*/
protected static DateTimeFormatter fixDateFormat = DateTimeFormat.forPattern("yyyyMMdd");
/**
* aka the "base" currency, eg EUR in EURUSD
*
*/
protected final String leftCCY;
/**
* aka the "pl" or "variable" currency, eg JPY in USDJPY
*/
protected final String rightCCY;
/**
* store the tenor as a string
* trading systems often value generic representations, eg Spot
* alternatively if we stored it as a date object
* we'd have to actually calculate the settlement date when any of the
* generics are specified which requires a holiday calendar of many countries
*/
protected final String nearTenor;
protected final String farTenor;
/**
* leftCCY/rightCCY, final cached for minute performance gain in getSymbol()
*/
protected final String fixSymbol;
/**
* likewise cache a hashCode
*/
private final int hashCode;
/**
* currency pertaining to the order submitted
* eg, both the SIDE and QUANTITY refer to this currency
*/
protected String tradedCCY;
/**
* JAXB empty constructor
* JAXB cannot instantiate an object without empty constructor
*/
protected Currency()
{
//this("","","","");
leftCCY = null;
rightCCY = null;
nearTenor = null;
farTenor = null;
hashCode = -1;
fixSymbol = null;
}
public Currency(String symbol)
{
String[] currencies = symbol.split("/");
fixSymbol = symbol;
nearTenor=null;
farTenor=null;
if(currencies.length ==2)
{
leftCCY = currencies[0];
rightCCY = currencies[1];
tradedCCY = currencies[0];
hashCode = symbol.hashCode();
} else {
leftCCY=null;
rightCCY=null;
tradedCCY=null;
hashCode=-1;
}
}
/**
* outright trade constructor for generic delivery date
*
* @param baseCCY
* @param plCCY
* @param nearTenor
*
*/
public Currency(String baseCCY, String plCCY, String nearTenor){
this(baseCCY, plCCY, nearTenor, new String(""));
}
/**
* outright trade constructor for an exact delivery date
*
* @param baseCCY
* @param plCCY
* @param nearTenor
*/
public Currency(String baseCCY, String plCCY, LocalDate nearTenor){
this(baseCCY,plCCY,nearTenor,null);
}
/**
* swap trade constructor for generic near tenor, exact far tenor swap
* (eg typically for use in Spot vs IMM fwd)
*
* @param baseCCY
* @param plCCY
* @param nearTenor
*/
public Currency(String baseCCY, String plCCY, String nearTenor, LocalDate farTenor){
this(baseCCY, plCCY, nearTenor,
farTenor==null ? null : farTenor.toString(fixDateFormat));
}
/**
* swap trade constructor for exact swap dates
*
* @param baseCCY
* @param plCCY
* @param nearTenor
*/
public Currency(String baseCCY, String plCCY, LocalDate nearTenor, LocalDate farTenor){
this(baseCCY, plCCY, nearTenor.toString(fixDateFormat),
farTenor==null ? null : farTenor.toString(fixDateFormat));
}
/**
* outright trade constructor -OR- generic swap constructor
*
* @param leftCCY
* @param rightCCY
* @param nearTenor
* @param farTenor
* @param baseCCY
*
*/
public Currency(String leftCCY, String rightCCY, String nearTenor, String farTenor, String baseCCY){
leftCCY = StringUtils.trimToNull(leftCCY);
rightCCY = StringUtils.trimToNull(rightCCY);
nearTenor = StringUtils.trimToNull(nearTenor);
farTenor = StringUtils.trimToNull(farTenor);
Validate.notNull(leftCCY,Messages.MISSING_LEFT_CURRENCY.getText());
Validate.notNull(rightCCY,Messages.MISSING_RIGHT_CURRENCY.getText());
this.leftCCY = leftCCY;
this.rightCCY = rightCCY;
this.fixSymbol = this.leftCCY+"/"+this.rightCCY;
if(Currency.isValidTenor(nearTenor)){
this.nearTenor = nearTenor;
}else{
this.nearTenor = null;
}
if(Currency.isValidTenor(farTenor)){
this.farTenor = farTenor;
}else{
this.farTenor = null;
}
this.setTradedCCY(baseCCY);
// how to handle null nearTenor in this case?
String hashSymbol = this.fixSymbol+this.nearTenor+(this.farTenor==null?"":this.farTenor);
this.hashCode = hashSymbol.hashCode();
}
public Currency(String leftCCY, String rightCCY, String nearTenor, String farTenor){
this(leftCCY, rightCCY, nearTenor, farTenor, leftCCY);
}
/**
* utility method determining if this is a currency swap
* eg when the farTenor is null
*
* @return
*/
public boolean isSwap(){
return (farTenor!=null);
}
/**
* returns the "base" or left ccy of the currency pair
* @return
*/
public String getLeftCCY() {
return leftCCY;
}
/**
* returns the "pl" or right ccy of the currency pair
* @return
*/
public String getRightCCY() {
return rightCCY;
}
/**
* returns the delivery date for the currency outright trade
* or if it is a currency swap the near leg's delivery date
*
* @return
*/
public String getNearTenor(){
return nearTenor;
}
/**
* returns the far delivery date for the currency swap trade
* @return
*/
public String getFarTenor(){
return farTenor;
}
/**
* determines which currency OrderQty (and related FIX fields)
* relates to... FIX Tag 15
*
* that is if sending an order for symbol USD/JPY, the order can specify
* whether the desired qty traded is in JPY or USD
*
* @param ccy
*/
public void setTradedCCY(String ccy){
if(ccy.equals(leftCCY)){
this.tradedCCY=ccy;
}else{
if(ccy.equals(rightCCY)){
this.tradedCCY=ccy;
}
}
}
/**
* returns the currency specified in the qty fields of FIX orders
* for this Instrument
*
* @return
*/
public String getTradedCCY(){
return this.tradedCCY;
}
/**
* returns the METC API enum corresponding to tag167
*/
@Override
public SecurityType getSecurityType() {
return SecurityType.Currency; // SecurityType.Currency
}
/**
* returns the FIX tag55 value for this Instrument
*/
@Override
public String getSymbol() {
return this.fixSymbol;
}
/**
* simple String hashcode of symbol+tenors
*
*/
public int hashCode(){
return this.hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Currency other = (Currency) obj;
if (nearTenor == null) {
if (other.nearTenor != null)
return false;
} else if (!nearTenor.equals(other.nearTenor))
return false;
if (farTenor == null) {
if (other.farTenor != null)
return false;
} else if (!farTenor.equals(other.farTenor))
return false;
if (leftCCY == null) {
if (other.leftCCY != null)
return false;
} else if (!leftCCY.equals(other.leftCCY))
return false;
if (rightCCY == null) {
if (other.rightCCY != null)
return false;
} else if (!rightCCY.equals(other.rightCCY))
return false;
if (tradedCCY == null) {
if (other.tradedCCY != null)
return false;
} else if (!tradedCCY.equals(other.tradedCCY))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Currency [leftCCY=").append(leftCCY)
.append(", rightCCY=").append(rightCCY).append(", nearTenor=")
.append(nearTenor).append(", farTenor=").append(farTenor)
.append(", fixSymbol=").append(fixSymbol).append(", hashCode=")
.append(hashCode).append(", tradedCCY=").append(tradedCCY)
.append("]");
return builder.toString();
}
/**
* utility method to invert the ccypair and return a new instrument
* eg for Currency Pair EUR/USD, returns USD/EUR
*
* WARN: inverse MAINTAINS the tradedCCY field
*
* @return
*/
public Currency inverse(){
Currency inverse = new Currency(this.rightCCY,this.leftCCY,this.nearTenor,this.farTenor);
inverse.setTradedCCY(this.getTradedCCY());
return inverse;
}
/**
* utility static method to verify tenors
* @param tenor
* @return
*/
protected static boolean isValidTenor(String tenor){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
sdf.setLenient(false);
try {
sdf.parse(tenor);
return true;
} catch (Exception ignore) {
}
return false;
}
@Override
public int compareTo(Currency o2) {
Currency o1 = this;
if(o2==null){
//throw new NullPointerException("compareTo invalid for null objects in Currency.compareTo");
return 1; // prefer to say this object is always greater than a null?
}
int symbolComp = o1.getSymbol().compareTo(o2.getSymbol());
if(symbolComp==0){
int nearTenorComp = o1.getNearTenor().compareTo(o2.getNearTenor());
if(nearTenorComp==0){
if(o1.isSwap() && o2.isSwap()){
return o1.getFarTenor().compareTo(o2.getFarTenor());
}else{
return 0;
}
}else{
return nearTenorComp;
}
}else{
return symbolComp;
}
}
}