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 extends IIndicator> 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 |
--------------------------------------------------------------------------------