package org.cryptocoinpartners.module; import java.util.Random; import javax.inject.Inject; import org.apache.commons.configuration.Configuration; import org.cryptocoinpartners.schema.Exchange; import org.cryptocoinpartners.schema.Market; import org.cryptocoinpartners.schema.Trade; import org.cryptocoinpartners.util.MathUtil; import org.joda.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tim Olson */ @SuppressWarnings("FieldCanBeLocal") public class MockTicker { @Inject public MockTicker(Context context, Configuration config) { this.context = context; String marketStr = config.getString("faketicker.exchange"); if (marketStr == null) throw new ConfigurationError("MockTicker must be configured with the \"mockticker.exchange\" property"); for (String marketName : marketStr.toUpperCase().split(",")) { String upperMarket = marketName.toUpperCase(); Exchange exchange = Exchange.forSymbolOrCreate(upperMarket); if (exchange == null) throw new ConfigurationError("Could not find Exchange with symbol \"" + upperMarket + "\""); for (Market market : context.getInjector().getInstance(Market.class).find(exchange)) { new Thread(new PoissonTickerThread(market)).start(); } } } public void stop() { running = false; } private double nextVolume() { return volumeBasis * MathUtil.getPoissonRandom(averageVolume); } private double nextPrice() { double delta = random.nextGaussian() * priceMovementStdDev; double multiple; if (delta < 0) multiple = 1 / (1 - delta); else multiple = 1 + delta; currentPrice *= multiple; return currentPrice; } private class PoissonTickerThread extends Thread { @Override public void run() { log.debug("running mock ticker"); running = true; while (running) { try { double lambda = 1 / averageTimeBetweenTrades; double poissonSleep = -Math.log(1d - random.nextDouble()) / lambda; sleep((long) (1000 * poissonSleep)); } catch (InterruptedException e) { break; } if (!running) break; Trade trade = Trade.fromDoubles(market, Instant.now(), null, nextPrice(), nextVolume()); context.publish(trade); } } private PoissonTickerThread(Market market) { setDaemon(true); this.market = market; } private final Market market; } private final double averageTimeBetweenTrades = 2; private final double priceMovementStdDev = 0.0001; private final double averageVolume = 100.0; private final double volumeBasis = 1 / 1000.0; private final Random random = new Random(); private double currentPrice = 100; private volatile boolean running; private final Context context; protected static Logger log = LoggerFactory.getLogger("org.cryptocoinpartners.mockTicker"); }