├── .gitignore ├── .project ├── .classpath ├── test ├── com │ └── quantisan │ │ └── JFUtil │ │ ├── RoundingTest.java │ │ ├── OrdererTester.java │ │ ├── AccountingTester.java │ │ ├── OrderingTester.java │ │ └── IndicatorBean │ │ └── BeanTester.java └── jfutilDemo.java ├── .settings └── org.eclipse.jdt.core.prefs └── src └── com └── quantisan └── JFUtil ├── Printer.java ├── IndicatorBean ├── AbstractIndicatorBean.java ├── AverageTrueRange.java ├── UltimateOscillator.java ├── MovingAverage.java ├── FastStochastic.java ├── IndicatorBeanFactory.java ├── StochasticRelativeStrengthIndex.java ├── MovingAverageConvergenceDivergence.java ├── Stochastic.java └── Indicating.java ├── LabelMaker.java ├── Rounding.java ├── Barer.java ├── JForexContext.java ├── JForexAccount.java ├── Recorder.java ├── Pairer.java ├── Logging.java ├── OrderTicket.java ├── Accounting.java ├── Orderer.java └── Ordering.java /.gitignore: -------------------------------------------------------------------------------- 1 | /doc 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | JFUtil 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/com/quantisan/JFUtil/RoundingTest.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import com.dukascopy.api.Instrument; 9 | 10 | public class RoundingTest { 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | } 15 | 16 | @Test 17 | public void testPip() { 18 | assertEquals(1.37983d, Rounding.pip(Instrument.EURUSD, 1.3798312368), 0d); 19 | } 20 | 21 | @Test 22 | public void testLot() { 23 | assertEquals(2.152d, Rounding.lot(2.15234568d), 0d); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Mon Oct 18 21:19:23 EDT 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.6 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.6 13 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Printer.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | /** 4 | * JForex printing functions 5 | * 6 | * @author plam 7 | * 8 | */ 9 | public class Printer { 10 | private Printer() {} 11 | 12 | /** 13 | * @param input to be printed to the JForex PrintStream 14 | */ 15 | public static void println(Object input) { 16 | JForexContext.getConsole().getOut().println(input); 17 | } 18 | 19 | /** 20 | * @param input to be printed to the JForex PrintStream 21 | * @param ex the caught exception to be printed with stack trace 22 | */ 23 | public static void printErr(Object input, Exception ex) { 24 | ex.printStackTrace(JForexContext.getConsole().getErr()); 25 | println(input); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/AbstractIndicatorBean.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators.AppliedPrice; 4 | import com.dukascopy.api.OfferSide; 5 | 6 | public abstract class AbstractIndicatorBean { 7 | protected String functionName; 8 | protected OfferSide[] offerSides; 9 | protected Integer[] optParams; 10 | protected AppliedPrice[] inputTypeArray; 11 | 12 | protected OfferSide[] getOfferSide() { return offerSides; } 13 | 14 | protected String getFunctionName() { return functionName; } 15 | 16 | protected AppliedPrice[] getInputTypes() { return inputTypeArray; } 17 | 18 | protected Object[] getParams() { return optParams; } 19 | 20 | @Override public abstract String toString(); 21 | } 22 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/LabelMaker.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import com.dukascopy.api.Instrument; 4 | 5 | /** 6 | * Makes unique order labels 7 | * 8 | * @author Paul Lam 9 | */ 10 | public class LabelMaker { 11 | private LabelMaker() {} 12 | 13 | /** 14 | * Returns an unique order label numbered by a timestamp of the last tick 15 | * 16 | * @param instrument instrument traded 17 | * @return an unique order label with instrument name + timestamp in millisecond 18 | */ 19 | public static String getLabel (Instrument instrument) 20 | { 21 | String iname = instrument.toString(); 22 | iname = iname.substring(0, 3) + iname.substring(4, 7); 23 | String label = iname; 24 | 25 | label += JForexContext.getTime(instrument); 26 | 27 | label.toLowerCase(); 28 | return label; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/AverageTrueRange.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.OfferSide; 4 | 5 | public class AverageTrueRange extends AbstractIndicatorBean { 6 | AverageTrueRange() { 7 | this.functionName = "ATR"; // must do this 8 | this.inputTypeArray = null; // must be null for no appliedprice 9 | 10 | // setting default parameters 11 | this.offerSides = new OfferSide[] {OfferSide.BID}; 12 | this.optParams = new Integer[]{14}; 13 | } 14 | 15 | /** 16 | * @param width number of price bars to used for calculation 17 | * @return 18 | */ 19 | public AverageTrueRange setWidth(int width) { 20 | optParams[0] = Integer.valueOf(width); 21 | return this; 22 | } 23 | 24 | /** 25 | * @param os side (i.e. bid or ask) of price to use for calculation 26 | * @return 27 | */ 28 | public AverageTrueRange setOfferSide(OfferSide os) { 29 | offerSides[0] = os; 30 | return this; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Average True Range"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Rounding.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import com.dukascopy.api.Instrument; 4 | 5 | /** 6 | * Methods for rounding numbers for JForex compliance 7 | * 8 | */ 9 | public class Rounding { 10 | private Rounding() {} 11 | 12 | /** 13 | * minimum lot size on JForex 14 | */ 15 | public final static double MINLOT = 0.001d; 16 | 17 | /** 18 | * Rounding a pip value to 1/10 of a pip 19 | * 20 | @param instrument the instrument 21 | * 22 | @param value a pip value 23 | * 24 | @return a pip value rounded to 0.1 pips 25 | */ 26 | public static double pip(Instrument instrument, double value) { 27 | int scale = instrument.getPipScale(); 28 | value = Math.round(value * java.lang.Math.pow(10, scale + 1)); 29 | value /= java.lang.Math.pow(10, scale + 1); 30 | return value; 31 | } 32 | 33 | /** 34 | * Rounding by floor a double number to the thousands 35 | * 36 | @param lot lot size in 37 | * 38 | @return a floored number to the thousands 39 | */ 40 | public static double lot(double lot) 41 | { 42 | lot = (int)(lot * 1000) / (1000d); // 1000 units mininum 43 | return lot; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/UltimateOscillator.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.OfferSide; 4 | 5 | public class UltimateOscillator extends AbstractIndicatorBean { 6 | UltimateOscillator() { 7 | this.functionName = "ULTOSC"; 8 | 9 | this.offerSides = new OfferSide[] {OfferSide.BID}; 10 | this.optParams = new Integer[]{7, 14, 28}; 11 | this.inputTypeArray = null; 12 | } 13 | 14 | /** 15 | * Set number of price bars to used for calculation 16 | * 17 | * @param firstWidth (default: 7) 18 | * @param secondWidth (default: 14) 19 | * @param thirdWidth (default: 28) 20 | * @return 21 | */ 22 | public UltimateOscillator setWidths(int firstWidth, int secondWidth, int thirdWidth) { 23 | optParams[0] = Integer.valueOf(firstWidth); 24 | optParams[1] = Integer.valueOf(secondWidth); 25 | optParams[2] = Integer.valueOf(thirdWidth); 26 | return this; 27 | } 28 | 29 | /** 30 | * @param os side (i.e. bid or ask) of price to use for calculation (default: BID) 31 | * @return 32 | */ 33 | public UltimateOscillator setOfferSide(OfferSide os) { 34 | offerSides[0] = os; 35 | return this; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "Ultimate Oscillator"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Barer.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import com.dukascopy.api.IBar; 4 | import com.dukascopy.api.IIndicators.AppliedPrice; 5 | 6 | /** 7 | * Bar data manipulation utility functions 8 | * 9 | * @author Paul Lam 10 | * 11 | */ 12 | public class Barer { 13 | private Barer() {}; 14 | 15 | /** 16 | * Calculates the AppliedPrice 17 | * @param bar the bar to calculate 18 | * @param ap HIGH, LOW, MEDIAN_PRICE, TYPICAL_PRICE, CLOSE, OPEN, or WEIGHTED_CLOSE 19 | * @return the corresponding AppliedPrice, except TIMESTAMP and VOLUME, 20 | * which will return CLOSE instead 21 | */ 22 | public static double calcAppliedPrice(IBar bar, AppliedPrice ap) { 23 | double price; 24 | switch (ap) { 25 | case HIGH: price = bar.getHigh(); break; 26 | case LOW: price = bar.getLow(); break; 27 | case MEDIAN_PRICE: 28 | price = (bar.getLow() + bar.getHigh()) / 2d; 29 | break; 30 | case TYPICAL_PRICE: 31 | price = (bar.getLow() + bar.getHigh() + bar.getClose()) / 3d; 32 | break; 33 | case CLOSE: price = bar.getClose(); break; 34 | case OPEN: price = bar.getOpen(); break; 35 | case WEIGHTED_CLOSE: 36 | price = (bar.getLow() + bar.getHigh() + (bar.getClose() * 2d)) / 4d; 37 | break; 38 | default: price = bar.getClose(); break; 39 | } 40 | return price; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/com/quantisan/JFUtil/OrdererTester.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import com.dukascopy.api.IAccount; 4 | import com.dukascopy.api.IBar; 5 | import com.dukascopy.api.IContext; 6 | import com.dukascopy.api.IMessage; 7 | import com.dukascopy.api.IStrategy; 8 | import com.dukascopy.api.ITick; 9 | import com.dukascopy.api.Instrument; 10 | import com.dukascopy.api.JFException; 11 | import com.dukascopy.api.Period; 12 | 13 | public class OrdererTester implements IStrategy { 14 | private boolean isTrade = true; 15 | 16 | @Override 17 | public void onStart(IContext context) throws JFException { 18 | JForexContext.setContext(context); 19 | JForexAccount.setAccount(context.getAccount()); 20 | 21 | } 22 | 23 | @Override 24 | public void onTick(Instrument instrument, ITick tick) throws JFException { 25 | 26 | 27 | } 28 | 29 | @Override 30 | public void onBar(Instrument instrument, Period period, IBar askBar, 31 | IBar bidBar) throws JFException { 32 | 33 | 34 | } 35 | 36 | @Override 37 | public void onMessage(IMessage message) throws JFException { 38 | // TODO Auto-generated method stub 39 | 40 | } 41 | 42 | @Override 43 | public void onAccount(IAccount account) throws JFException { 44 | // TODO Auto-generated method stub 45 | 46 | } 47 | 48 | @Override 49 | public void onStop() throws JFException { 50 | // TODO Auto-generated method stub 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/MovingAverage.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators; 4 | import com.dukascopy.api.OfferSide; 5 | import com.dukascopy.api.IIndicators.AppliedPrice; 6 | 7 | public class MovingAverage extends AbstractIndicatorBean { 8 | MovingAverage() { 9 | this.functionName = "MA"; // must do this 10 | 11 | // setting default parameters 12 | this.offerSides = new OfferSide[] {OfferSide.BID}; 13 | this.optParams = new Integer[]{20, IIndicators.MaType.SMA.ordinal()}; 14 | this.inputTypeArray = new AppliedPrice[] {IIndicators.AppliedPrice.CLOSE}; 15 | } 16 | 17 | /** 18 | * @param mt type of moving average implementation to use (default: SMA) 19 | * @return 20 | */ 21 | public MovingAverage setMAType(IIndicators.MaType mt) { 22 | optParams[1] = mt.ordinal(); 23 | return this; 24 | } 25 | 26 | /** 27 | * @param width number of price bars to used for calculation (default: 20) 28 | * @return 29 | */ 30 | public MovingAverage setWidth(int width) { 31 | optParams[0] = Integer.valueOf(width); 32 | return this; 33 | } 34 | 35 | /** 36 | * @param ap which price to use for calculation (default: CLOSE) 37 | * @return 38 | */ 39 | public MovingAverage setAppliedPrice(AppliedPrice ap) { 40 | inputTypeArray[0] = ap; 41 | return this; 42 | } 43 | 44 | /** 45 | * @param os side (i.e. bid or ask) of price to use for calculation (default: BID) 46 | * @return 47 | */ 48 | public MovingAverage setOfferSide(OfferSide os) { 49 | offerSides[0] = os; 50 | return this; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Moving Average"; 56 | } 57 | } -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/FastStochastic.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators; 4 | import com.dukascopy.api.OfferSide; 5 | import com.dukascopy.api.IIndicators.AppliedPrice; 6 | 7 | public class FastStochastic extends AbstractIndicatorBean { 8 | public FastStochastic() { 9 | this.functionName = "STOCHF"; // must do this 10 | 11 | // setting default parameters 12 | this.offerSides = new OfferSide[] {OfferSide.BID}; 13 | this.optParams = new Integer[]{5, 3, IIndicators.MaType.SMA.ordinal()}; 14 | this.inputTypeArray = new IIndicators.AppliedPrice[] {AppliedPrice.CLOSE}; 15 | } 16 | 17 | /** 18 | * @param width width of the fast %K line (default: 5) 19 | * @return 20 | */ 21 | public FastStochastic setFastKWidth(int width) { 22 | this.optParams[0] = width; 23 | return this; 24 | } 25 | 26 | /** 27 | * @param width width of the fast %D line (default: 3) 28 | * @return 29 | */ 30 | public FastStochastic setFastDWidth(int width) { 31 | this.optParams[1] = width; 32 | return this; 33 | } 34 | 35 | /** 36 | * @param mt type of moving average use for fast %D line (default: SMA) 37 | * @return 38 | */ 39 | public FastStochastic setFastDMAType(IIndicators.MaType mt) { 40 | optParams[2] = mt.ordinal(); 41 | return this; 42 | } 43 | 44 | /** 45 | * @param os side (i.e. bid or ask) of price to use for calculation (default: BID) 46 | * @return 47 | */ 48 | public FastStochastic setOfferSide(OfferSide os) { 49 | offerSides[0] = os; 50 | return this; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | // TODO Auto-generated method stub 56 | return "Fast Stochastic Oscillator"; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /test/com/quantisan/JFUtil/AccountingTester.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.*; 4 | import com.dukascopy.api.*; 5 | 6 | @Library("JFQuantisan.jar") 7 | public class AccountingTester implements IStrategy { 8 | private Accounting accounter; 9 | private Logging logger; 10 | private IContext context; 11 | 12 | @Configurable("Instrument") public Instrument selectedInst = Instrument.EURJPY; 13 | @Configurable("StopSize") public int stopSize = 100; 14 | 15 | @Override 16 | public void onStart(IContext context) throws JFException { 17 | logger = new Logging(context.getConsole()); 18 | accounter = new Accounting(context); 19 | this.context = context; 20 | Set instSet = new HashSet(); 21 | instSet.add(selectedInst); 22 | accounter.subscribeTransitionalInstruments(instSet); 23 | logger.print("done onStart"); 24 | 25 | } 26 | 27 | @Override 28 | public void onTick(Instrument instrument, ITick tick) throws JFException { 29 | 30 | } 31 | 32 | @Override 33 | public void onBar(Instrument instrument, Period period, IBar askBar, 34 | IBar bidBar) throws JFException 35 | { 36 | if (period == Period.TEN_SECS) { 37 | double stopSizePip = this.stopSize * selectedInst.getPipValue(); 38 | //logger.print("" + accounter.getAccountRiskPerUnit(selectedInst, stopSizePip)); 39 | this.context.stop(); 40 | } 41 | 42 | } 43 | 44 | @Override 45 | public void onMessage(IMessage message) throws JFException { 46 | 47 | 48 | } 49 | 50 | @Override 51 | public void onAccount(IAccount account) throws JFException { 52 | accounter.update(account); 53 | 54 | } 55 | 56 | @Override 57 | public void onStop() throws JFException { 58 | 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /test/com/quantisan/JFUtil/OrderingTester.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.*; 5 | import com.dukascopy.api.*; 6 | 7 | @Library("JFQuantisan.jar") 8 | public class OrderingTester implements IStrategy { 9 | Ordering orderer; 10 | Logging logger; 11 | 12 | @Configurable("Instrument") public Instrument selectedInst = Instrument.EURJPY; 13 | 14 | @Override 15 | public void onStart(IContext context) throws JFException { 16 | orderer = new Ordering(context, 3); 17 | logger = new Logging(context.getConsole()); 18 | 19 | IOrder order = null; 20 | logger.print("Placing bid"); 21 | 22 | Future future = 23 | orderer.placeMarketOrder("ABC", "xyz", selectedInst, 24 | IEngine.OrderCommand.SELL, 25 | 0.1, 112.00, 50, 0d); 26 | // try { 27 | // order = future.get(); 28 | // } 29 | // catch (Exception ex) { 30 | // logger.printErr("Bid order not ready yet.", ex); 31 | // return; 32 | // } 33 | // order.waitForUpdate(1000); 34 | logger.print("done onStart"); 35 | } 36 | 37 | @Override 38 | public void onTick(Instrument instrument, ITick tick) throws JFException { 39 | 40 | 41 | } 42 | 43 | @Override 44 | public void onBar(Instrument instrument, Period period, IBar askBar, 45 | IBar bidBar) throws JFException { 46 | if (period != Period.ONE_HOUR || instrument != selectedInst) return; 47 | 48 | List orders = orderer.getOrders(selectedInst); 49 | for (IOrder order : orders) 50 | logger.printOrderInfo(order); 51 | } 52 | 53 | @Override 54 | public void onMessage(IMessage message) throws JFException { 55 | 56 | 57 | } 58 | 59 | @Override 60 | public void onAccount(IAccount account) throws JFException { 61 | 62 | } 63 | 64 | @Override 65 | public void onStop() throws JFException { 66 | //for (IOrder order : engine.getOrders()) 67 | //engine.closeOrders(order); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/IndicatorBeanFactory.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | public class IndicatorBeanFactory { 4 | protected IndicatorBeanFactory() {}; 5 | 6 | /** 7 | * Average True Range (ATR) 8 | * 9 | * @return ATR indicator bean 10 | */ 11 | public static AverageTrueRange getAverageTrueRange() { return new AverageTrueRange(); } 12 | 13 | /** 14 | * Moving Average (MA), includes different implementations 15 | * 16 | * @return moving average bean 17 | */ 18 | public static MovingAverage getMovingAverage() { return new MovingAverage(); } 19 | 20 | /** 21 | * Moving Average Convergence-Divergence (MACD). 22 | * 23 | * Example use to cast the output: 24 | * 25 | *
{@code 
26 | 		*double[][] macd = new double[3][];
27 | 		*macd[0] = (double[])objs[0];		// macd values
28 | 		*macd[1] = (double[])objs[1];		// signal values
29 | 		*macd[2] = (double[])objs[2];		// histogram values}
30 | * 31 | * @author plam 32 | * @return macd bean 33 | */ 34 | public static MovingAverageConvergenceDivergence getMovingAverageConvergenceDivergence() { return new MovingAverageConvergenceDivergence(); } 35 | 36 | /** 37 | * Ultimate Oscillator (UltOsc) 38 | * 39 | * @return Ult Osc bean 40 | */ 41 | public static UltimateOscillator getUltimateOscillator() { return new UltimateOscillator(); } 42 | 43 | /** 44 | * Stochastic Oscillator (STOCH) 45 | * 46 | * @return stoch bean 47 | */ 48 | public static Stochastic getStochastic() { return new Stochastic(); } 49 | 50 | /** 51 | * Fast Stochasic Oscillator (STOCHF) 52 | * @return fast stoch bean 53 | */ 54 | public static FastStochastic getFastStochastic() { return new FastStochastic(); } 55 | 56 | /** 57 | * Stochastic Relative Strength Index (STOCHRSI) 58 | * 59 | * @return stoch-RSI bean 60 | */ 61 | public static StochasticRelativeStrengthIndex getStochasticRSI() { return new StochasticRelativeStrengthIndex(); } 62 | } 63 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/StochasticRelativeStrengthIndex.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators; 4 | import com.dukascopy.api.OfferSide; 5 | import com.dukascopy.api.IIndicators.AppliedPrice; 6 | 7 | /** 8 | * Example use to cast the output 9 | *
10 | * {@code 
11 | 	*Object[] objs = Indicating.calculateMultiDimension(instrument, Period.ONE_MIN, srBean, 1);
12 | 	*double[][] sto = new double[2][];
13 | 	*sto[0] = (double[])objs[0];		// Fast %K values
14 | 	*sto[1] = (double[])objs[1];		// Fast %D values}
15 | * 
16 | * @author plam 17 | * 18 | */ 19 | public class StochasticRelativeStrengthIndex extends AbstractIndicatorBean { 20 | public StochasticRelativeStrengthIndex() { 21 | this.functionName = "STOCHRSI"; // must do this 22 | 23 | // setting default parameters 24 | this.offerSides = new OfferSide[] {OfferSide.BID}; 25 | this.optParams = new Integer[]{14, 5, 3, IIndicators.MaType.SMA.ordinal()}; 26 | this.inputTypeArray = new AppliedPrice[] {IIndicators.AppliedPrice.CLOSE}; 27 | } 28 | 29 | /** 30 | * @param width width of RSI (default: 14) 31 | * @return 32 | */ 33 | public StochasticRelativeStrengthIndex setRSIWidth(int width) { 34 | this.optParams[0] = width; 35 | return this; 36 | } 37 | 38 | /** 39 | * @param width width of the fast %K line (default: 5) 40 | * @return 41 | */ 42 | public StochasticRelativeStrengthIndex setFastKWidth(int width) { 43 | this.optParams[1] = width; 44 | return this; 45 | } 46 | 47 | /** 48 | * @param width width of the fast %D line (default: 3) 49 | * @return 50 | */ 51 | public StochasticRelativeStrengthIndex setFastDWidth(int width) { 52 | this.optParams[2] = width; 53 | return this; 54 | } 55 | 56 | /** 57 | * @param mt type of moving average to use for slow %D line (default: SMA) 58 | * @return 59 | */ 60 | public StochasticRelativeStrengthIndex setSlowDMAType(IIndicators.MaType mt) { 61 | optParams[3] = mt.ordinal(); 62 | return this; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "Stochastic-Relative Strength Index"; 68 | } 69 | } -------------------------------------------------------------------------------- /test/com/quantisan/JFUtil/IndicatorBean/BeanTester.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.Filter; 4 | import com.dukascopy.api.IAccount; 5 | import com.dukascopy.api.IBar; 6 | import com.dukascopy.api.IContext; 7 | import com.dukascopy.api.IIndicators; 8 | import com.dukascopy.api.IMessage; 9 | import com.dukascopy.api.IStrategy; 10 | import com.dukascopy.api.ITick; 11 | import com.dukascopy.api.Instrument; 12 | import com.dukascopy.api.JFException; 13 | import com.dukascopy.api.Library; 14 | import com.dukascopy.api.Period; 15 | import com.quantisan.JFUtil.JForexAccount; 16 | import com.quantisan.JFUtil.JForexContext; 17 | import com.quantisan.JFUtil.Printer; 18 | 19 | @Library("JFQuantisan.jar") 20 | public class BeanTester implements IStrategy { 21 | 22 | @Override 23 | public void onBar(Instrument instrument, Period period, IBar askBar, 24 | IBar bidBar) throws JFException 25 | { 26 | if (period != Period.TEN_SECS || instrument != Instrument.EURUSD) return; 27 | 28 | MovingAverageConvergenceDivergence macdBean = IndicatorBeanFactory.getMovingAverageConvergenceDivergence(); 29 | Object[] objs = Indicating.calculateMultiDimension(instrument, Period.ONE_MIN, macdBean, 1); 30 | 31 | double[][] macd = new double[3][]; 32 | macd[0] = (double[])objs[0]; // macd values 33 | macd[1] = (double[])objs[1]; // signal values 34 | macd[2] = (double[])objs[2]; // histogram values 35 | 36 | Printer.println(instrument.toString() + " macd = " + macd[0][0] 37 | + " / " + macd[1][0] 38 | + " / " + macd[2][0]); 39 | } 40 | 41 | @Override 42 | public void onStart(IContext context) throws JFException { 43 | JForexContext.setContext(context); 44 | JForexAccount.setAccount(context.getAccount()); 45 | 46 | Indicating.setFilter(Filter.WEEKENDS); 47 | } 48 | 49 | @Override 50 | public void onTick(Instrument instrument, ITick tick) throws JFException { 51 | } 52 | 53 | @Override 54 | public void onMessage(IMessage message) throws JFException { 55 | } 56 | 57 | @Override 58 | public void onAccount(IAccount account) throws JFException { 59 | } 60 | 61 | @Override 62 | public void onStop() throws JFException { 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/MovingAverageConvergenceDivergence.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators; 4 | import com.dukascopy.api.OfferSide; 5 | import com.dukascopy.api.IIndicators.AppliedPrice; 6 | 7 | /** 8 | * Example use to cast the output 9 | *
10 | * {@code 
11 | 	*double[][] macd = new double[3][];
12 | 	*macd[0] = (double[])objs[0];		// macd values
13 | 	*macd[1] = (double[])objs[1];		// signal values
14 | 	*macd[2] = (double[])objs[2];		// histogram values}
15 | * 
16 | * @author plam 17 | * 18 | */ 19 | public class MovingAverageConvergenceDivergence extends AbstractIndicatorBean { 20 | // [0] = MACD, [1] = signal, [2] = Macd Hist data 21 | 22 | public MovingAverageConvergenceDivergence() { 23 | 24 | this.functionName = "MACD"; // must do this 25 | 26 | // setting default parameters 27 | this.offerSides = new OfferSide[] {OfferSide.BID}; 28 | this.optParams = new Integer[]{12, 26, 9}; 29 | this.inputTypeArray = new IIndicators.AppliedPrice[] {AppliedPrice.CLOSE}; 30 | } 31 | 32 | /** 33 | * @param width width of the fast line (default: 12) 34 | * @return 35 | */ 36 | public MovingAverageConvergenceDivergence setFastWidth(int width) { 37 | this.optParams[0] = width; 38 | return this; 39 | } 40 | 41 | /** 42 | * @param width width of the slow line (default: 26) 43 | * @return 44 | */ 45 | public MovingAverageConvergenceDivergence setSlowWidth(int width) { 46 | this.optParams[1] = width; 47 | return this; 48 | } 49 | 50 | /** 51 | * @param ap which price to use for calculation (default: CLOSE) 52 | * @return 53 | */ 54 | public MovingAverageConvergenceDivergence setAppliedPrice(AppliedPrice ap) { 55 | inputTypeArray[0] = ap; 56 | return this; 57 | } 58 | 59 | /** 60 | * @param width width of the signal line (default: 9) 61 | * @return 62 | */ 63 | public MovingAverageConvergenceDivergence setSignalWidth(int width) { 64 | this.optParams[2] = width; 65 | return this; 66 | } 67 | 68 | /** 69 | * @param os side (i.e. bid or ask) of price to use for calculation (default: BID) 70 | * @return 71 | */ 72 | public MovingAverageConvergenceDivergence setOfferSide(OfferSide os) { 73 | offerSides[0] = os; 74 | return this; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "Moving Average Convergence-Divergence"; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/Stochastic.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import com.dukascopy.api.IIndicators; 4 | import com.dukascopy.api.IIndicators.AppliedPrice; 5 | import com.dukascopy.api.OfferSide; 6 | 7 | /** 8 | * Example use to cast the output 9 | *
10 | * {@code 
11 | 	*Object[] objs = Indicating.calculateMultiDimension(instrument, Period.ONE_MIN, stochBean, 1);
12 | 	*double[][] sto = new double[2][];
13 | 	*sto[0] = (double[])objs[0];		// %K values
14 | 	*sto[1] = (double[])objs[1];		// %D values}
15 | * 
16 | * @author plam 17 | * 18 | */ 19 | public class Stochastic extends AbstractIndicatorBean { 20 | public Stochastic() { 21 | this.functionName = "STOCH"; // must do this 22 | 23 | // setting default parameters 24 | this.offerSides = new OfferSide[] {OfferSide.BID}; 25 | this.optParams = new Integer[]{5, 3, IIndicators.MaType.SMA.ordinal() 26 | ,3, IIndicators.MaType.SMA.ordinal()}; 27 | this.inputTypeArray = new IIndicators.AppliedPrice[] {AppliedPrice.CLOSE}; 28 | } 29 | 30 | /** 31 | * @param width width of the fast %K line (default: 5) 32 | * @return 33 | */ 34 | public Stochastic setFastKWidth(int width) { 35 | this.optParams[0] = width; 36 | return this; 37 | } 38 | 39 | /** 40 | * @param width width of the slow %K line (default: 3) 41 | * @return 42 | */ 43 | public Stochastic setSlowKWidth(int width) { 44 | this.optParams[1] = width; 45 | return this; 46 | } 47 | 48 | /** 49 | * @param mt type of moving average use for slow %K line (default: SMA) 50 | * @return 51 | */ 52 | public Stochastic setSlowKMAType(IIndicators.MaType mt) { 53 | optParams[2] = mt.ordinal(); 54 | return this; 55 | } 56 | 57 | /** 58 | * @param width width of the slow %D line (default: 3) 59 | * @return 60 | */ 61 | public Stochastic setSlowDWidth(int width) { 62 | this.optParams[3] = width; 63 | return this; 64 | } 65 | 66 | /** 67 | * @param mt type of moving average to use for slow %D line (default: SMA) 68 | * @return 69 | */ 70 | public Stochastic setSlowDMAType(IIndicators.MaType mt) { 71 | optParams[4] = mt.ordinal(); 72 | return this; 73 | } 74 | 75 | /** 76 | * @param os side (i.e. bid or ask) of price to use for calculation (default: BID) 77 | * @return 78 | */ 79 | public Stochastic setOfferSide(OfferSide os) { 80 | offerSides[0] = os; 81 | return this; 82 | } 83 | 84 | @Override 85 | public String toString() { 86 | return "Stochastic"; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/JForexContext.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.Calendar; 4 | import java.util.TimeZone; 5 | 6 | import com.dukascopy.api.*; 7 | 8 | /** 9 | * Provides singleton access to JForex context objects 10 | * 11 | */ 12 | public enum JForexContext { 13 | INSTANCE; 14 | 15 | private IContext context; 16 | private IEngine engine; 17 | private IConsole console; 18 | private IHistory history; 19 | private IIndicators indicators; 20 | 21 | /** 22 | * Set JForex context objects. Must initialize before use of strategy. 23 | * 24 | * @param context 25 | */ 26 | public static void setContext(IContext context) { 27 | INSTANCE.context = context; 28 | INSTANCE.engine = context.getEngine(); 29 | INSTANCE.console = context.getConsole(); 30 | INSTANCE.history = context.getHistory(); 31 | INSTANCE.indicators = context.getIndicators(); 32 | } 33 | 34 | /** 35 | * @return the context 36 | */ 37 | public static IContext getContext() { 38 | return INSTANCE.context; 39 | } 40 | 41 | /** 42 | * @return the engine 43 | */ 44 | public static IEngine getEngine() { 45 | return INSTANCE.engine; 46 | } 47 | 48 | /** 49 | * @return the console 50 | */ 51 | public static IConsole getConsole() { 52 | return INSTANCE.console; 53 | } 54 | 55 | /** 56 | * @return the history 57 | */ 58 | public static IHistory getHistory() { 59 | return INSTANCE.history; 60 | } 61 | 62 | /** 63 | * Access to JForex IIndicators 64 | * 65 | * @return the indicators 66 | */ 67 | public static IIndicators getIndicators() { 68 | return INSTANCE.indicators; 69 | } 70 | 71 | /** 72 | * Get the latest bid price of an instrument 73 | * 74 | @param instrument the instrument to lookup 75 | * 76 | @return latest tick bid price 77 | * 78 | **/ 79 | public static double getPrice(Instrument instrument) { 80 | double price; 81 | try { 82 | price = getHistory().getLastTick(instrument).getBid(); 83 | } catch (JFException ex) { 84 | price = Double.NaN; 85 | Printer.printErr("Cannot get price.", ex); 86 | } 87 | return price; 88 | } 89 | 90 | /** 91 | * @param instrument 92 | * @return time of last tick if available; if not, time of system in GMT 93 | */ 94 | public static long getTime(Instrument instrument) { 95 | try { 96 | return getHistory().getTimeOfLastTick(instrument); 97 | } catch (JFException ex) { 98 | Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 99 | return calendar.getTimeInMillis(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/JForexAccount.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.Currency; 4 | import java.util.List; 5 | 6 | import com.dukascopy.api.IAccount; 7 | import com.dukascopy.api.IOrder; 8 | import com.dukascopy.api.Instrument; 9 | import com.dukascopy.api.JFException; 10 | 11 | /** 12 | * Provides singleton access to JForex IAccount object 13 | * 14 | * @author plam 15 | * 16 | */ 17 | public enum JForexAccount { 18 | INSTANCE; 19 | 20 | private IAccount account; 21 | //private double riskPct; 22 | 23 | private double maxEquity = Double.NEGATIVE_INFINITY; 24 | //private HashMap pairs = new HashMap(); 25 | 26 | /** 27 | * 28 | */ 29 | private JForexAccount() { 30 | } 31 | 32 | // /** 33 | // * @return the riskPct 34 | // */ 35 | // public static double getRiskPct() { 36 | // return INSTANCE.riskPct; 37 | // } 38 | // 39 | // /** 40 | // * @param riskPct Fraction of account to put at risk per position (0.0, 1.0] 41 | // */ 42 | // public static void setRiskPct(double riskPct) { 43 | // INSTANCE.riskPct = riskPct; 44 | // } 45 | 46 | /** 47 | * @return the account 48 | */ 49 | public static IAccount getAccount() { 50 | return INSTANCE.account; 51 | } 52 | 53 | /** 54 | * @deprecated use {@link #getCurrency()} 55 | * @return the account currency 56 | */ 57 | @Deprecated public static Currency getAccountCurrency() { 58 | return INSTANCE.account.getCurrency(); 59 | } 60 | 61 | /** 62 | * @return the account currency 63 | */ 64 | public static Currency getCurrency() { 65 | return INSTANCE.account.getCurrency(); 66 | } 67 | 68 | /** 69 | * Set or update IAccount object. For initializing and update in onAccount() 70 | * 71 | * @param account the account to set 72 | */ 73 | public static void setAccount(IAccount account) { 74 | INSTANCE.account = account; 75 | INSTANCE.updateMaxEquity(); 76 | } 77 | 78 | /** 79 | * Maximum drawdown is calculated from peak of realised + unrealised gains to current equity. 80 | * 81 | * @param maxDrawdown maximum drawdown in percent decimal, [0.0, 1.0]. 82 | * For example, max drawdown of 5% should be entered as 0.05 83 | * 84 | * @return true if max drawdown is reached 85 | */ 86 | public static boolean isMaxDrawdownBroken(double maxDrawdown) { 87 | if (maxDrawdown < 0d || maxDrawdown > 1d) { 88 | throw new IllegalArgumentException("maxDrawdown must be [0.0, 1.0]"); 89 | } 90 | INSTANCE.updateMaxEquity(); 91 | return (INSTANCE.getDrawdown() < -maxDrawdown); 92 | } 93 | 94 | private void updateMaxEquity() { 95 | if(getEquity() > this.maxEquity) 96 | this.maxEquity = getEquity(); 97 | } 98 | 99 | /** 100 | * 101 | * @return current drawdown in negative percentage, positive means profitable 102 | */ 103 | private double getDrawdown() { 104 | return 1 - getEquity()/INSTANCE.maxEquity; 105 | } 106 | 107 | /** 108 | * @return account equity 109 | * 110 | */ 111 | public static double getEquity() { 112 | return INSTANCE.account.getEquity(); 113 | } 114 | 115 | /** 116 | * Iterate through the list of opened orders and positions of an instrument 117 | * to calculate the cummulative monetary amount (in account currency) 118 | * that is at risk. Only includes orders/positions with a stop loss price set. 119 | * 120 | * @param instrument 121 | * @return amount in account currency at risk, only include orders with stop loss set 122 | * @throws JFException 123 | */ 124 | public static double getAmountAtRisk(Instrument instrument) throws JFException { 125 | double totalValueExposed = 0; 126 | 127 | List orders = Orderer.getOrders(instrument); 128 | for (IOrder order : orders) { 129 | if (order.getState() == IOrder.State.FILLED || 130 | order.getState() == IOrder.State.OPENED || 131 | order.getState() == IOrder.State.CREATED) 132 | { 133 | double stop = order.getStopLossPrice(); 134 | if (stop != 0d) { 135 | double pipsExposed = order.getOpenPrice() - order.getStopLossPrice(); 136 | pipsExposed *= order.isLong() ? 1d : -1d; 137 | totalValueExposed += pipsExposed * order.getAmount() * 1e6d; 138 | } 139 | } 140 | } 141 | return Pairer.convertValueToAccountCurrency(instrument, totalValueExposed); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /test/jfutilDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import com.dukascopy.api.*; 3 | import com.quantisan.JFUtil.*; 4 | import com.quantisan.JFUtil.IndicatorBean.*; 5 | 6 | /* 7 | * Changelog: 8 | * March 17: 9 | * 10 | */ 11 | 12 | @Library("JFQuantisan.jar") // place this file in your ../JForex/Strategy/files folder 13 | public class jfutilDemo implements IStrategy { 14 | @Override 15 | public void onStart(IContext context) throws JFException { 16 | // ** Essential steps ** 17 | // must initialize objects once and for all 18 | JForexContext.setContext(context); 19 | JForexAccount.setAccount(context.getAccount()); 20 | 21 | 22 | Set set = new HashSet(context.getSubscribedInstruments()); 23 | set = context.getSubscribedInstruments(); // get list of subscribed instruments 24 | // subscribe to transitional instruments for currency conversion calculations 25 | Pairer.subscribeTransitionalInstruments(set); 26 | // ** End of essential steps ** 27 | 28 | Printer.println("-- Quantisan.com JFUtil v2.0 alpha: Usage demo --"); 29 | Printer.println(""); 30 | } 31 | 32 | @Override 33 | public void onBar(Instrument instrument, Period period, IBar askBar, 34 | IBar bidBar) throws JFException { 35 | if (period != Period.TEN_SECS || instrument != Instrument.EURUSD) 36 | return; // skipping most periods and instruments 37 | 38 | // *** 1. access IContext and IAccount from anywhere *** 39 | //Printer.println("Account equity = " + JForexAccount.getEquity()); 40 | 41 | // *** 2. simpler indicator use with intuitive method calls *** 42 | // get an EMA indicator value by building an indicator bean 43 | MovingAverage maBean = IndicatorBeanFactory.getMovingAverage(); 44 | // then sets its parameters with obvious method names 45 | maBean.setAppliedPrice(IIndicators.AppliedPrice.MEDIAN_PRICE) 46 | .setMAType(IIndicators.MaType.EMA) 47 | .setWidth(14); // all of these are optional parameters 48 | // feed the bean into a generic calculation method to get the result 49 | double ema = Indicating.calculate(instrument, Period.ONE_MIN, maBean); 50 | 51 | // printing the EMA value 52 | Printer.println(instrument.toString() + " EMA = " + ema); 53 | 54 | // *** 3. Profit/loss calculation to account currency before placing your order *** 55 | // Demonstrating currency conversion 56 | double risk = 100 * Pairer.convertPipToAccountCurrency(instrument); 57 | String symbol = JForexAccount.getCurrency().getSymbol(); 58 | Printer.println(symbol + risk + 59 | " risked in for 1,000 units and 100 pips move in " + 60 | instrument.toString()); 61 | 62 | 63 | // ** 4. Simplify order parameters with order ticket builder *** 64 | // Demonstrating trade ordering 65 | String label = LabelMaker.getLabel(instrument) + 'a'; 66 | OrderTicket mktBuyTicket = new OrderTicket // order ticket 67 | .Builder(label, // setting required ticket info 68 | instrument, 69 | IEngine.OrderCommand.BUY, 70 | 0.1) 71 | .build(); // build ticket 72 | Orderer.placeOrder(mktBuyTicket); // placing order 73 | 74 | // market buy order with a 100 pips stop and 100 pips target 75 | double stopPrice = JForexContext.getPrice(instrument) - (100 * instrument.getPipValue()); 76 | double targetPrice = JForexContext.getPrice(instrument) + (100 * instrument.getPipValue()); 77 | 78 | label = LabelMaker.getLabel(instrument) + 'b'; 79 | OrderTicket buySpTicket = new OrderTicket 80 | .Builder(label, 81 | instrument, 82 | IEngine.OrderCommand.BUY, 83 | 0.1) 84 | .setStopLossPrice(stopPrice) // set stop price to ticket 85 | .setTakeProfitPrice(targetPrice) // set target 86 | .build(); 87 | // ** 5. Single method to placing orders for all order types and parameters *** 88 | Orderer.placeOrder(buySpTicket); 89 | } 90 | 91 | @Override 92 | public void onAccount(IAccount account) throws JFException { 93 | JForexAccount.setAccount(account); // update IAccount to latest 94 | 95 | } 96 | 97 | @Override 98 | public void onStop() throws JFException { 99 | for (IOrder order : Orderer.getOrders()) // close all orders 100 | Orderer.close(order); 101 | //order.close(); 102 | } 103 | 104 | 105 | // methods below this line are not used 106 | 107 | 108 | @Override 109 | public void onTick(Instrument instrument, ITick tick) throws JFException { 110 | } 111 | 112 | @Override 113 | public void onMessage(IMessage message) throws JFException { 114 | } 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Recorder.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.Writer; 8 | import java.util.List; 9 | 10 | import com.dukascopy.api.IOrder; 11 | import com.dukascopy.api.JFException; 12 | 13 | /** 14 | * Data recording utility class 15 | * 16 | * @author plam 17 | * 18 | */ 19 | public class Recorder { 20 | //private static Analyzer instance = new Analyzer(); 21 | private Recorder() {} 22 | 23 | /** 24 | * Fetch and save order history into a CSV file. 25 | * 26 | * @param fileName CSV file name to save order history. Will overwrite file. 27 | * @return true if orders were successfully saved to fileName 28 | */ 29 | public static boolean record(String fileName) { 30 | // Thread worker; 31 | // Runnable task = instance.new fetchAndSaveOrders(); 32 | // worker = new Thread(task); 33 | // worker.start(); 34 | 35 | List orders; 36 | try { 37 | orders = Orderer.getOrders(); 38 | } catch (JFException ex) { 39 | Printer.printErr("Unable to get list of orders", ex); 40 | return false; 41 | } 42 | 43 | File fileDir = JForexContext.getContext().getFilesDir(); 44 | // full path name for file, with some string cleanup 45 | String fullFileName = fileDir.toString() + File.separator + fileName + ".csv"; 46 | // TODO add date suffix to file name 47 | BufferedWriter writer; 48 | try { 49 | // BufferedWriter to increase I/O performance 50 | writer = new BufferedWriter(new FileWriter(fullFileName)); 51 | final String comma = ","; 52 | writer.write("id" + comma); 53 | writer.write("label" + comma); 54 | writer.write("instrument" + comma); 55 | writer.write("is_long" + comma); 56 | writer.write("order_command" + comma); 57 | writer.write("amount" + comma); 58 | writer.write("requested_amount" + comma); 59 | writer.write("open_price" + comma); 60 | writer.write("creation_time" + comma); 61 | writer.write("fill_time" + comma); 62 | writer.write("close_price" + comma); 63 | writer.write("close_time" + comma); 64 | writer.write("stoploss_price" + comma); 65 | writer.write("takeprofit_price" + comma); 66 | writer.write("trailing_step" + comma); 67 | writer.write("profitloss_pips" + comma); 68 | writer.write("profitloss_usd" + comma); 69 | writer.write("profitloss_accountcurrency" + comma); 70 | writer.write("close_time" + comma); 71 | writer.write("comment" + "\n"); 72 | writer.flush(); 73 | } 74 | catch (IOException ex) { 75 | Printer.printErr("Cannot open file.", ex); 76 | return false; 77 | } 78 | 79 | 80 | try { 81 | writeToCSV(writer, orders); 82 | writer.flush(); 83 | writer.close(); 84 | } 85 | catch (IOException ex) { 86 | Printer.printErr("Cannot flush to file", ex); 87 | return false; 88 | } 89 | 90 | return true; // reached end without exception return, thus okay 91 | } 92 | 93 | private static void writeToCSV(Writer writer, List orders) throws IOException 94 | { 95 | final String comma = ","; 96 | for (IOrder order : orders) { 97 | if (order.getState() != IOrder.State.CLOSED) 98 | continue; // only record closed positions 99 | 100 | writer.write(order.getId() + comma); 101 | writer.write(order.getLabel() + comma); 102 | writer.write(order.getInstrument().toString() + comma); 103 | writer.write(order.isLong() + comma); 104 | writer.write(order.getOrderCommand().toString() + comma); 105 | writer.write(order.getAmount() + comma); 106 | writer.write(order.getRequestedAmount() + comma); 107 | writer.write(order.getOpenPrice() + comma); 108 | writer.write(order.getCreationTime() + comma); 109 | writer.write(order.getFillTime() + comma); 110 | writer.write(order.getClosePrice() + comma); 111 | writer.write(order.getCloseTime() + comma); 112 | writer.write(order.getStopLossPrice() + comma); 113 | writer.write(order.getTakeProfitPrice() + comma); 114 | writer.write(order.getTrailingStep() + comma); 115 | writer.write(order.getProfitLossInPips() + comma); 116 | writer.write(order.getProfitLossInUSD() + comma); 117 | writer.write(order.getProfitLossInAccountCurrency() + comma); 118 | writer.write(order.getComment() + "\n"); 119 | } 120 | } 121 | 122 | // private class fetchAndSaveOrders implements Runnable { 123 | // 124 | // @Override 125 | // public void run() { 126 | // } 127 | // 128 | // } 129 | } 130 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Pairer.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | import java.util.Currency; 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import com.dukascopy.api.Instrument; 8 | 9 | /** 10 | * Currency pair utilities 11 | * 12 | * @author plam 13 | * 14 | */ 15 | public enum Pairer { 16 | INSTANCE; 17 | 18 | private HashMap pairs = new HashMap(); 19 | private final Currency ACCOUNTCURRENCY; 20 | 21 | private Pairer() { 22 | ACCOUNTCURRENCY = JForexAccount.getCurrency(); 23 | initializeMajorPairs(); 24 | } 25 | 26 | /** 27 | * Initialize currency pairs for getting AUD, CAD, CHF, EUR, GBP 28 | * JPY, NZD, and USD counters to account currency. 29 | * 30 | */ 31 | private void initializeMajorPairs() { 32 | Set curSet = new HashSet(); 33 | // add all major currencies 34 | curSet.add(Currency.getInstance("AUD")); 35 | curSet.add(Currency.getInstance("CAD")); 36 | curSet.add(Currency.getInstance("CHF")); 37 | curSet.add(Currency.getInstance("EUR")); 38 | curSet.add(Currency.getInstance("GBP")); 39 | curSet.add(Currency.getInstance("JPY")); 40 | curSet.add(Currency.getInstance("NZD")); 41 | curSet.add(Currency.getInstance("USD")); 42 | Instrument instrument; 43 | for (Currency curr : curSet) { 44 | if (!curr.equals(ACCOUNTCURRENCY)) { 45 | instrument = getPair(curr, ACCOUNTCURRENCY); 46 | pairs.put(curr, instrument); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Get the Instrument given two Currencies 53 | * 54 | @param first a currency in a pair 55 | * 56 | @param second the other currency in a pair 57 | * 58 | @return an Instrument with the correct base/counter currencies order 59 | * 60 | **/ 61 | private static Instrument getPair(Currency first, Currency second) { 62 | String pair; 63 | pair = first.toString() + Instrument.getPairsSeparator() + 64 | second.toString(); 65 | if (Instrument.isInverted(pair)) 66 | pair = second.toString() + Instrument.getPairsSeparator() + 67 | first.toString(); 68 | return Instrument.fromString(pair); 69 | } 70 | 71 | /** 72 | * Subscribe to transitional instruments for converting profit/loss 73 | * to account currency. 74 | * Must be called before use of {@link #convertPipToAccountCurrency(Instrument)} 75 | * 76 | @param instSet set of instruments to be traded 77 | * 78 | **/ 79 | public static void subscribeTransitionalInstruments(Set instSet) { 80 | Currency firstCurr, secondCurr; 81 | Set subscribeSet = 82 | new HashSet(JForexContext.getContext().getSubscribedInstruments()); 83 | 84 | for (Instrument instrument : instSet) { 85 | firstCurr = instrument.getPrimaryCurrency(); 86 | secondCurr = instrument.getSecondaryCurrency(); 87 | if (!firstCurr.equals(INSTANCE.ACCOUNTCURRENCY) && 88 | !secondCurr.equals(INSTANCE.ACCOUNTCURRENCY)) 89 | { 90 | // TODO dynamically build pairs list according to instSet 91 | subscribeSet.add(INSTANCE.pairs.get(secondCurr)); // transitional pair 92 | } 93 | } 94 | JForexContext.getContext().setSubscribedInstruments(subscribeSet); 95 | } 96 | 97 | /** 98 | * Calculate the equivalent amount in account currency for each +1 pip on 99 | * a single unit position size (e.g. $1) of an instrument. 100 | * Beware of roundoff errors in the result. 101 | * 102 | @param instrument the instrument traded 103 | * 104 | @return the equivalent account currency amount for each +1 pip movement of instrument with a 1,000 position size 105 | **/ 106 | public static double convertPipToAccountCurrency(Instrument instrument) { 107 | return convertValueToAccountCurrency(instrument, instrument.getPipValue()); 108 | } 109 | 110 | /** 111 | * @param instrument the instrument traded 112 | * @param value initial monetary value in base currency of instrument 113 | * @return 114 | */ 115 | public static double convertValueToAccountCurrency(Instrument instrument, double value) { 116 | double output; 117 | 118 | if (instrument.getSecondaryCurrency().equals(INSTANCE.ACCOUNTCURRENCY)) { 119 | // If second currency in the instrument is account currency, 120 | // then risk is equal amount difference 121 | output = value; 122 | } else if (instrument.getPrimaryCurrency().equals(INSTANCE.ACCOUNTCURRENCY)) { 123 | output = value / JForexContext.getPrice(instrument); 124 | } else { 125 | Instrument transitionalInstrument = INSTANCE.pairs.get(instrument.getSecondaryCurrency()); 126 | double transitionalPrice = JForexContext.getPrice(transitionalInstrument); 127 | if (transitionalInstrument.getSecondaryCurrency().equals(INSTANCE.ACCOUNTCURRENCY)) 128 | output = value * transitionalPrice; 129 | else 130 | output = value / transitionalPrice; 131 | } 132 | 133 | return output; 134 | } 135 | 136 | /** 137 | * @param instrument instrument to trade 138 | * @param riskPct percent decimal (e.g. 1% = 0.01) of account risk to take 139 | * @param stopDiff = openPrice - stopPrice with the correct decimals for the particular instrument 140 | * @return a rounded lot size 141 | */ 142 | public static double getLot(Instrument instrument, double riskPct, double stopDiff) 143 | { 144 | double amount, lotSize; 145 | double equity = JForexAccount.getEquity(); 146 | amount = equity * riskPct; 147 | lotSize = amount / (convertPipToAccountCurrency(instrument) * Math.abs(stopDiff) * Math.pow(10, instrument.getPipScale())); 148 | lotSize /= 1e6; // in millions for JForex API 149 | 150 | return Rounding.lot(lotSize); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Logging.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.Writer; 8 | import java.util.List; 9 | 10 | import com.dukascopy.api.*; 11 | import com.dukascopy.api.IOrder.State; 12 | 13 | /** 14 | * Logging operations 15 | * 16 | * @deprecated use {@link Printer} 17 | */ 18 | @Deprecated public class Logging { 19 | private IConsole console; 20 | private IHistory history; 21 | private File fileDir; // JForex default write-accessible path 22 | private String currSymbol; 23 | 24 | /** 25 | * Constructor 26 | * 27 | @param console console stream for printing 28 | * 29 | @deprecated use {@link #Logging(IContext)} 30 | */ 31 | public Logging(IConsole console) { 32 | this.console = console; 33 | } 34 | 35 | /** 36 | * Constructor 37 | * 38 | @param context strategy context object 39 | * 40 | */ 41 | public Logging(IContext context) { 42 | this.fileDir = context.getFilesDir(); 43 | this.console = context.getConsole(); 44 | this.history = context.getHistory(); 45 | this.currSymbol = context.getAccount().getCurrency().getSymbol(); 46 | } 47 | 48 | public IConsole getConsole() { 49 | return console; 50 | } 51 | 52 | /** 53 | * Printing a string to the JForex PrintStream 54 | 55 | @param string a message to be printed 56 | * 57 | 58 | */ 59 | public void print(String string) { 60 | getConsole().getOut().println(string); 61 | } 62 | 63 | /** 64 | * Printing an error message and the stack trace 65 | 66 | @param string an error message to be printed 67 | * 68 | @param ex the caught exception 69 | * 70 | 71 | */ 72 | public void printErr(String string, Exception ex) { 73 | ex.printStackTrace(getConsole().getErr()); 74 | getConsole().getErr().println(string); 75 | } 76 | 77 | /** 78 | * Printing an error message and the stack trace 79 | * 80 | @param console console object 81 | * 82 | @param string an error message to be printed 83 | * 84 | @param ex the caught exception 85 | * 86 | */ 87 | public static void printErr(IConsole console, String string, Exception ex) { 88 | ex.printStackTrace(console.getErr()); 89 | console.getErr().println(string); 90 | } 91 | 92 | /** 93 | * Printing information of an order to console 94 | 95 | @param order the order 96 | * 97 | */ 98 | public void printOrderInfo(IOrder order) { 99 | long now; 100 | try { 101 | now = history.getLastTick(order.getInstrument()).getTime(); 102 | } catch (JFException ex) { 103 | printErr("Cannot get time of last tick.", ex); 104 | return; 105 | } 106 | 107 | print(order.getLabel() + " long: " + order.isLong() + 108 | " amt: " + order.getAmount() + 109 | " op: " + order.getOpenPrice() + 110 | " st: " + order.getStopLossPrice() + 111 | " tr: " + order.getTrailingStep() + 112 | " tp: " + order.getTakeProfitPrice() + 113 | " p/l pips: " + order.getProfitLossInPips() + 114 | " held for: " + timeConvert(now - order.getFillTime())); 115 | } 116 | 117 | /** 118 | * 119 | * @param time time in milliseconds 120 | * @return reformatted time in kk:hh:mm:ss, where kk is number of days, 121 | * hh is hours, mm is minutes, ss is seconds 122 | */ 123 | private String timeConvert(long time) { 124 | return time/1000/24/60 + ":" + time/1000/60%24 + ":" + time/1000%60 + 125 | ":" + time%1000; 126 | } 127 | 128 | /** 129 | * Log closed orders to a CSV file 130 | * 131 | * @param instrument 132 | * @param filename must end in '.csv' without quotes 133 | * @param from starting time to look to log orders data 134 | * @throws JFException 135 | */ 136 | public void logClosedOrdersToCSV(Instrument instrument, 137 | String filename, 138 | long from) throws JFException 139 | { 140 | long now = history.getLastTick(instrument).getTime(); 141 | 142 | List orders = history.getOrdersHistory(instrument, from, now); 143 | String fullFileName = this.fileDir.toString() + File.separator + filename; 144 | print("Writing " + orders.size() + " orders to " + fullFileName); 145 | 146 | Writer writer; 147 | // Write header 148 | try { 149 | // BufferedWriter to increase I/O performance 150 | writer = new BufferedWriter(new FileWriter(fullFileName, true)); 151 | writer.append("ID,"); 152 | writer.append("Label,"); 153 | writer.append("Fill Time,"); 154 | writer.append("Close Time,"); 155 | writer.append("Instrument,"); 156 | writer.append("Is Long,"); 157 | writer.append("Amount,"); // write column headers 158 | writer.append("Open Price,"); 159 | writer.append("Clos Price,"); 160 | writer.append("P&L [pips],"); 161 | writer.append("P&L [" + this.currSymbol + "]\n"); 162 | writer.flush(); 163 | } 164 | catch (IOException ex) { 165 | printErr("Cannot write header.", ex); 166 | return; 167 | } 168 | 169 | 170 | for (IOrder order : orders) { // Loops through all orders 171 | if (order.getState() == State.CLOSED) { // only log closed ones 172 | try { // Write row entry 173 | writer.append(order.getId() + ","); 174 | writer.append(order.getLabel() + ","); 175 | writer.append(order.getFillTime() + ","); 176 | writer.append(order.getCloseTime() + ","); 177 | writer.append(order.getInstrument() + ","); 178 | writer.append(order.isLong() + ","); 179 | writer.append(order.getAmount() + ","); 180 | writer.append(order.getOpenPrice() + ","); 181 | writer.append(order.getClosePrice() + ","); 182 | // TODO include commission and swap 183 | writer.append(order.getProfitLossInPips() + ","); 184 | writer.append(order.getProfitLossInAccountCurrency() + "\n"); 185 | } catch (IOException ex) { 186 | printErr("I/O error writing order: " + order.getId(), ex); 187 | return; 188 | } 189 | } 190 | } 191 | 192 | // File closing 193 | try { 194 | writer.flush(); 195 | writer.close(); 196 | } 197 | catch (IOException ex) { 198 | printErr("Cannot flush to file", ex); 199 | return; 200 | } 201 | 202 | print("Finished writing to " + fullFileName); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/OrderTicket.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import com.dukascopy.api.*; 4 | import com.dukascopy.api.IEngine.OrderCommand;; 5 | 6 | /** 7 | * Order ticket information encapsulation class using Builder pattern. 8 | * 9 | * Example: 10 | *
 11 |  * {@code 
 12 |  * OrderTicket smallTicket = new OrderTicket.Builder(label, instrument, cmd, amt).build();
 13 |  * OrderTicket bigTicket = new OrderTicket.Builder(label, instrument, cmd, amt).setStopLossPrice(sl).setTakeProfitPrice(tp).build();
 14 |  * }
 15 |  * 
16 | * @author plam 17 | * 18 | */ 19 | public class OrderTicket { 20 | /** 21 | * Builder for OrderTicket 22 | * 23 | * @author plam 24 | * 25 | */ 26 | public static class Builder { 27 | // set default values here 28 | // required parameters 29 | private String label; 30 | private Instrument instrument; 31 | private IEngine.OrderCommand command; 32 | private double lot; 33 | 34 | // optional parameters 35 | private double price = 0; 36 | private double slippage = 5; 37 | private double stopLossPrice = 0; 38 | private double takeProfitPrice = 0; 39 | private long goodTillTime = 0; 40 | private String comment = ""; 41 | 42 | /** 43 | * @param label unique user defined identifier for the order. 44 | * @param instrument instrument to trade 45 | * @param command type of submitted order 46 | * @param lot amount in millions for the order, will be implicitly rounded with {@link Rounder#lot(double) Rounder.lot} 47 | */ 48 | public Builder(String label, Instrument instrument, 49 | OrderCommand command, double lot) { 50 | this.label = label; 51 | this.instrument = instrument; 52 | this.command = command; 53 | this.lot = lot; 54 | } 55 | 56 | /** 57 | * Default is zero. 58 | * If zero, then last market price visible on the JForex will be used. 59 | * In case of market orders,incorrect price (worse than current market) 60 | * will be changed to current price and slippage. 61 | * 62 | * 63 | * 64 | * @param price Preferred price for order, will be implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 65 | * @return 66 | */ 67 | public Builder setPrice(double price) { 68 | this.price = price; 69 | return this; 70 | } 71 | 72 | /** 73 | * Default is 5 pips. If negative then default value of 5 pips is used. 74 | * Set in pips, you should pass 1, not 0.0001. 75 | * 76 | * @param slippage slippage to to the order 77 | * @return 78 | */ 79 | public Builder setSlippage(double slippage) { 80 | this.slippage = slippage; 81 | return this; 82 | } 83 | 84 | /** 85 | * Default is 0, which means no stop loss set. 86 | * 87 | * @param stopLossPrice Price of the stop loss, will be implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 88 | * @return 89 | */ 90 | public Builder setStopLossPrice(double stopLossPrice) { 91 | this.stopLossPrice = stopLossPrice; 92 | return this; 93 | } 94 | 95 | /** 96 | * Default is 0, which means no take profit set. 97 | * 98 | * @param takeProfitPrice Price of the take profit, will be implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 99 | * @return 100 | */ 101 | public Builder setTakeProfitPrice(double takeProfitPrice) { 102 | this.takeProfitPrice = takeProfitPrice; 103 | return this; 104 | } 105 | 106 | /** 107 | * how long order should live if not executed. 108 | * Only if > 0, then orderCommand should be 109 | * {@link OrderCommand#PLACE_BID} or {@link OrderCommand#PLACE_OFFER} 110 | * 111 | * @param goodTillTime 112 | * @return 113 | * 114 | */ 115 | public Builder setGoodTillTime(long goodTillTime) { 116 | this.goodTillTime = goodTillTime; 117 | return this; 118 | } 119 | 120 | /** 121 | * @param comment Comment that will be saved in order 122 | * @return 123 | */ 124 | public Builder setComment(String comment) { 125 | this.comment = comment; 126 | return this; 127 | } 128 | 129 | 130 | /** 131 | * Last method in chain to build OrderTicket with the set parameters. 132 | * @return {@link OrderTicket} with the set parameters. 133 | */ 134 | public OrderTicket build() { 135 | return new OrderTicket(this); 136 | } 137 | } // end of Builder inner class 138 | 139 | private OrderTicket(Builder builder) { 140 | label = builder.label; 141 | instrument = builder.instrument; 142 | command = builder.command; 143 | lot = Rounding.lot(builder.lot); 144 | price = Rounding.pip(instrument, builder.price); 145 | slippage = builder.slippage; 146 | stopLossPrice = Rounding.pip(instrument, builder.stopLossPrice); 147 | takeProfitPrice = Rounding.pip(instrument, builder.takeProfitPrice); 148 | goodTillTime = builder.goodTillTime; 149 | comment = builder.comment; 150 | } 151 | 152 | public String getLabel() { 153 | return label; 154 | } 155 | public Instrument getInstrument() { 156 | return instrument; 157 | } 158 | public IEngine.OrderCommand getOrderCmd() { 159 | return command; 160 | } 161 | /** 162 | * @return amount is implicitly rounded with {@link Rounder#lot(double) Rounder.lot} 163 | */ 164 | public double getLot() { 165 | return lot; 166 | } 167 | /** 168 | * @return price is implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 169 | */ 170 | public double getPrice() { 171 | return price; 172 | } 173 | public double getSlippage() { 174 | return slippage; 175 | } 176 | /** 177 | * @return stop loss price is implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 178 | */ 179 | public double getStopLossPrice() { 180 | return stopLossPrice; 181 | } 182 | /** 183 | * @return take profit price is implicitly rounded with {@link Rounder#pip(Instrument, double) Rounder.pip} 184 | */ 185 | public double getTakeProfitPrice() { 186 | return takeProfitPrice; 187 | } 188 | public long getGoodTillTime() { 189 | return goodTillTime; 190 | } 191 | public String getComment() { 192 | return comment; 193 | } 194 | 195 | private final String label; 196 | private final Instrument instrument; 197 | private final IEngine.OrderCommand command; 198 | private final double lot; 199 | private final double price; 200 | private final double slippage; 201 | private final double stopLossPrice; 202 | private final double takeProfitPrice; 203 | private final long goodTillTime; 204 | private final String comment; 205 | } 206 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Accounting.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.*; 4 | 5 | import com.dukascopy.api.*; 6 | 7 | /** 8 | * Accounting class 9 | * 10 | * @deprecated use {@link JForexAccount} 11 | **/ 12 | @Deprecated public class Accounting { 13 | private IAccount account; 14 | private IHistory history; 15 | private IContext context; 16 | private final Currency ACCOUNTCURRENCY; 17 | private double maxEquity; 18 | 19 | private HashMap pairs = new HashMap(); 20 | 21 | /** 22 | * Constructor 23 | * 24 | @param context context of the strategy 25 | * 26 | */ 27 | public Accounting(IContext context) { 28 | this.context = context; 29 | this.account = context.getAccount(); 30 | this.history = context.getHistory(); 31 | this.maxEquity = Double.NEGATIVE_INFINITY; 32 | updateMaxEquity(); 33 | this.ACCOUNTCURRENCY = account.getCurrency(); 34 | initializeCurrencyPairs(); 35 | } 36 | 37 | /** 38 | * @return the account currency 39 | */ 40 | public Currency getACCOUNTCURRENCY() { 41 | return ACCOUNTCURRENCY; 42 | } 43 | 44 | /** 45 | * Initialize currency pairs for getting all major counter 46 | * to account currency. 47 | * 48 | * Called in constructor. 49 | * 50 | */ 51 | private void initializeCurrencyPairs() { 52 | Set curSet = new HashSet(); 53 | // add all major currencies 54 | curSet.add(Currency.getInstance("AUD")); 55 | curSet.add(Currency.getInstance("CAD")); 56 | curSet.add(Currency.getInstance("CHF")); 57 | curSet.add(Currency.getInstance("EUR")); 58 | curSet.add(Currency.getInstance("GBP")); 59 | curSet.add(Currency.getInstance("JPY")); 60 | curSet.add(Currency.getInstance("NZD")); 61 | curSet.add(Currency.getInstance("USD")); 62 | Instrument instrument; 63 | for (Currency curr : curSet) { 64 | if (!curr.equals(this.ACCOUNTCURRENCY)) { 65 | instrument = getPair(curr, this.ACCOUNTCURRENCY); 66 | this.pairs.put(curr, instrument); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * Get the Instrument given two Currencies 73 | * 74 | @param first a currency in a pair 75 | * 76 | @param second the other currency in a pair 77 | * 78 | @return an Instrument with the correct base/counter currencies order 79 | * 80 | **/ 81 | private Instrument getPair(Currency first, Currency second) { 82 | String pair; 83 | pair = first.toString() + Instrument.getPairsSeparator() + 84 | second.toString(); 85 | if (Instrument.isInverted(pair)) 86 | pair = second.toString() + Instrument.getPairsSeparator() + 87 | first.toString(); 88 | return Instrument.fromString(pair); 89 | } 90 | 91 | /** 92 | * Get the latest bid price of an instrument 93 | * 94 | @param instrument the instrument to lookup 95 | * 96 | @return latest tick bid price 97 | * 98 | **/ 99 | private double getPrice(Instrument instrument) { 100 | double price; 101 | try { 102 | price = this.history.getLastTick(instrument).getBid(); 103 | } 104 | catch (JFException ex) { 105 | price = Double.NaN; 106 | Logging.printErr(getContext().getConsole(), "Cannot get price.", ex); 107 | } 108 | return price; 109 | } 110 | 111 | /** 112 | * Subscribe to transitional instruments for converting profit/loss 113 | * to account currency 114 | * 115 | @param instSet set of instruments to be traded 116 | * 117 | **/ 118 | public void subscribeTransitionalInstruments(Set instSet) { 119 | Currency firstCurr, secondCurr; 120 | Set subscribeSet = 121 | new HashSet(getContext().getSubscribedInstruments()); 122 | 123 | for (Instrument instrument : instSet) { 124 | firstCurr = instrument.getPrimaryCurrency(); 125 | secondCurr = instrument.getSecondaryCurrency(); 126 | if (!firstCurr.equals(this.ACCOUNTCURRENCY) && 127 | !secondCurr.equals(this.ACCOUNTCURRENCY)) 128 | { 129 | subscribeSet.add(pairs.get(secondCurr)); // transitional pair 130 | } 131 | } 132 | getContext().setSubscribedInstruments(subscribeSet); 133 | } 134 | 135 | /** 136 | * Calculate the risked amount in home currency per unit traded of an instrument 137 | * 138 | @param instrument the instrument traded 139 | * 140 | @param stopSize the stop size to use for calculation 141 | * 142 | @return the risked amount per unit traded with given stop size 143 | **/ 144 | private double getAccountRiskPerUnit(Instrument instrument, double stopSize) { 145 | double transitionalPrice; 146 | Instrument transitionalInstrument; 147 | double riskInitialCurrency = stopSize; 148 | 149 | if (instrument.getSecondaryCurrency().equals(this.ACCOUNTCURRENCY)) { 150 | // If second currency in the instrument is account currency, 151 | // then risk is equal amount difference 152 | return riskInitialCurrency; 153 | } else if (instrument.getPrimaryCurrency().equals(this.ACCOUNTCURRENCY)) { 154 | return riskInitialCurrency / getPrice(instrument); 155 | } else { 156 | transitionalInstrument = pairs.get(instrument.getSecondaryCurrency()); 157 | transitionalPrice = getPrice(transitionalInstrument); 158 | if (transitionalInstrument.getSecondaryCurrency().equals(this.ACCOUNTCURRENCY)) 159 | return riskInitialCurrency * transitionalPrice; 160 | else 161 | return riskInitialCurrency / transitionalPrice; 162 | } 163 | } 164 | 165 | /** 166 | * Update account information object and max equity, call in onAccount(). 167 | * 168 | **/ 169 | public void update(IAccount account) { 170 | this.account = account; 171 | updateMaxEquity(); 172 | } 173 | 174 | /** 175 | * Get lot size given size of stop and preferred risk percentage 176 | * 177 | @param instrument the instrument traded 178 | * 179 | @param stopSize the stop size to use for calculation 180 | * 181 | @param riskPct a risk percentage in range of (0, 1] 182 | * 183 | @return a suggested lot size in millions 184 | **/ 185 | public double getLot(Instrument instrument, 186 | double stopSize, double riskPct) 187 | { 188 | return getPartLot(instrument, stopSize, riskPct, 1); 189 | } 190 | 191 | /** 192 | * Get lot size divided by number of parts 193 | * given size of stop and preferred risk percentage. 194 | * For use with splitting a position into multiple trades, such that 195 | * the total risk is less than the given risk percentage. 196 | * 197 | @param instrument the instrument traded 198 | * 199 | @param stopSize the stop size to use for calculation 200 | * 201 | @param riskPct a risk percentage in range of (0, 1] 202 | * 203 | @param parts number of parts of the position 204 | * 205 | @return a suggested partitioned lot size in millions 206 | **/ 207 | public double getPartLot(Instrument instrument, 208 | double stopSize, double riskPct, int parts) 209 | { 210 | double riskAmount, lotSize; 211 | double equity = this.account.getEquity(); 212 | riskAmount = equity * riskPct; 213 | lotSize = riskAmount / getAccountRiskPerUnit(instrument, stopSize); 214 | lotSize /= 1e6; // in millions for JForex API 215 | 216 | lotSize /= parts; 217 | 218 | return Rounding.lot(lotSize); 219 | } 220 | 221 | /** 222 | * 223 | * @return account equity 224 | * @see IAccount#getEquity() 225 | */ 226 | public double getEquity() { 227 | return this.account.getEquity(); 228 | } 229 | 230 | /** 231 | * @return the maxEquity 232 | */ 233 | public double getMaxEquity() { 234 | return maxEquity; 235 | } 236 | 237 | private void updateMaxEquity() { 238 | if(getEquity() > this.maxEquity) 239 | this.maxEquity = getEquity(); 240 | } 241 | 242 | /** 243 | * 244 | * @return current drawdown in negative percentage, positive means profitable 245 | */ 246 | public double getDrawdown() { 247 | return 1 - getEquity()/this.maxEquity; 248 | } 249 | 250 | /** 251 | * 252 | * @param maxDrawdown maximum drawdown in percent decimal, [0.0, 1.0]. 253 | * For example, max drawdown of 5% should be entered as 0.05 254 | * 255 | * @return true if max drawdown is reached 256 | */ 257 | public boolean isMaxDrawdownBroken(double maxDrawdown) { 258 | if (maxDrawdown < 0d || maxDrawdown > 1d) { 259 | throw new IllegalArgumentException("maxDrawdown must be [0.0, 1.0]"); 260 | } 261 | updateMaxEquity(); 262 | return (getDrawdown() < -maxDrawdown); 263 | } 264 | 265 | /** 266 | * 267 | * @return context object 268 | */ 269 | protected IContext getContext() { 270 | return context; 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Orderer.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.Callable; 5 | import java.util.concurrent.ExecutionException; 6 | import java.util.concurrent.Future; 7 | 8 | import com.dukascopy.api.*; 9 | import com.dukascopy.api.IOrder.State; 10 | 11 | /** 12 | * Executing orders in separate threads 13 | * 14 | */ 15 | public enum Orderer { 16 | INSTANCE; 17 | 18 | /** 19 | * Set a trail step for an order 20 | * 21 | @param order the order to be changed 22 | * 23 | @param trailStep the trail step size in pips, greater than 10 24 | * 25 | */ 26 | public static void setTrailStep(IOrder order, double trailStep) 27 | { // TODO use OrderTicket 28 | (new Thread(INSTANCE.new TrailStepTask(order, trailStep))).start(); 29 | } 30 | 31 | /** 32 | * Set a trail step for a {@link Future} order 33 | * 34 | @param future the Future order to be changed 35 | * 36 | @param trailStep the trail step size in pips, greater than 10 37 | * 38 | */ 39 | public static void setTrailStep(Future future, double trailStep) 40 | { 41 | (new Thread(INSTANCE.new TrailStepTask(future, trailStep))).start(); 42 | } 43 | 44 | /** 45 | * Set the stop loss and trail step of an order in a new Thread 46 | * 47 | @param order the order to be changed 48 | * 49 | @param newStop new stop loss price 50 | * 51 | @param trailStep trailing step in pips, must be greater than 10 52 | * 53 | @see IOrder#setStopLossPrice(double, OfferSide, double) 54 | * 55 | */ 56 | public static Future setStopLoss(IOrder order, double newStop, 57 | double trailStep) { 58 | StopTask task = INSTANCE.new StopTask(order, newStop, trailStep); 59 | return JForexContext.getContext().executeTask(task); 60 | } 61 | 62 | /** 63 | * Set the stop loss and trail step of an order in a new Thread 64 | * 65 | @param future the future order to be changed 66 | * 67 | @param newStop new stop loss price 68 | * 69 | @param trailStep trailing step in pips, must be greater than 10 70 | * 71 | @see IOrder#setStopLossPrice(double, OfferSide, double) 72 | * 73 | */ 74 | public static Future setStopLoss(Future future, double newStop, 75 | double trailStep) { 76 | StopTask task = INSTANCE.new StopTask(future, newStop, trailStep); 77 | return JForexContext.getContext().executeTask(task); 78 | } 79 | 80 | /** 81 | * Send an order in its own separate thread 82 | * 83 | * @param ticket encapsulated order information 84 | * 85 | * @see IEngine#submitOrder(String, Instrument, com.dukascopy.api.IEngine.OrderCommand, double, double, double, double, double, long, String) 86 | **/ 87 | public static Future placeOrder(OrderTicket ticket) 88 | { 89 | OrderTask task = INSTANCE.new OrderTask(ticket); 90 | return JForexContext.getContext().executeTask(task); 91 | } 92 | 93 | /** 94 | * Close an order at market price 95 | * 96 | @param order the order to be closed 97 | */ 98 | public static void close(IOrder order) throws JFException { 99 | close(order , 1d); 100 | } 101 | 102 | /** 103 | * Partially close an order at market price 104 | * 105 | @param order the order to be closed 106 | * 107 | @param percentage the percentage of the amount to be closed, 108 | * value (0, 1] 109 | */ 110 | public static void close(IOrder order, double percentage) throws JFException { 111 | if (percentage <= 0d || percentage > 1d) { 112 | throw new IllegalArgumentException("percentage must in range (0, 1]"); 113 | } 114 | else if (percentage == 1d) { // close full amount 115 | order.close(); 116 | } 117 | else { // close partial amount 118 | order.close(Rounding.lot(order.getAmount() * percentage)); 119 | } 120 | } 121 | 122 | 123 | /** 124 | * Get a list of orders for the particular instrument 125 | * 126 | @param instrument the instrument of which orders are to be fetched 127 | * 128 | @return a list of orders for the instrument 129 | */ 130 | public static List getOrders(Instrument instrument) throws JFException { 131 | return JForexContext.getEngine().getOrders(instrument); 132 | } 133 | 134 | /** 135 | * Get a list of orders for all instruments 136 | * 137 | @return a list of orders 138 | */ 139 | public static List getOrders() throws JFException { 140 | return JForexContext.getEngine().getOrders(); 141 | } 142 | 143 | private class TrailStepTask implements Runnable { 144 | private Future future; 145 | private IOrder order; 146 | private double trailStep; 147 | 148 | public TrailStepTask(Future future, double trailStep) 149 | { 150 | this.future = future; 151 | this.trailStep = trailStep; 152 | } 153 | 154 | public TrailStepTask(IOrder order, double trailStep) 155 | { 156 | this.order = order; 157 | this.trailStep = trailStep; 158 | } 159 | 160 | @Override 161 | public void run() { 162 | if (future != null) { 163 | try { 164 | order = future.get(); // wait for order 165 | } catch (InterruptedException ex) { 166 | Printer.printErr("TrailStepTask interrupted.", ex); 167 | 168 | } catch (ExecutionException ex) { 169 | Printer.printErr("TrailStepTask cannot execute.", ex); 170 | } 171 | } 172 | 173 | // set trailing step only if trailStep is >= 10d and 174 | // there is no trailing step in order already 175 | if (trailStep < 10d || order.getTrailingStep() != 0d) 176 | return; 177 | 178 | // TODO overcome "change to same stop loss price warning" 179 | setStopLoss(order, order.getStopLossPrice(), this.trailStep); 180 | } 181 | } 182 | 183 | /** 184 | * Inner class for sending stop order in a Callable thread 185 | * 186 | */ 187 | private class StopTask implements Callable { 188 | private IOrder order; 189 | private Future future; 190 | private double newStop, trailStep; 191 | 192 | /** 193 | * Construct a StopTask 194 | * 195 | @param order the order to be updated 196 | * 197 | @param newStop the price of the new stop loss 198 | * 199 | @param trailStep step size of the trailing order 200 | * 201 | @see IOrder#setStopLossPrice(double, OfferSide, double) 202 | */ 203 | public StopTask(IOrder order, double newStop, double trailStep) 204 | { 205 | this.order = order; 206 | this.newStop = newStop; 207 | this.trailStep = trailStep; 208 | } 209 | 210 | public StopTask(Future future, double newStop, double trailStep) 211 | { 212 | this.future = future; 213 | this.newStop = newStop; 214 | this.trailStep = trailStep; 215 | } 216 | 217 | public IOrder call() { 218 | if (future != null) { 219 | try { 220 | this.order = this.future.get(); // wait for order 221 | } catch (InterruptedException ex) { 222 | Printer.printErr("TrailStepTask interrupted.", ex); 223 | 224 | } catch (ExecutionException ex) { 225 | Printer.printErr("TrailStepTask cannot execute.", ex); 226 | } 227 | } 228 | 229 | for (int i = 0; i < 10; i++) { // wait for order in correct state 230 | if (order.getState() != State.FILLED && order.getState() != State.OPENED) { 231 | order.waitForUpdate(1000); 232 | } 233 | else break; 234 | } 235 | 236 | OfferSide side = order.isLong() ? OfferSide.BID : OfferSide.ASK; 237 | 238 | this.newStop = Rounding.pip(order.getInstrument(), newStop); 239 | this.trailStep = Math.round(this.trailStep); 240 | 241 | try { 242 | // TODO check if order is in Filled state? 243 | order.setStopLossPrice(newStop, side, trailStep); 244 | order.waitForUpdate(1000); 245 | } 246 | catch (JFException ex) { 247 | Printer.printErr(order.getLabel() + "-- couldn't set newStop: " + 248 | newStop + ", trailStep: " + trailStep, ex); 249 | return null; 250 | } 251 | return order; 252 | } 253 | } 254 | 255 | /** 256 | * Inner class for sending buy order in a Callable thread 257 | * 258 | */ 259 | private class OrderTask implements Callable{ 260 | private OrderTicket ticket; 261 | 262 | /** 263 | * Construct a BuyTask 264 | * 265 | @param instrument the purchasing instrument 266 | * 267 | @param amount the amount to buy, in millions, minimum 0.001 268 | * 269 | @param stopLossPrice stop loss price, 0 for no stop 270 | * 271 | @param targetPrice target price, 0 for no target 272 | * 273 | */ 274 | public OrderTask(OrderTicket ticket) 275 | { 276 | this.ticket = ticket; 277 | } 278 | 279 | public IOrder call() { 280 | IOrder order; 281 | try { 282 | order = JForexContext.getEngine().submitOrder( 283 | ticket.getLabel(), 284 | ticket.getInstrument(), 285 | ticket.getOrderCmd(), 286 | ticket.getLot(), 287 | ticket.getPrice(), 288 | ticket.getSlippage(), 289 | ticket.getStopLossPrice(), 290 | ticket.getTakeProfitPrice(), 291 | ticket.getGoodTillTime(), 292 | ticket.getComment()); 293 | } 294 | catch (JFException ex) { 295 | Printer.printErr(ticket.getLabel() + " -- cannot place order.", ex); 296 | return null; 297 | } 298 | return order; 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/IndicatorBean/Indicating.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil.IndicatorBean; 2 | 3 | import java.io.File; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import com.dukascopy.api.Filter; 8 | import com.dukascopy.api.IBar; 9 | import com.dukascopy.api.IContext; 10 | import com.dukascopy.api.IHistory; 11 | import com.dukascopy.api.IIndicators; 12 | import com.dukascopy.api.IIndicators.AppliedPrice; 13 | import com.dukascopy.api.ITick; 14 | import com.dukascopy.api.Instrument; 15 | import com.dukascopy.api.JFException; 16 | import com.dukascopy.api.OfferSide; 17 | import com.dukascopy.api.Period; 18 | import com.dukascopy.api.indicators.IIndicator; 19 | import com.quantisan.JFUtil.JForexContext; 20 | import com.quantisan.JFUtil.Printer; 21 | 22 | /** 23 | * Accessing technical analysis indicators 24 | * 25 | */ 26 | public class Indicating { 27 | private static final Indicating INSTANCE = new Indicating(); 28 | private Filter filter = Filter.NO_FILTER; 29 | private Set singleArrayOutputs = new HashSet(); 30 | private Set multiArrayOutputs = new HashSet(); 31 | 32 | private Indicating() { 33 | singleArrayOutputs.add("ATR"); 34 | singleArrayOutputs.add("ULTOSC"); 35 | singleArrayOutputs.add("RSI"); 36 | singleArrayOutputs.add("MA"); 37 | 38 | multiArrayOutputs.add("STOCH"); 39 | multiArrayOutputs.add("STOCHF"); 40 | multiArrayOutputs.add("STOCHRSI"); 41 | multiArrayOutputs.add("MACD"); 42 | }; 43 | 44 | /** 45 | * Register a custom indicator function name as outputting a single array. 46 | * Call immediately after {@link Indicating#registerCustomIndicator(File)} 47 | * 48 | * @param functionName 49 | */ 50 | public static void registerSingleArrayOutputs(String functionName) { 51 | INSTANCE.singleArrayOutputs.add(functionName); 52 | } 53 | 54 | /** 55 | * Register a custom indicator function name as outputting a N-dimensional array. 56 | * Call immediately after {@link Indicating#registerCustomIndicator(File)} 57 | * 58 | * @param functionName 59 | */ 60 | public static void registerMultiArrayOutputs(String functionName) { 61 | INSTANCE.multiArrayOutputs.add(functionName); 62 | } 63 | 64 | public static double[] calculate(Instrument instrument, Period period, 65 | AbstractIndicatorBean indicatorBean, int dataPoints) throws JFException 66 | { 67 | // check if this is the function returns a 1-D array 68 | if (!INSTANCE.singleArrayOutputs.contains(indicatorBean.getFunctionName())) 69 | { 70 | throw new IllegalArgumentException(indicatorBean.getFunctionName() 71 | + " does not return a 1-dimensional array"); 72 | } 73 | 74 | IBar bar = JForexContext.getHistory().getBar(instrument, period, OfferSide.BID, 1); 75 | Object[] objs = JForexContext.getIndicators() 76 | .calculateIndicator(instrument, 77 | period, 78 | indicatorBean.getOfferSide(), 79 | indicatorBean.getFunctionName(), 80 | indicatorBean.getInputTypes(), 81 | indicatorBean.getParams(), 82 | getFilter(), 83 | dataPoints, 84 | bar.getTime(), 85 | 0); 86 | return (double[])objs[0]; 87 | } 88 | 89 | public static double calculate(Instrument instrument, Period period, 90 | AbstractIndicatorBean indicatorBean) throws JFException { 91 | return calculate(instrument, period, indicatorBean, 1)[0]; 92 | } 93 | 94 | /** 95 | * BETA: not a good design, expect changes to this method interface 96 | * 97 | * @param instrument 98 | * @param period 99 | * @param indicatorBean 100 | * @param dataPoints 101 | * @return 102 | * @throws JFException 103 | */ 104 | public static Object[] calculateMultiDimension(Instrument instrument, Period period, 105 | AbstractIndicatorBean indicatorBean, int dataPoints) throws JFException 106 | { 107 | // check if this is the function returns a 1-D array 108 | if (!INSTANCE.multiArrayOutputs.contains(indicatorBean.getFunctionName())) 109 | { 110 | throw new IllegalArgumentException(indicatorBean.getFunctionName() 111 | + " does not return a 2-dimensional array"); 112 | } 113 | 114 | IBar bar = JForexContext.getHistory().getBar(instrument, period, OfferSide.BID, 1); 115 | Object[] objs = JForexContext.getIndicators() 116 | .calculateIndicator(instrument, 117 | period, 118 | indicatorBean.getOfferSide(), 119 | indicatorBean.getFunctionName(), 120 | indicatorBean.getInputTypes(), 121 | indicatorBean.getParams(), 122 | getFilter(), 123 | dataPoints, 124 | bar.getTime(), 125 | 0); 126 | return objs; 127 | // double[][] output = new double[2][]; 128 | // output[0] = (double[])objs[0]; 129 | // output[1] = (double[])objs[1]; 130 | // return output; 131 | } 132 | 133 | 134 | 135 | /** 136 | * Register a custom indicator in the system before using it. Preferably 137 | * run this in onStart for all your custom indicators. 138 | * 139 | @param file full path to the indicator .jfx file or just the filename 140 | * if file is inside directory of {@link IContext#getFilesDir()} 141 | * 142 | */ 143 | public static void registerCustomIndicator(File file) throws JFException { 144 | JForexContext.getIndicators().registerCustomIndicator(file); 145 | } 146 | 147 | public static void registerCustomIndicator(Class indicatorClass) 148 | throws JFException 149 | { 150 | JForexContext.getIndicators().registerCustomIndicator(indicatorClass); 151 | } 152 | 153 | /** 154 | * Set the type of chart filter to use. 155 | * 156 | @param filter type of chart filter. 157 | * 158 | */ 159 | public static void setFilter(Filter filter) { 160 | INSTANCE.filter = filter; 161 | } 162 | 163 | /** 164 | * Get the type of chart filter being used. 165 | * 166 | @return type of chart filter 167 | * 168 | */ 169 | public static Filter getFilter() { 170 | return INSTANCE.filter; 171 | } 172 | 173 | 174 | @Deprecated private IIndicators indicators; 175 | @Deprecated private IHistory history; 176 | @Deprecated private OfferSide offerSide; 177 | @Deprecated private IContext context; 178 | 179 | 180 | /** 181 | * Constructor 182 | * 183 | @param context IContext for accessing JForex API functions 184 | * 185 | */ 186 | @Deprecated public Indicating(IContext context) { 187 | this.context = context; 188 | this.indicators = context.getIndicators(); 189 | this.history = context.getHistory(); 190 | this.offerSide = OfferSide.BID; 191 | this.filter = Filter.WEEKENDS; 192 | } 193 | 194 | @Deprecated protected IContext getContext() { 195 | return context; 196 | } 197 | 198 | /** 199 | * Get the latest indicator results using default parameters 200 | * 201 | * 202 | @param instrument the instrument 203 | * 204 | @param period the period to calculate the indicator 205 | * 206 | @param functionName name of the indicator 207 | * 208 | @param dataPoints number of data points to get 209 | * 210 | 211 | @return an array of indicator results 212 | */ 213 | @Deprecated public double[] getIndicatorResult(Instrument instrument, Period period, 214 | String functionName, int dataPoints) 215 | { 216 | AppliedPrice[] inputTypeArray; 217 | OfferSide[] offerSides; 218 | 219 | Object[] optParams; 220 | 221 | // set default parameters 222 | if (functionName.equals("ULTOSC")) { 223 | offerSides = new OfferSide[] {OfferSide.BID}; 224 | inputTypeArray = null; 225 | optParams = new Integer[]{7, 14, 28}; 226 | } 227 | else if (functionName.equals("ATR")) { 228 | offerSides = new OfferSide[] {OfferSide.BID}; 229 | inputTypeArray = null; 230 | optParams = new Integer[]{14}; 231 | } 232 | else { 233 | offerSides = new OfferSide[] {OfferSide.BID}; 234 | inputTypeArray = new AppliedPrice[] {IIndicators.AppliedPrice.CLOSE}; 235 | optParams = new Integer[]{20}; 236 | } 237 | 238 | // note for multi value indicator, .e.g with 4 outputs 239 | // http://www.dukascopy.com/swiss/english/forex/jforex/forum/viewtopic.php?f=5&t=24358 240 | // double aRsi = ((double[])aArr[0])[0]; 241 | // double aMfi = ((double[])aArr[1])[0]; 242 | // double aStoch = ((double[])aArr[2])[0]; 243 | // double aAvg = ((double[])aArr[3])[0]; 244 | 245 | return getIndicatorResult(instrument, period, functionName, offerSides, 246 | inputTypeArray, optParams, dataPoints); 247 | } 248 | 249 | /** 250 | * Get the latest indicator results using custom parameters 251 | * 252 | * 253 | @param instrument the instrument 254 | * 255 | @param period the period to calculate the indicator 256 | * 257 | @param functionName name of the indicator 258 | * 259 | @param inputTypes an array of AppliedPrice setting price data 260 | * 261 | @param params an array of Objects setting the parameters to the indicator 262 | * 263 | @param dataPoints number of data points to get 264 | * 265 | @return an array of indicator results 266 | * 267 | @see IIndicators#calculateIndicator(Instrument, Period, OfferSide[], String, AppliedPrice[], Object[], Filter, int, long, int) 268 | */ 269 | @Deprecated public double[] getIndicatorResult(Instrument instrument, Period period, 270 | String functionName, OfferSide[] offerSides, 271 | AppliedPrice[] inputTypes, Object[] params, int dataPoints) 272 | { 273 | IBar bar; 274 | try { 275 | bar = this.history.getBar(instrument, period, this.offerSide, 1); 276 | } catch (JFException ex) { 277 | Printer.printErr("Cannot get bar history", ex); 278 | return null; 279 | } 280 | 281 | Object[] objs; 282 | try { 283 | objs = this.indicators.calculateIndicator(instrument, 284 | period, 285 | offerSides, 286 | functionName, 287 | inputTypes, 288 | params, 289 | filter, 290 | dataPoints, 291 | bar.getTime(), 292 | 0); 293 | } catch (JFException ex) { 294 | Printer.printErr("Cannot calculate indicator", ex); 295 | return null; 296 | } 297 | 298 | return (double[])objs[0]; 299 | } 300 | 301 | /** 302 | * Get the most recent completed bar 303 | * 304 | @param instrument the instrument 305 | * 306 | @param period the period 307 | * 308 | @param offerSide bid or ask bar using OfferSide.BID or OfferSide.ASK 309 | * 310 | @return the last completed bid/ask bar 311 | * 312 | @see IHistory#getBar(Instrument, Period, OfferSide, int) 313 | */ 314 | @Deprecated public IBar getLastBar(Instrument instrument, Period period, OfferSide offerSide) { 315 | IBar bar; 316 | 317 | try { 318 | bar = this.history.getBar(instrument, period, offerSide, 1); 319 | } catch (JFException ex) { 320 | Printer.printErr("Cannot load bar history", ex); 321 | return null; 322 | } 323 | return bar; 324 | } 325 | 326 | /** 327 | * Get the last tick price of an instrument 328 | * 329 | @param instrument the instrument to lookup 330 | * 331 | @param offerSide bid or ask 332 | * 333 | @return latest tick price 334 | * 335 | **/ 336 | @Deprecated public double getLastPrice(Instrument instrument, OfferSide offerSide) { 337 | double price; 338 | try { 339 | if (offerSide == OfferSide.BID) 340 | price = this.history.getLastTick(instrument).getBid(); 341 | else 342 | price = this.history.getLastTick(instrument).getAsk(); 343 | } 344 | catch (JFException ex) { 345 | price = Double.NaN; 346 | Printer.printErr("Cannot get price.", ex); 347 | } 348 | return price; 349 | } 350 | 351 | /** 352 | * Get the last Tick of an instrument 353 | * 354 | @param instrument the instrument to lookup 355 | * 356 | @return latest tick 357 | * 358 | @see IHistory#getLastTick(Instrument) 359 | **/ 360 | @Deprecated public ITick getLastTick(Instrument instrument) throws JFException { 361 | ITick tick; 362 | try { 363 | tick = JForexContext.getHistory().getLastTick(instrument); 364 | } 365 | catch (JFException ex) { 366 | tick = null; 367 | Printer.printErr("Cannot get price.", ex); 368 | } 369 | return tick; 370 | } 371 | 372 | /** 373 | * Get the strategy write-accessible JForex files directory 374 | * 375 | @return directory path 376 | * 377 | @see IContext#getFilesDir() 378 | */ 379 | @Deprecated public String getFilesDir() { 380 | return this.context.getFilesDir().toString(); 381 | } 382 | 383 | /** 384 | * Set the offer side used. 385 | * 386 | @param ofs OfferSide.BID or OfferSide.ASK 387 | * 388 | */ 389 | @Deprecated public void setOfferSide(OfferSide ofs) { 390 | this.offerSide = ofs; 391 | } 392 | 393 | /** 394 | * Get the offer side being used. 395 | * 396 | @return offer side, bid or ask 397 | * 398 | */ 399 | @Deprecated public OfferSide getOfferSide() { 400 | return offerSide; 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/com/quantisan/JFUtil/Ordering.java: -------------------------------------------------------------------------------- 1 | package com.quantisan.JFUtil; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Calendar; 5 | import java.util.List; 6 | import java.util.TimeZone; 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Future; 10 | 11 | import com.dukascopy.api.*; 12 | import com.dukascopy.api.IOrder.State; 13 | 14 | /** 15 | * Executing orders in separate threads 16 | * 17 | * @deprecated use {@link Orderer} 18 | */ 19 | @Deprecated public class Ordering { 20 | private IContext context; 21 | private IEngine engine; 22 | private int counter; 23 | private int slippage; 24 | 25 | /** 26 | * Constructor 27 | * 28 | @param context context of the strategy 29 | * 30 | @param slippage slippage to use 31 | * 32 | */ 33 | public Ordering(IContext context, int slippage) { 34 | this.context = context; 35 | this.engine = context.getEngine(); 36 | setSlippage(slippage); 37 | this.counter = 0; 38 | } 39 | 40 | /** 41 | * Set a trail step for an order 42 | * 43 | @param order the order to be changed 44 | * 45 | @param trailStep the trail step size in pips, greater than 10 46 | * 47 | */ 48 | public void setTrailStep(IOrder order, double trailStep) 49 | { 50 | (new Thread(new TrailStepTask(order, trailStep))).start(); 51 | } 52 | 53 | /** 54 | * Set a trail step for a Future order 55 | * 56 | @param future the Future order to be changed 57 | * 58 | @param trailStep the trail step size in pips, greater than 10 59 | * 60 | */ 61 | public void setTrailStep(Future future, double trailStep) 62 | { 63 | (new Thread(new TrailStepTask(future, trailStep))).start(); 64 | } 65 | 66 | /** 67 | * Check to see if there is any risk exposure 68 | * 69 | * 70 | @param orders a list of orders to scan 71 | * 72 | 73 | @return true if there is exposed risk, false is not 74 | */ 75 | public static boolean checkExposed(List orders) { 76 | return !(getExposed(orders).isEmpty()); // no exposed position 77 | } 78 | 79 | /** 80 | * Get a list of opened positions that have a risk exposure, 81 | * i.e. stop price below opening price for long, and vice versa 82 | * 83 | * 84 | @param orders a list of orders to scan 85 | * 86 | 87 | @return a list of risk-exposed filled orders 88 | */ 89 | public static List getExposed(List orders) { 90 | List outList = new ArrayList(); 91 | boolean isLong; 92 | double diff, stop; 93 | for (IOrder order : orders) { 94 | if (order.getState() == IOrder.State.FILLED) { 95 | isLong = order.isLong(); 96 | stop = order.getStopLossPrice(); 97 | diff = stop - order.getOpenPrice(); 98 | diff *= isLong ? 1d : -1d; 99 | if (diff < 0d || stop == 0d) 100 | outList.add(order); 101 | } 102 | } // end FOR loop 103 | return outList; 104 | } 105 | 106 | /** 107 | * Send a market order in its own thread 108 | * 109 | * @param prefix a prefix for the order label 110 | * 111 | * @param suffix a suffix for the order label 112 | * 113 | * @param instrument the purchasing instrument 114 | * 115 | * @param orderCmd BUY or SELL 116 | * 117 | * @param amount the amount to buy, in millions, minimum 0.001 118 | * 119 | * @param stopLossSize size of the stop loss, in scale of instrument 120 | * 121 | * @see #placeMarketOrder(String, String, Instrument, 122 | * com.dukascopy.api.IEngine.OrderCommand, double, double, double, double) 123 | **/ 124 | public Future placeMarketOrder(String prefix, String suffix, 125 | Instrument instrument, 126 | IEngine.OrderCommand orderCmd, 127 | double amount, 128 | double stopLossSize) 129 | { 130 | double price = Double.NaN; 131 | try { 132 | if (orderCmd == IEngine.OrderCommand.SELL) 133 | price = getContext().getHistory().getLastTick(instrument).getBid(); 134 | else if (orderCmd == IEngine.OrderCommand.BUY) 135 | price = getContext().getHistory().getLastTick(instrument).getAsk(); 136 | } 137 | catch(JFException ex) { 138 | Logging.printErr(getContext().getConsole(), "Cannot get price.", ex); 139 | return null; 140 | } 141 | double stopLossPrice = price + stopLossSize; 142 | return placeMarketOrder(prefix, suffix, instrument, orderCmd, amount, stopLossPrice, 0d); 143 | } 144 | 145 | /** 146 | * Send a market order in its own thread 147 | * 148 | * @param prefix a prefix for the order label 149 | * 150 | * @param suffix a suffix for the order label 151 | * 152 | * @param instrument the purchasing instrument 153 | * 154 | * @param orderCmd BUY or SELL 155 | * 156 | * @param amount the amount to buy, in millions, minimum 0.001 157 | * 158 | * @param stopLossPrice stop loss price, 0 for no stop 159 | * 160 | * @param targetPrice target price, 0 for no target 161 | * 162 | * @see IEngine#submitOrder(String, Instrument, 163 | * com.dukascopy.api.IEngine.OrderCommand, double, double, double, double, double) 164 | **/ 165 | public Future placeMarketOrder(String prefix, String suffix, 166 | Instrument instrument, 167 | IEngine.OrderCommand orderCmd, 168 | double amount, 169 | double stopLossPrice, double targetPrice) 170 | { // TODO add label prefix and suffix param 171 | MarketOrderTask task = 172 | new MarketOrderTask(prefix, suffix, 173 | instrument, orderCmd, 174 | amount, stopLossPrice, targetPrice); 175 | return getContext().executeTask(task); 176 | } 177 | 178 | /** 179 | * Send a market order in its own thread 180 | * 181 | * @param prefix a prefix for the order label 182 | * 183 | * @param suffix a suffix for the order label 184 | * 185 | * @param instrument the purchasing instrument 186 | * 187 | * @param orderCmd BUY or SELL 188 | * 189 | * @param amount the amount to buy, in millions, minimum 0.001 190 | * 191 | * @param stopLossPrice stop loss price, 0 for no stop 192 | * 193 | * @param trailStep sets a trailing step to the order 194 | * 195 | * @param targetPrice target price, 0 for no target 196 | * 197 | * @see IEngine#submitOrder(String, Instrument, 198 | * com.dukascopy.api.IEngine.OrderCommand, double, double, double, double, double) 199 | **/ 200 | public Future placeMarketOrder(String prefix, String suffix, 201 | Instrument instrument, 202 | IEngine.OrderCommand orderCmd, 203 | double amount, 204 | double stopLossPrice, 205 | double trailStep, 206 | double targetPrice) 207 | { 208 | Future future = placeMarketOrder(prefix, suffix, 209 | instrument, orderCmd, 210 | amount, stopLossPrice, targetPrice); 211 | if (trailStep != 0d) { 212 | // creates new thread and wait for order to set trailstep 213 | setTrailStep(future, trailStep); 214 | } 215 | return future; 216 | } 217 | 218 | /** 219 | * Close an order at market price 220 | * 221 | @param order the order to be closed 222 | */ 223 | public void close(IOrder order) { 224 | close(order , 1d); 225 | } 226 | 227 | /** 228 | * Partially close an order at market price 229 | * 230 | @param order the order to be closed 231 | * 232 | @param percentage the percentage of the amount to be closed, 233 | * value (0, 1] 234 | */ 235 | public void close(IOrder order, double percentage) { 236 | if (percentage <= 0d || percentage > 1d) { 237 | throw new IllegalArgumentException("percentage must in range (0, 1]"); 238 | } 239 | else if (percentage == 1d) { // close all 240 | // TODO move to Callable 241 | try { 242 | order.close(); 243 | } 244 | catch (JFException ex) { 245 | Logging.printErr(getContext().getConsole(), "Cannot close order.", ex); 246 | } 247 | } 248 | else { 249 | try { 250 | order.close(Rounding.lot(order.getAmount() * percentage)); 251 | } 252 | catch (JFException ex) { 253 | Logging.printErr(getContext().getConsole(), "Cannot close order.", ex); 254 | } 255 | } 256 | } 257 | 258 | 259 | /** 260 | * Set the stop loss and trail step of an order in a new Thread 261 | * 262 | @param order the order to be changed 263 | * 264 | @param newStop new stop loss price 265 | * 266 | @param trailStep trailing step in pips, greater than 10 267 | * 268 | @see IOrder#setStopLossPrice(double, OfferSide, double) 269 | * 270 | */ 271 | public Future setStopLoss(IOrder order, double newStop, 272 | double trailStep) { 273 | StopTask task = new StopTask(order, newStop, trailStep); 274 | return getContext().executeTask(task); 275 | } 276 | 277 | /** 278 | * Get a list of orders for the particular instrument 279 | * 280 | @param instrument the instrument of which orders are to be fetched 281 | * 282 | @return a list of orders for the instrument 283 | */ 284 | public List getOrders(Instrument instrument) throws JFException { 285 | return this.engine.getOrders(instrument); 286 | } 287 | 288 | /** 289 | * Get a list of orders for all instruments 290 | * 291 | @return a list of orders 292 | */ 293 | public List getOrders() throws JFException { 294 | return this.engine.getOrders(); 295 | } 296 | 297 | /** 298 | * Returns a string for labelling an order, which is 299 | * = Strategy Tag + Day of Month + a counter. Override this method 300 | * for custom trade label. 301 | * 302 | @return a String label 303 | * 304 | @see java.lang.Override 305 | */ 306 | private String getLabel(String prefix, String suffix) { 307 | Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 308 | int day = calendar.get(Calendar.DAY_OF_MONTH); 309 | 310 | return (prefix + day + "d" + counter++ + suffix); 311 | } 312 | 313 | /** 314 | * @return the context 315 | * 316 | * @see IContext 317 | */ 318 | protected IContext getContext() { 319 | return context; 320 | } 321 | 322 | /** 323 | * @return the slippage 324 | */ 325 | public int getSlippage() { 326 | return slippage; 327 | } 328 | 329 | /** 330 | * @param slippage the slippage to set 331 | */ 332 | public void setSlippage(int slippage) { 333 | this.slippage = slippage; 334 | } 335 | 336 | /** 337 | * @return the counter 338 | */ 339 | public int getCounter() { 340 | return counter; 341 | } 342 | 343 | /** 344 | * @param counter the counter to set 345 | */ 346 | public void setCounter(int counter) { 347 | this.counter = counter; 348 | } 349 | 350 | /** 351 | * Reset the trade label counter to zero. 352 | * 353 | */ 354 | public void resetCounter() { 355 | setCounter(0); 356 | } 357 | 358 | private class TrailStepTask implements Runnable { 359 | private Future future; 360 | private IOrder order; 361 | private double trailStep; 362 | 363 | public TrailStepTask(Future future, double trailStep) 364 | { 365 | this.future = future; 366 | this.trailStep = trailStep; 367 | } 368 | 369 | public TrailStepTask(IOrder order, double trailStep) 370 | { 371 | this.order = order; 372 | this.trailStep = trailStep; 373 | } 374 | 375 | @Override 376 | public void run() { 377 | if (future != null) { 378 | try { 379 | order = future.get(); 380 | } catch (InterruptedException ex) { 381 | Logging.printErr(getContext().getConsole(), "TrailStepTask interrupted.", ex); 382 | 383 | } catch (ExecutionException ex) { 384 | Logging.printErr(getContext().getConsole(), "TrailStepTask cannot execute.", ex); 385 | } 386 | } 387 | 388 | // set trailing step only if trailStep is >= 10d and 389 | // there is no trailing step in order already 390 | if (trailStep < 10d || order.getTrailingStep() != 0d) 391 | return; 392 | 393 | for (int i = 0; i < 10; i++) { 394 | if (order.getState() != State.FILLED && order.getState() != State.OPENED) { 395 | order.waitForUpdate(1000); 396 | } 397 | else break; 398 | } 399 | 400 | // TODO how to overcome "change to same stop loss price warning" 401 | setStopLoss(order, order.getStopLossPrice(), this.trailStep); 402 | } 403 | } 404 | 405 | /** 406 | * Inner class for sending stop order in a Callable thread 407 | * 408 | */ 409 | private class StopTask implements Callable { 410 | private IOrder order; 411 | private double newStop, trailStep; 412 | 413 | /** 414 | * Construct a StopTask 415 | * 416 | @param order the order to be updated 417 | * 418 | @param newStop the price of the new stop loss 419 | * 420 | @param trailStep step size of the trailing order 421 | * 422 | @see IOrder#setStopLossPrice(double, OfferSide, double) 423 | */ 424 | public StopTask(IOrder order, double newStop, 425 | double trailStep) { 426 | this.order = order; 427 | this.newStop = newStop; 428 | this.trailStep = trailStep; 429 | } 430 | 431 | public IOrder call() { 432 | OfferSide side = order.isLong() ? OfferSide.BID : OfferSide.ASK; 433 | 434 | this.newStop = Rounding.pip(order.getInstrument(), newStop); 435 | this.trailStep = Math.round(this.trailStep); 436 | 437 | try { 438 | // TODO check if order is in Filled state? 439 | order.setStopLossPrice(newStop, side, trailStep); 440 | order.waitForUpdate(1000); 441 | } 442 | catch (JFException ex) { 443 | Logging.printErr(getContext().getConsole(), 444 | order.getLabel() + "-- couldn't set newStop: " + 445 | newStop + ", trailStep: " + trailStep, ex); 446 | return null; 447 | } 448 | return order; 449 | } 450 | } 451 | 452 | /** 453 | * Inner class for sending buy order in a Callable thread 454 | * 455 | */ 456 | private class MarketOrderTask implements Callable{ 457 | private Instrument instrument; 458 | private IEngine.OrderCommand orderCmd; 459 | private double stopLossPrice, targetPrice, amount; 460 | private String prefix, suffix; 461 | 462 | /** 463 | * Construct a BuyTask 464 | * 465 | @param instrument the purchasing instrument 466 | * 467 | @param amount the amount to buy, in millions, minimum 0.001 468 | * 469 | @param stopLossPrice stop loss price, 0 for no stop 470 | * 471 | @param targetPrice target price, 0 for no target 472 | * 473 | */ 474 | public MarketOrderTask(String prefix, String suffix, 475 | Instrument instrument, 476 | IEngine.OrderCommand orderCmd, double amount, 477 | double stopLossPrice, double targetPrice) 478 | { 479 | this.instrument = instrument; 480 | this.orderCmd = orderCmd; 481 | this.amount = amount; 482 | this.stopLossPrice = stopLossPrice; 483 | this.targetPrice = targetPrice; 484 | this.prefix = prefix; 485 | this.suffix = suffix; 486 | 487 | 488 | } 489 | 490 | public IOrder call() { 491 | IOrder order; 492 | String label = Ordering.this.getLabel(this.prefix, this.suffix); 493 | try { 494 | order = engine.submitOrder(label, 495 | this.instrument, this.orderCmd, 496 | Rounding.lot(this.amount), 0, Ordering.this.slippage, 497 | Rounding.pip(this.instrument, this.stopLossPrice), 498 | Rounding.pip(this.instrument, this.targetPrice)); 499 | } 500 | catch (JFException ex) { 501 | Logging.printErr(getContext().getConsole(), label + 502 | "-- cannot place market order.", ex); 503 | return null; 504 | } 505 | return order; 506 | } 507 | } 508 | } 509 | --------------------------------------------------------------------------------