Skip to content

Instantly share code, notes, and snippets.

@jaybuidl
Last active January 11, 2019 17:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaybuidl/979cb5392fbc560f0807573b45e68d38 to your computer and use it in GitHub Desktop.
Save jaybuidl/979cb5392fbc560f0807573b45e68d38 to your computer and use it in GitHub Desktop.
Expected behaviour of order entry by units or amount, input validation and 2-ways conversion
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public
class OrderTest
{
private final BigDecimal LOT_SIZE = bd("0.1");
private final BigDecimal LEVERAGE = bd("100");
private final BigDecimal BID_PRICE = bd("99.9999"); // assuming tick size = 0.0001
private final BigDecimal ASK_PRICE = bd("100.0001");
private enum Side {BUY, SELL};
private BigDecimal bd(String s)
{
return new BigDecimal(s);
}
//******************************************************************************************************************
// Volume Input Validation
//******************************************************************************************************************
BigDecimal validateVolume(BigDecimal volume)
{
if (volume.compareTo(LOT_SIZE) < 0)
{
throw new IllegalArgumentException();
}
return volume.subtract(volume.remainder(LOT_SIZE));
}
@Test
public
void testValidateVolume()
{
// volume >= LOT_SIZE
assertThrows(IllegalArgumentException.class, () -> validateVolume(bd("-4.44")));
assertThrows(IllegalArgumentException.class, () -> validateVolume(bd("0")));
assertThrows(IllegalArgumentException.class, () -> validateVolume(bd("0.09")));
// volume must be a multiple of LOT_SIZE
// volume is rounded down
assertEquals(bd("0.10"), validateVolume(bd("0.10")));
assertEquals(bd("0.10"), validateVolume(bd("0.11")));
assertEquals(bd("0.10"), validateVolume(bd("0.15")));
assertEquals(bd("0.10"), validateVolume(bd("0.19")));
assertEquals(bd("0.20"), validateVolume(bd("0.20")));
}
//******************************************************************************************************************
// Volume Conversion to $ Amount
//******************************************************************************************************************
BigDecimal volumeToAmount(BigDecimal volume, Side side)
{
if (side == Side.BUY)
{
return validateVolume(volume).multiply(ASK_PRICE).multiply(LEVERAGE);
}
else if (side == Side.SELL)
{
return validateVolume(volume).multiply(BID_PRICE).multiply(LEVERAGE);
}
throw new IllegalArgumentException("illegal side value");
}
@Test
void testVolumeToAmountBuy()
{
var side = Side.BUY;
// volume > 0
assertThrows(IllegalArgumentException.class, () -> volumeToAmount(bd("-0.4"), side));
assertThrows(IllegalArgumentException.class, () -> volumeToAmount(bd("0"), side));
// conversion is rounded down
assertEquals(bd("1000.001000"), volumeToAmount(bd("0.10"), side));
assertEquals(bd("1000.001000"), volumeToAmount(bd("0.16"), side));
assertEquals(bd("11000.011000"), volumeToAmount(bd("1.10"), side));
assertEquals(bd("15000.015000"), volumeToAmount(bd("1.51"), side));
}
@Test
void testVolumeToAmountSell()
{
final var side = Side.SELL;
// volume > 0
assertThrows(IllegalArgumentException.class, () -> volumeToAmount(bd("-0.4"), side));
assertThrows(IllegalArgumentException.class, () -> volumeToAmount(bd("0"), side));
// conversion is rounded down
assertEquals(bd("999.999000"), volumeToAmount(bd("0.10"), side));
assertEquals(bd("999.999000"), volumeToAmount(bd("0.16"), side));
assertEquals(bd("10999.989000"), volumeToAmount(bd("1.10"), side));
assertEquals(bd("14999.985000"), volumeToAmount(bd("1.51"), side));
}
//******************************************************************************************************************
// $ Amount Input Validation
//******************************************************************************************************************
BigDecimal validateAmount(BigDecimal amount, Side side)
{
final BigDecimal lotSizeValue;
if (side == Side.BUY)
{
lotSizeValue = LOT_SIZE.multiply(ASK_PRICE).multiply(LEVERAGE);
}
else if (side == Side.SELL)
{
lotSizeValue = LOT_SIZE.multiply(BID_PRICE).multiply(LEVERAGE);
}
else
{
throw new IllegalArgumentException("illegal side value");
}
if (amount.compareTo(lotSizeValue) < 0)
{
throw new IllegalArgumentException();
}
return amount.subtract(amount.remainder(lotSizeValue));
}
@Test
void testValidateAmountBuy()
{
final var side = Side.BUY;
// amount > 0
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("-8.888"), side));
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("0"), side));
// less than 1 lot size value = $1000.001
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("1000"), side));
// must be a multiple of the "Lot Size Value" = LOT_SIZE * ASK_PRICE * LEVERAGE
// amount is rounded down
assertEquals(bd("1000.00100"), validateAmount(bd("1000.00100"), side));
assertEquals(bd("1000.00100"), validateAmount(bd("1000.00110"), side));
assertEquals(bd("1000.00100"), validateAmount(bd("1000.00200"), side));
assertEquals(bd("1000.00100"), validateAmount(bd("2000.00"), side));
assertEquals(bd("1000.00100"), validateAmount(bd("2000.00100"), side));
assertEquals(bd("2000.00200"), validateAmount(bd("2000.00200"), side));
assertEquals(bd("99000.09900"), validateAmount(bd("100000.00"), side));
assertEquals(bd("100000.100"), validateAmount(bd("100987.65"), side));
}
@Test
void testValidateAmountSell()
{
final var side = Side.SELL;
// amount > 0
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("-8.888"), side));
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("0"), side));
// less than 1 lot size value = $999.999
assertThrows(IllegalArgumentException.class, () -> validateAmount(bd("900"), side));
// must be a multiple of "Lot Size Value" = LOT_SIZE * ASK_PRICE * LEVERAGE
// amount is rounded down
assertEquals(bd("999.99900"), validateAmount(bd("999.999"), side));
assertEquals(bd("999.99900"), validateAmount(bd("1000.000"), side));
assertEquals(bd("999.99900"), validateAmount(bd("1000.001"), side));
assertEquals(bd("999.99900"), validateAmount(bd("1999.997"), side));
assertEquals(bd("1999.99800"), validateAmount(bd("1999.998"), side));
assertEquals(bd("98999.90100"), validateAmount(bd("99999.888"), side));
assertEquals(bd("99999.900"), validateAmount(bd("100999.898"), side));
}
//******************************************************************************************************************
// $ Amount Conversion to Volume
//******************************************************************************************************************
BigDecimal amountToVolume(BigDecimal amount, Side side)
{
if (side == Side.BUY)
{
return validateAmount(amount, side).divide(ASK_PRICE.multiply(LEVERAGE));
}
else if (side == Side.SELL)
{
return validateAmount(amount, side).divide(BID_PRICE.multiply(LEVERAGE));
}
else throw new IllegalArgumentException("illegal side value");
}
@Test
void testAmountToVolumeBuy()
{
var side = Side.BUY;
// amount > 0
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("-8.888"), side));
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("0"), side));
// less than 1 LOT_SIZE = 1000.001
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("1000"), side));
// conversion is rounded down
assertEquals(bd("0.1"), amountToVolume(bd("1000.001"), side));
assertEquals(bd("0.1"), amountToVolume(bd("1001.0000"), side));
assertEquals(bd("0.9"), amountToVolume(bd("10000.0000"), side));
}
@Test
void testAmountToVolumeSell()
{
var side = Side.SELL;
// amount > 0
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("-8.888"), side));
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("0"), side));
// less than 1 LOT_SIZE = 999.999
assertThrows(IllegalArgumentException.class, () -> amountToVolume(bd("900"), side));
// conversion is rounded down
assertEquals(bd("0.1"), amountToVolume(bd("999.999"), side));
assertEquals(bd("0.1"), amountToVolume(bd("1000"), side));
assertEquals(bd("0.9"), amountToVolume(bd("9000.0000"), side));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment