From c43b8f3291c145784ad9734a9814478406e31228 Mon Sep 17 00:00:00 2001 From: reiss Date: Wed, 8 Nov 2017 15:12:29 +0100 Subject: [PATCH] Trial implementation of a CrossInstrument class. --- .../instrument/CrossInstrument.java | 96 +++++++++++++++++++ .../jforex/programming/instrument/FxRate.java | 23 +++++ .../instrument/test/CrossInstrumentTest.java | 77 +++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 src/main/java/com/jforex/programming/instrument/CrossInstrument.java create mode 100644 src/main/java/com/jforex/programming/instrument/FxRate.java create mode 100644 src/test/java/com/jforex/programming/instrument/test/CrossInstrumentTest.java diff --git a/src/main/java/com/jforex/programming/instrument/CrossInstrument.java b/src/main/java/com/jforex/programming/instrument/CrossInstrument.java new file mode 100644 index 00000000..4b4210e8 --- /dev/null +++ b/src/main/java/com/jforex/programming/instrument/CrossInstrument.java @@ -0,0 +1,96 @@ +package com.jforex.programming.instrument; + +import static java.util.stream.Collectors.toSet; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import com.dukascopy.api.ICurrency; +import com.dukascopy.api.Instrument; +import com.google.common.collect.Sets; +import com.jforex.programming.currency.CurrencyFactory; +import com.jforex.programming.currency.CurrencyUtil; + +import io.reactivex.Maybe; + +public class CrossInstrument { + + private final Instrument firstInstrument; + private final Instrument secondInstrument; + private final Instrument instrument; + private final ICurrency crossCurrency; + private final boolean isEmpty; + private final boolean shouldDivide; + private final int pipScale; + + public CrossInstrument(final Instrument firstInstrument, + final Instrument secondInstrument) { + this.firstInstrument = firstInstrument; + this.secondInstrument = secondInstrument; + + final Maybe maybeCross = InstrumentFactory.maybeCross(firstInstrument, secondInstrument); + + instrument = maybeCross.blockingGet(); + isEmpty = maybeCross + .isEmpty() + .blockingGet(); + crossCurrency = calcCrossCurrency(); + shouldDivide = shouldDivide(); + pipScale = instrument.getPipScale() + 1; + } + + public boolean isValid() { + return !isEmpty; + } + + public Instrument get() { + return instrument; + } + + public ICurrency crossCurrency() { + return crossCurrency; + } + + public FxRate rate(final FxRate rateA, + final FxRate rateB) { + final BigDecimal bdcFirst = rateA.instrument().equals(firstInstrument) + ? BigDecimal.valueOf(rateA.value()) + : BigDecimal.valueOf(rateB.value()); + final BigDecimal bdcSecond = rateB.instrument().equals(secondInstrument) + ? BigDecimal.valueOf(rateB.value()) + : BigDecimal.valueOf(rateA.value()); + + final double crossValue = shouldDivide + ? bdcFirst.divide(bdcSecond, + pipScale, + RoundingMode.HALF_UP) + .doubleValue() + : bdcFirst + .multiply(bdcSecond) + .setScale(pipScale, RoundingMode.HALF_UP) + .doubleValue(); + + return new FxRate(crossValue, instrument); + } + + private ICurrency calcCrossCurrency() { + return CurrencyFactory + .fromInstruments(firstInstrument, secondInstrument) + .stream() + .filter(currency -> CurrencyUtil.isInAllInstruments(currency, + Sets.newHashSet(firstInstrument, secondInstrument))) + .collect(toSet()) + .iterator() + .next(); + } + + private boolean shouldDivide() { + final ICurrency baseFirstCurrency = firstInstrument.getPrimaryJFCurrency(); + final ICurrency baseSecondCurrency = secondInstrument.getPrimaryJFCurrency(); + final ICurrency quoteFirstCurrency = firstInstrument.getSecondaryJFCurrency(); + final ICurrency quoteSecondCurrency = secondInstrument.getSecondaryJFCurrency(); + + return baseFirstCurrency.equals(crossCurrency) && baseSecondCurrency.equals(crossCurrency) + || quoteFirstCurrency.equals(crossCurrency) && quoteSecondCurrency.equals(crossCurrency); + } +} diff --git a/src/main/java/com/jforex/programming/instrument/FxRate.java b/src/main/java/com/jforex/programming/instrument/FxRate.java new file mode 100644 index 00000000..c5c3c78d --- /dev/null +++ b/src/main/java/com/jforex/programming/instrument/FxRate.java @@ -0,0 +1,23 @@ +package com.jforex.programming.instrument; + +import com.dukascopy.api.Instrument; + +public class FxRate { + + private final double value; + private final Instrument instrument; + + public FxRate(final double value, + final Instrument instrument) { + this.value = value; + this.instrument = instrument; + } + + public double value() { + return value; + } + + public Instrument instrument() { + return instrument; + } +} diff --git a/src/test/java/com/jforex/programming/instrument/test/CrossInstrumentTest.java b/src/test/java/com/jforex/programming/instrument/test/CrossInstrumentTest.java new file mode 100644 index 00000000..de941320 --- /dev/null +++ b/src/test/java/com/jforex/programming/instrument/test/CrossInstrumentTest.java @@ -0,0 +1,77 @@ +package com.jforex.programming.instrument.test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import com.jforex.programming.instrument.CrossInstrument; +import com.jforex.programming.instrument.FxRate; +import com.jforex.programming.test.common.CurrencyUtilForTest; + +public class CrossInstrumentTest extends CurrencyUtilForTest { + + private CrossInstrument crossInstrumentA; + private CrossInstrument crossInstrumentB; + private CrossInstrument crossInstrumentC; + private CrossInstrument crossInstrumentD; + + @Before + public void setUp() { + crossInstrumentA = new CrossInstrument(instrumentEURUSD, instrumentGBPUSD); + crossInstrumentB = new CrossInstrument(instrumentEURGBP, instrumentGBPUSD); + crossInstrumentC = new CrossInstrument(instrumentEURJPY, instrumentGBPJPY); + crossInstrumentD = new CrossInstrument(instrumentEURGBP, instrumentGBPJPY); + } + + @Test + public void instanceIsValid() { + assertTrue(crossInstrumentA.isValid()); + assertTrue(crossInstrumentB.isValid()); + assertTrue(crossInstrumentC.isValid()); + assertTrue(crossInstrumentD.isValid()); + } + + @Test + public void instrumentIsCorrect() { + assertThat(crossInstrumentA.get(), equalTo(instrumentEURGBP)); + assertThat(crossInstrumentB.get(), equalTo(instrumentEURUSD)); + assertThat(crossInstrumentC.get(), equalTo(instrumentEURGBP)); + assertThat(crossInstrumentD.get(), equalTo(instrumentEURJPY)); + } + + @Test + public void crossCurrencyIsCorrect() { + assertThat(crossInstrumentA.crossCurrency(), equalTo(currencyUSD)); + assertThat(crossInstrumentB.crossCurrency(), equalTo(currencyGBP)); + assertThat(crossInstrumentC.crossCurrency(), equalTo(currencyJPY)); + assertThat(crossInstrumentD.crossCurrency(), equalTo(currencyGBP)); + } + + @Test + public void crossRateIsCorrect() { + FxRate rateA = new FxRate(1.15863, instrumentEURUSD); + FxRate rateB = new FxRate(1.31044, instrumentGBPUSD); + assertThat(crossInstrumentA.rate(rateA, rateB).value(), + equalTo(0.88415)); + assertThat(crossInstrumentA.rate(rateA, rateB).instrument(), + equalTo(instrumentEURGBP)); + + rateA = new FxRate(0.88415, instrumentEURGBP); + rateB = new FxRate(1.31044, instrumentGBPUSD); + assertThat(crossInstrumentB.rate(rateA, rateB).value(), + equalTo(1.15863)); + + rateA = new FxRate(131.452, instrumentEURJPY); + rateB = new FxRate(148.653, instrumentGBPJPY); + assertThat(crossInstrumentC.rate(rateA, rateB).value(), + equalTo(0.88429)); + + rateA = new FxRate(0.88429, instrumentEURGBP); + rateB = new FxRate(148.653, instrumentGBPJPY); + assertThat(crossInstrumentD.rate(rateA, rateB).value(), + equalTo(131.452)); + } +}