/* ===========================================================
* 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.StrategyData;
import org.trade.strategy.data.candle.CandleItem;
/**
*/
public class PosMgrFHXRBHYRStrategy extends AbstractStrategyRule {
/**
* 1/ If the open position is filled create a STP and 2 Targets (LMT) OCA
* orders at xR and yR with 50% of the filled quantity for each. Use the
* open position fill quantity, price and stop price to determine the target
* price. The STP orders take an initial risk of 2R.
*
* 2/ Target/Stop prices should be round over/under whole/half numbers when
* ever they are calculated..
*
* 3/ After 9:35 and before 15:30 if the current VWAP crosses the 9:35
* candles VWAP move the STP price on each of the STP order to break even.
*
* 4/ At 15:30 move the STP order to the average fill price of the filled
* open order.
*
* 5/ Move stop to B.E when target one hit (Optional see code).
*
* 6/ When target one hit trail back half(BH) on 1min bars (Optional).
*
* 7/ Close any open positions at 15:58.
*
*/
private static final long serialVersionUID = -6717691162128305191L;
private final static Logger _log = LoggerFactory.getLogger(PosMgrFHXRBHYRStrategy.class);
/**
* 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 PosMgrFHXRBHYRStrategy(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 Mgr 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. Two targets at 2R and
* 2R Stop and 2X actual stop this will be managed to 1R below
*
* Make the stop -2R and manage to the Vwap MA of the opening
* bar.
*/
Integer quantity = this.getOpenPositionOrder().getFilledQuantity();
Integer tgt1Qty = quantity / 2;
Integer tgt2Qty = quantity - tgt1Qty;
// Integer tgt3Qty = quantity - (tgt1Qty + tgt2Qty);
createStopAndTargetOrder(getOpenPositionOrder(), 2, new Money(0.01), 2, new Money(0.01), tgt1Qty, true);
createStopAndTargetOrder(getOpenPositionOrder(), 2, new Money(0.01), 2, new Money(0.01), tgt2Qty, true);
// createStopAndTargetOrder(getOpenPositionOrder(),
// 2,0.01,4,0.01, tgt3Qty, true);
_log.info(
"Open position submit Stop/Tgt orders created Symbol: " + getSymbol() + " Time:" + startPeriod);
}
/*
* TODO We the first pivot as a point to take profits.
*
* Note this is just an example need refining.
*/
// if
// (startPeriod.after(TradingCalendar.addMinutes(this.getTradestrategy().getTradingday().getOpen(),
// 15)) && newBar) {
// PivotDataset dataset = (PivotDataset) getTradestrategy()
// .getStrategyData().getIndicatorByType(
// IndicatorSeries.PivotSeries);
// PivotSeries pivotSeries = dataset.getSeries(0);
// // Start with the current bar and work back
// int itemCount = pivotSeries.getItemCount() - 1;
//
// if (itemCount > 0 && this.isThereOpenPosition()) {
// for (int i = itemCount; i > 1; i--) {
// PivotItem pivot = (PivotItem) pivotSeries.getDataItem(i);
// if (pivot
// .getPeriod()
// .getStart()
// .after(TradingCalendar.addMinutes(this.getTradestrategy().getTradingday().getOpen(),
// 15))
// && newBar) {
// _log.error("startPeriod: "
// + startPeriod
// + " Pivot time: "
// + pivot.getPeriod().getStart()
// + " value: "
// + pivot.getPivotPrice()
// + " Time 9:45"
// +
// TradingCalendar.addMinutes(this.getTradestrategy().getTradingday().getOpen(),
// 15));
// double avgfillPrice = this.getOpenPositionOrder()
// .getAverageFilledPrice().doubleValue();
//
// double stopPrice = this.getOpenPositionOrder()
// .getStopPrice().doubleValue();
//
// if (Side.BOT.equals(getOpenTradePosition()
// .getSide())) {
// if (pivot.getPivotPrice() > avgfillPrice
// && Side.SLD
// .equals(pivot.getPivotSide())) {
// moveStopOCAPrice(new Money(
// currentCandleItem.getVwap()), true);
// }
// } else {
// if (pivot.getPivotPrice() < avgfillPrice
// && Side.BOT
// .equals(pivot.getPivotSide())) {
// moveStopOCAPrice(new Money(
// currentCandleItem.getVwap()), true);
// }
// }
// break;
// } else {
// break;
// }
// }
// }
// }
/*
* TODO this check will plot the last three vwaps as y = a + bx +
* cx^2 with a correlation coeff of 0.6 then extrapolate the next
* vwap. If that is beyond the stop it will move the stop to b.e.
*
* Note this is just an example need refining.
*/
// if
// (startPeriod.after(TradingCalendar.addMinutes(this.getTradestrategy().getTradingday().getOpen(),
// 20)) && newBar) {
//
// _log.info("Symbol: " + this.getSymbol() + " Current Time: "
// + currentCandleItem.getPeriod().getStart() + " vwap: "
// + currentCandleItem.getVwap());
// MatrixFunctions matrixFunctions = new MatrixFunctions();
// int barBack = 3;
// int polyOrder = 2;
// double _minCorrelationCoeff = 0.6;
// if (candleSeries.getItemCount() < barBack)
// return;
//
// List<Pair> pairs = new ArrayList<Pair>();
//
// int startBar =
// candleSeries.indexOf(prevCandleItem.getPeriod())
// - (barBack - 1);
// Long startTime = ((CandleItem) candleSeries
// .getDataItem(startBar)).getPeriod().getStart()
// .getTime();
// double prevY = Double.MAX_VALUE;
// for (int i = startBar; i < (startBar + barBack); i++) {
// CandleItem candleItem = (CandleItem) candleSeries
// .getDataItem(i);
// pairs.add(new Pair(
// ((double) (candleItem.getPeriod().getStart()
// .getTime() - startTime) / (1000 * 60 * 60)),
// candleItem.getVwap()));
// _log.info("Symbol: "
// + this.getSymbol()
// + " Time: "
// + candleItem.getPeriod().getStart()
// + " vwap: "
// + candleItem.getVwap()
// + " diff: "
// + (prevY != Double.MAX_VALUE ? (candleItem
// .getVwap() - prevY) : 0));
// prevY = candleItem.getVwap();
// }
// Collections.sort(pairs, Pair.X_VALUE_ASC);
// Pair[] pairsArray = pairs.toArray(new Pair[] {});
// double[] terms = matrixFunctions.solve(pairsArray,
// polyOrder);
// double correlationCoeff = matrixFunctions
// .getCorrelationCoefficient(pairsArray, terms);
// double standardError = matrixFunctions.getStandardError(
// pairsArray, terms);
// _log.info("Symbol: " + this.getSymbol() +
// " correlationCoeff: "
// + correlationCoeff + " standardError: " + standardError);
// if (correlationCoeff > _minCorrelationCoeff) {
//
// Entrylimit entryLimit = this.getEntryLimit().getValue(
// new Money(prevCandleItem.getVwap()));
//
// Money pivotRange = new Money(
// Math.abs((pairs.get(0).y - pairs.get(pairs.size() - 1).y)));
// if (null != entryLimit
// && (entryLimit.getPivotRange().doubleValue()) <= pivotRange
// .doubleValue()) {
//
// Pair prevPair = null;
// boolean biggerDiff = true;
// // double diffAmt = entryLimit.getPivotRange()
// // .doubleValue();
// double diffAmt = Double.MAX_VALUE;
// if (Side.BOT.equals(getOpenTradePosition().getSide()))
// diffAmt = diffAmt * -1;
//
// for (Pair pair : pairs) {
// if (null != prevPair) {
// if (diffAmt != Double.MAX_VALUE) {
// double diff = prevPair.y - pair.y;
// if (diffAmt > (prevPair.y - pair.y)) {
// biggerDiff = false;
// break;
// }
// }
// diffAmt = prevPair.y - pair.y;
// }
// prevPair = pair;
// }
// if (biggerDiff) {
// double nextTime = (double) (currentCandleItem
// .getPeriod().getStart().getTime() - startTime)
// / (1000 * 60 * 60);
// double nextY = MatrixFunctions.fx(nextTime, terms);
//
// pairs.add(new Pair(
// ((double) (currentCandleItem.getPeriod()
// .getStart().getTime() - startTime) / (1000 * 60 * 60)),
// nextY));
//
// for (Pair pair : pairs) {
// double y = MatrixFunctions.fx(pair.x, terms);
// pair.y = y;
// _log.info("Symbol: " + this.getSymbol()
// + " New Values x: " + pair.x + " y: "
// + pair.y);
// }
// double avgfillPrice = this.getOpenPositionOrder()
// .getAverageFilledPrice().doubleValue();
//
// double stopPrice = this.getOpenPositionOrder()
// .getStopPrice().doubleValue();
//
// _log.info("*** Symbol: " + this.getSymbol()
// + " Move stop avgfillPrice: "
// + avgfillPrice + " x: "
// + currentCandleItem.getPeriod().getStart()
// + " nextY: " + nextY);
//
// if (Side.BOT.equals(getOpenTradePosition()
// .getSide())) {
// if (nextY <= avgfillPrice) {
// moveStopOCAPrice(new Money(
// (avgfillPrice - stopPrice) / 2),
// true);
// }
// } else {
// if (nextY >= avgfillPrice) {
// moveStopOCAPrice(new Money(
// (stopPrice - avgfillPrice) / 2),
// true);
// }
// }
// }
// }
// }
// }
/*
* 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().minusMinutes(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) {
_log.info("Rule move stop to b.e.. Symbol: " + getSymbol() + " Time: " + startPeriod);
String action = Action.SELL;
double avgPrice = this.getOpenTradePosition().getTotalBuyValue().doubleValue()
/ this.getOpenTradePosition().getTotalBuyQuantity().doubleValue();
CandleItem prevCandleItem = null;
if (getCurrentCandleCount() > 0) {
prevCandleItem = (CandleItem) candleSeries.getDataItem(getCurrentCandleCount() - 1);
// AbstractStrategyRule
// .logCandle(this, prevCandleItem.getCandle());
}
if (avgPrice < prevCandleItem.getLow())
avgPrice = prevCandleItem.getLow();
if (Side.SLD.equals(getOpenTradePosition().getSide())) {
action = Action.BUY;
avgPrice = this.getOpenTradePosition().getTotalSellValue().doubleValue()
/ this.getOpenTradePosition().getTotalSellQuantity().doubleValue();
if (avgPrice > prevCandleItem.getHigh())
avgPrice = prevCandleItem.getHigh();
}
Money stopPrice = addPennyAndRoundStop(avgPrice, getOpenTradePosition().getSide(), action, 0.01);
moveStopOCAPrice(stopPrice, true);
}
/*
* Move stock to b.e. when target one hit.
*/
if (null != getTargetOneOrder()) {
if (this.getTargetOneOrder().getIsFilled() && newBar) {
_log.info("Rule move stop to b.e. after target one hit Symbol: " + getSymbol() + " Time: "
+ startPeriod);
String action = Action.SELL;
if (Side.SLD.equals(getOpenTradePosition().getSide()))
action = Action.BUY;
Money newStop = addPennyAndRoundStop(this.getTargetOneOrder().getAverageFilledPrice().doubleValue(),
getOpenTradePosition().getSide(), action, 0.01);
if (!newStop.equals(this.getStopPriceMinUnfilled())) {
// moveStopOCAPrice(newStop, true);
}
}
}
/*
* We have sold the first half of the position try to trail BH on
* one minute bars.
*/
if (null != getTargetOneOrder()) {
if (this.getTargetOneOrder().getIsFilled()) {
Money newStop = getOneMinuteTrailStop(candleSeries, this.getStopPriceMinUnfilled(),
currentCandleItem);
if (!newStop.equals(new Money(this.getStopPriceMinUnfilled()))) {
_log.info("PositionManagerStrategy OneMinuteTrail: " + getSymbol() + " Trail Price: " + newStop
+ " Time: " + startPeriod + " Side: " + this.getOpenTradePosition().getSide());
// moveStopOCAPrice(newStop, true);
}
}
}
/*
* 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("PositionManagerStrategy 15:58:00 done: " + 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 getTargetOneOrder.
*
* This method is used to get target one order.
*
* @return TradeOrder target one tradeOrder.
* @throws StrategyRuleException
*/
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 stopPrice
* Money
* @param bars
* int
* @return Money new stop or orginal if not trail.
* @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;
}
}