/* =========================================================== * TradeManager : An application to trade strategies for the Java(tm) platform * =========================================================== * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Project Info: org.trade * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Oracle, Inc. * in the United States and other countries.] * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Original Author: Simon Allen; * Contributor(s): -; * * Changes * ------- * */ package org.trade.strategy; import java.time.ZonedDateTime; import java.util.Collections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trade.broker.BrokerModel; import org.trade.core.util.TradingCalendar; import org.trade.core.valuetype.Money; import org.trade.dictionary.valuetype.Action; import org.trade.dictionary.valuetype.OrderType; import org.trade.dictionary.valuetype.Side; import org.trade.persistent.dao.TradeOrder; import org.trade.strategy.data.CandleSeries; import org.trade.strategy.data.HeikinAshiDataset; import org.trade.strategy.data.HeikinAshiSeries; import org.trade.strategy.data.IndicatorSeries; import org.trade.strategy.data.StrategyData; import org.trade.strategy.data.candle.CandleItem; import org.trade.strategy.data.heikinashi.HeikinAshiItem; /** */ public class PosMgrHeikinAshiTrailStrategy extends AbstractStrategyRule { /** * * 1/ trail the open position using prev two Heikin-Ashi bars. i.e. STP * price is moved up to the Low of current Heikin-Ashi bar -2 as long as the * prev bars low is higher than the prev bar -1 low. * * 2/ Close any open positions at 15:58. * */ private static final long serialVersionUID = -6717691162128305191L; private final static Logger _log = LoggerFactory.getLogger(PosMgrHeikinAshiTrailStrategy.class); private static final Integer _hiekinAshiTrailStartR = 2; private static final Integer stopRiskUnits = 1; private static final Double stopAddAmount = 0.01; private static final Integer targetRiskUnits = 8; private static final Double targetAddAmount = 0.01; /** * Default Constructor Note if you use class variables remember these will * need to be initialized if the strategy is restarted i.e. if they are * created on startup under a constraint you must find a way to populate * that value if the strategy were to be restarted and the constraint is not * met. * * @param brokerManagerModel * BrokerModel * @param strategyData * StrategyData * @param idTradestrategy * Integer */ public PosMgrHeikinAshiTrailStrategy(BrokerModel brokerManagerModel, StrategyData strategyData, Integer idTradestrategy) { super(brokerManagerModel, strategyData, idTradestrategy); } /** * Method runStrategy. * * @param candleSeries * CandleSeries * @param newBar * boolean * @see org.trade.strategy.StrategyRule#runStrategy(CandleSeries, boolean) */ public void runStrategy(CandleSeries candleSeries, boolean newBar) { try { /* * Get the current candle */ CandleItem currentCandleItem = this.getCurrentCandle(); // AbstractStrategyRule.logCandle(this, // currentCandleItem.getCandle()); ZonedDateTime startPeriod = currentCandleItem.getPeriod().getStart(); /* * Get the current open trade. If no trade is open this Strategy * will be closed down. */ if (!this.isThereOpenPosition()) { _log.info("No open position so Cancel Strategy Symbol: " + getSymbol() + " Time: " + startPeriod); this.cancel(); return; } /* * If all trades are closed shut down the position manager * * Note this strategy is run as soon as we enter a position. * * Check to see if the open position is filled and the open quantity * is > 0 also check to see if we already have this position * covered. */ if (this.isThereOpenPosition() && !this.isPositionCovered()) { /* * Position has been opened and not covered submit the target * and stop orders for the open quantity. * * Make the stop -1R and manage to the Vwap MA of the opening * bar. */ Integer quantity = Math.abs(this.getOpenTradePosition().getOpenQuantity()); // Add a penny to the stop and target Money stopPrice = getRiskMultiplerPrice(stopRiskUnits, true, stopAddAmount, true); Money targetPrice = getRiskMultiplerPrice(targetRiskUnits, true, targetAddAmount, false); createStopAndTargetOrder(stopPrice, targetPrice, quantity, true); _log.info("Open position submit Stop/Tgt orders created Symbol: " + getSymbol() + " Time:" + startPeriod + " quantity: " + quantity + " targetPrice: " + targetPrice + " stopPrice: " + stopPrice); } /* * Manage the stop orders if the current bars Vwap crosses the Vwap * of the first 5min bar then move the stop price ( currently -2R) * to the average fill price i.e. break even. This allows for tails * that break the 5min high/low between 9:40 thru 15:30. */ if (startPeriod.isBefore(this.getTradestrategy().getTradingday().getClose().minusHours(30)) && startPeriod.isAfter(this.getTradestrategy().getTradingday().getOpen().plusMinutes(5))) { CandleItem firstCandle = this.getCandle( TradingCalendar.getDateAtTime(startPeriod, this.getTradestrategy().getTradingday().getOpen())); if (Side.BOT.equals(getOpenTradePosition().getSide())) { if (currentCandleItem.getVwap() < firstCandle.getVwap()) { Money stopPrice = addPennyAndRoundStop( this.getOpenPositionOrder().getAverageFilledPrice().doubleValue(), getOpenTradePosition().getSide(), Action.SELL, 0.01); moveStopOCAPrice(stopPrice, true); _log.info("Move Stop to b.e. Strategy Mgr Symbol: " + getSymbol() + " Time:" + startPeriod + " Price: " + stopPrice + " first bar Vwap: " + firstCandle.getVwap() + " Curr Vwap: " + currentCandleItem.getVwap()); } } else { if (currentCandleItem.getVwap() > firstCandle.getVwap()) { Money stopPrice = addPennyAndRoundStop( this.getOpenPositionOrder().getAverageFilledPrice().doubleValue(), getOpenTradePosition().getSide(), Action.BUY, 0.01); moveStopOCAPrice(stopPrice, true); _log.info("Move Stop to b.e. Strategy Mgr Symbol: " + getSymbol() + " Time:" + startPeriod + " Price: " + stopPrice + " first bar Vwap: " + firstCandle.getVwap() + " Curr Vwap: " + currentCandleItem.getVwap()); } } } /* * At 15:30 Move stop order to b.e. i.e. the average fill price of * the open order. */ if (startPeriod.equals(this.getTradestrategy().getTradingday().getClose().minusMinutes(30)) && newBar) { double avgFillPrice = (Math.abs(this.getOpenTradePosition().getTotalNetValue().doubleValue()) / Math.abs(this.getOpenTradePosition().getOpenQuantity())); String action = Action.SELL; if (Side.SLD.equals(getOpenTradePosition().getSide())) action = Action.BUY; /* * Use the previous bars H/L as stop is we are in the money for * the trade. */ if (getCurrentCandleCount() > 0) { CandleItem prevCandleItem = (CandleItem) candleSeries.getDataItem(getCurrentCandleCount() - 1); if (Action.SELL.equals(action)) { if (avgFillPrice < prevCandleItem.getLow()) avgFillPrice = prevCandleItem.getLow(); } else { if (avgFillPrice > prevCandleItem.getHigh()) avgFillPrice = prevCandleItem.getHigh(); } } Money stopPrice = addPennyAndRoundStop(avgFillPrice, getOpenTradePosition().getSide(), action, 0.01); moveStopOCAPrice(stopPrice, true); _log.info("Rule move stop to b.e or prev bars H/L Symbol: " + getSymbol() + " Time: " + startPeriod + " stopPrice: " + stopPrice + " Side: " + this.getOpenTradePosition().getSide()); } /* * Trail on Heikin-Ashi above target 1 with a two bar trail. */ if (newBar) { Money target2RPrice = getRiskMultiplerPrice(_hiekinAshiTrailStartR, true, targetAddAmount, false); if ((target2RPrice.isLessThan(new Money(currentCandleItem.getClose())) && Side.BOT.equals(this.getOpenTradePosition().getSide())) || (target2RPrice.isGreaterThan(new Money(currentCandleItem.getClose())) && Side.SLD.equals(this.getOpenTradePosition().getSide()))) { Money newStop = getHiekinAshiTrailStop(this.getStopPriceMinUnfilled(), 2); if (!newStop.equals(this.getStopPriceMinUnfilled())) { moveStopOCAPrice(newStop, true); _log.info("Hiekin-AshiTrail: " + getSymbol() + " Trail Price: " + newStop + " Time: " + startPeriod + " Side: " + this.getOpenTradePosition().getSide()); } } } /* * Close any opened positions with a market order at the end of the * day. */ if (!currentCandleItem.getLastUpdateDate() .isBefore(this.getTradestrategy().getTradingday().getClose().minusMinutes(2))) { cancelOrdersClosePosition(true); _log.info("Close position 2min before close Symbol: " + getSymbol() + " Time: " + startPeriod); this.cancel(); } } catch (StrategyRuleException ex) { _log.error("Error Position Manager exception: " + ex.getMessage(), ex); error(1, 40, "Error Position Manager exception: " + ex.getLocalizedMessage()); } } /** * Method getHiekinAshiTrailStop. * * * This method is used to trail on Heikin-Ashi bars. Note trail is on the * low/high of the bar and assumes the bar are in the direction of the trade * i.e. side. * * @param stopPrice * @param bars * @return * @throws StrategyRuleException */ public Money getHiekinAshiTrailStop(Money stopPrice, int bars) throws StrategyRuleException { boolean trail = false; HeikinAshiDataset dataset = (HeikinAshiDataset) getTradestrategy().getStrategyData() .getIndicatorByType(IndicatorSeries.HeikinAshiSeries); if (null == dataset) { throw new StrategyRuleException(1, 110, "Error no Hiekin-Ashi indicator defined for this strategy"); } HeikinAshiSeries series = dataset.getSeries(0); // Start with the previous bar and work back int itemCount = series.getItemCount() - 2; if (itemCount > (2 + bars) && this.isThereOpenPosition()) { itemCount = itemCount - 2; for (int i = itemCount; i > (itemCount - bars); i--) { HeikinAshiItem candle = (HeikinAshiItem) series.getDataItem(i); // AbstractStrategyRule.logCandle(candle.getCandle()); trail = false; if (Side.BOT.equals(this.getOpenTradePosition().getSide())) { if ((candle.getLow() > stopPrice.doubleValue()) && (candle.getOpen() < candle.getClose())) { stopPrice = new Money(candle.getLow()); trail = true; } } else { if ((candle.getHigh() < stopPrice.doubleValue()) && (candle.getOpen() > candle.getClose())) { stopPrice = new Money(candle.getHigh()); trail = true; } } if (!trail) { break; } } } return stopPrice; } /** * Method getTargetOneOrder. * * This method is used to get target one order. * * @return TradeOrder */ public TradeOrder getTargetOneOrder() { if (this.isThereOpenPosition()) { Collections.sort(this.getTradestrategyOrders().getTradeOrders(), TradeOrder.ORDER_KEY); for (TradeOrder tradeOrder : this.getTradestrategyOrders().getTradeOrders()) { if (!tradeOrder.getIsOpenPosition()) { if (OrderType.LMT.equals(tradeOrder.getOrderType()) && null != tradeOrder.getOcaGroupName()) { return tradeOrder; } } } } return null; } /** * Method getOneMinuteTrailStop. * * This method is used to trail on one minute bars over the first target. * * @param candleSeries * @param stopPrice * @param currentCandle * @return * @throws StrategyRuleException */ public Money getOneMinuteTrailStop(CandleSeries candleSeries, Money stopPrice, CandleItem currentCandle) throws StrategyRuleException { if (!(59 == currentCandle.getLastUpdateDate().getSecond())) return stopPrice; if (Side.BOT.equals(this.getOpenTradePosition().getSide())) { if (stopPrice.isLessThan(new Money(candleSeries.getPreviousRollingCandle().getVwap()))) return new Money(candleSeries.getPreviousRollingCandle().getVwap()); if (candleSeries.getPreviousRollingCandle().getVwap() < candleSeries.getRollingCandle().getVwap()) return new Money(candleSeries.getPreviousRollingCandle().getVwap()); } else { if (stopPrice.isGreaterThan(new Money(candleSeries.getPreviousRollingCandle().getVwap()))) return new Money(candleSeries.getPreviousRollingCandle().getVwap()); if (candleSeries.getPreviousRollingCandle().getVwap() > candleSeries.getRollingCandle().getVwap()) return new Money(candleSeries.getPreviousRollingCandle().getVwap()); } // if (Side.BOT.equals(this.getOpenTradePosition().getSide())) { // if (null == candleHighLow // || currentCandle.getLow() > candleHighLow.doubleValue()) // candleHighLow = new Money(currentCandle.getLow()); // // if (stopPrice.isLessThan(candleHighLow)) // return candleHighLow; // // } else { // if (null == candleHighLow // || currentCandle.getHigh() < candleHighLow.doubleValue()) // candleHighLow = new Money(currentCandle.getHigh()); // // if (stopPrice.isGreaterThan(candleHighLow)) // return candleHighLow; // } return stopPrice; } /** * Method: getRiskMultiplerPrice * * @param riskMultipler * @param round * @param roundAmt * @param stop * @return * @throws StrategyRuleException */ private Money getRiskMultiplerPrice(Integer riskMultipler, Boolean round, Double roundAmt, Boolean stop) throws StrategyRuleException { Integer quantity = Math.abs(this.getOpenTradePosition().getOpenQuantity()); double avgFillPrice = (Math.abs(this.getOpenTradePosition().getTotalNetValue().doubleValue()) / quantity); double riskAmount = Math.abs(this.getTradestrategy().getRiskAmount().doubleValue() / quantity); String action = Action.BUY; int buySellMultipliter = 1; if (Side.BOT.equals(getOpenTradePosition().getSide())) { action = Action.SELL; buySellMultipliter = -1; } double amount = (avgFillPrice + (riskAmount * riskMultipler * buySellMultipliter * (stop ? 1 : -1))); if (amount < 0) amount = 0.02; if (round) { return addPennyAndRoundStop(amount, this.getOpenTradePosition().getSide(), action, roundAmt); } else { return new Money(amount); } } }