/// <summary>Called when new data is received</summary> public override void Step() { base.Step(); if (!Instrument.NewCandle) { return; } var position = Positions.FirstOrDefault(); if (position == null) { var snr = new SnR(Instrument, tf: TimeFrame.Hour8); var mcs = Instrument.MCS; // Look for price being near an SnR level var price = Instrument.LatestPrice; var near = snr.Nearest(price.Mid, 0); if (near == null || Math.Abs(near.Price - price.Mid) > 0.5 * mcs) { return; } // Get the preceding trend var trend = Instrument.MeasureTrendFromCandles(-5, 1); } }
/// <summary></summary> protected override void StepCore() { // If the SL is on the winning side, let the trade run its course var sl_rel = Position.StopLossRel(); if (sl_rel < 0) { return; } var mcs = Instrument.MCS; var sign = Position.Sign(); var price = Instrument.LatestPrice; var trend_sign_slow = Strategy.TrendSignSlow; var trend_sign_fast = Strategy.TrendSignFast; // If the position opposes the slow trend, try to close it at break even if (sign == -trend_sign_slow && sign == -trend_sign_fast) { var cp = price.Price(-sign); // Choose a SL based on SnR levels var snr = new SnR(Instrument); Debugging.Dump(snr); var near = snr.Nearest(cp, -sign, min_dist: mcs * 0.5); var sl = near != null ? near.Price : cp - sign * mcs; if (Math.Abs(sl - Position.EntryPrice) < sl_rel) { Broker.ModifyOrder(Instrument, Position, sl: sl); Debugging.Trace(" +Position opposes the trend (trend={0}), closing at break even".Fmt(trend_sign_slow)); return; } } // If the trade is better than RtR=1:1, and the close probability is high close the trade // For closing positions, use Ask when the price is high, Bit when the price is low var prob_sign = Strategy.ProbSign; var prob = Strategy.Prob(prob_sign); if (sign == prob_sign && Math.Abs(prob) > StrategyPriceDistribution.ClosePositionProb) { // If the price gradient is in the direction of the trade, don't close it var d = Instrument.HighRes.FirstDerivative(-1); if (Math.Sign(d) == sign) { return; } var min_profit = Instrument.Symbol.QuoteToAcct(sl_rel * Position.Volume); if (Position.NetProfit > min_profit) { Debugging.Trace(" +Close Prob: {0}".Fmt(prob)); Broker.ClosePosition(Position); } } }
/// <summary> /// Update the 'Features' vector with values for the quality of a trade at 'CurrentIndex'. /// Note: args.Candle is the candle at 'CurrentIndex'</summary> protected override void UpdateFeatureValues(DataEventArgs args) { // Only make predictions on new candle events if (!args.NewCandle) { return; } //// Get the candle in question //var candle = Instrument[neg_idx]; //// Assume 'candle' has just opened, so the current price is the Open price //var price = candle.Open; //// Get the average candle size over the history window //var mcs = Instrument.MeanCandleSize(neg_idx - HistoryWindow, neg_idx); Features.Clear(); RSIFeatures(); CandlePatterns(); #if false //{// Preceding candle data // foreach (var c in Instrument.CandleRange(neg_idx - 3, neg_idx)) // { // values.Add(c.Open ); // values.Add(c.High ); // values.Add(c.Low ); // values.Add(c.Close); // } //} { // Preceding trend var trend = Instrument.MeasureTrend(neg_idx - 5, neg_idx); values.Add(trend); } { // SnR Levels // Normalised Distance from the nearest SnR level. // 'Near' means within the mean candle size of the SnR level var snr = new SnR(Instrument, neg_idx - HistoryWindow, neg_idx); var lvl = snr.SnRLevels.MinBy(x => Math.Abs(price - x.Price)); // Normalised distance based on the mean candle size with the candle centred on the SnR level var d = Math.Abs(lvl.Price - price); var dist = Math.Min(2 * d / mcs, 1.0); values.Add(dist); } { // EMA gradients var ema = new[] { Bot.Indicators.ExponentialMovingAverage(Bot.MarketSeries.Close, 21), Bot.Indicators.ExponentialMovingAverage(Bot.MarketSeries.Close, 55), Bot.Indicators.ExponentialMovingAverage(Bot.MarketSeries.Close, 144), }; for (var j = 0; j != ema.Length; ++j) { for (var i = 3; i-- != 0;) { var index = neg_idx - i - Instrument.FirstIdx; var dp = ema[j].Result.FirstDerivative(index) / mcs; var ddp = ema[j].Result.SecondDerivative(index) / mcs; values.Add(dp); values.Add(ddp); } } } { // MACD // At Buy points: // - MACD (blue line) is negative // - MACD is a minima // - The histogram has a positive gradient (opposite to the MACD) // - The gap between MACD and the signal line (red line) is large // At Sell points: // - inverse of buy var macd = Bot.Indicators.MacdCrossOver(Bot.MarketSeries.Close, 26, 12, 9); values.Add(macd.MACD.LastValue / mcs); values.Add(macd.Histogram.LastValue / mcs); var index = neg_idx - 1 - Instrument.FirstIdx; var y1 = macd.MACD.FirstDerivative(index) / mcs; var y2 = macd.MACD.SecondDerivative(index) / mcs; values.Add(y1); values.Add(y2); var h1 = macd.Histogram.FirstDerivative(index) / mcs; var h2 = macd.Histogram.SecondDerivative(index) / mcs; values.Add(h1); values.Add(h2); } { // Bollinger Bands // Buy signal when: // - price touches the centre line during a rising trend // Sell signal when: // - price touches the upper band during a falling trend // Also, the distance between upper/lower bands means something var bb = Bot.Indicators.BollingerBands(Bot.MarketSeries.Close, 21, 2, MovingAverageType.Exponential); var index = neg_idx - 1 - Instrument.FirstIdx; var c1 = bb.Main.FirstDerivative(index) / mcs; values.Add(c1); // Signed distance from the upper/lower bands. Positive means outside the bands var dist_upper = (price - bb.Top.LastValue) / (bb.Top.LastValue - bb.Main.LastValue); var dist_lower = (bb.Bottom.LastValue - price) / (bb.Main.LastValue - bb.Bottom.LastValue); values.Add(dist_upper); values.Add(dist_lower); var dist = (bb.Top.LastValue - bb.Bottom.LastValue) / mcs; values.Add(dist); } return(values); #endif }
/// <summary>Called when new data is received</summary> public override void Step() { base.Step(); using (Broker.SuspendRiskCheck(ignore_risk: true)) { const double TakeProfitFrac = 1.020; const double ReverseFrac = 0.998; Dump(); // The unit of volume to trade var mcs = Instrument.MCS; var ep = Instrument.LatestPrice.Mid; var vol = Broker.ChooseVolume(Instrument, 5.0 * mcs); // Open a trade in the direction of the trend if (ProfitSign == 0) { Dump(); Debugging.Trace("Idx={0},Tick={1} - Setting profit sign ({2}) - Equity = ${3}".Fmt(Instrument.Count, Bot.TickNumber, TrendSign, Equity)); var tt = CAlgo.SignToTradeType(TrendSign); var trade = new Trade(Instrument, tt, Label, ep, null, null, vol); Broker.CreateOrder(trade); LastEquity = Equity; } // While the profit direction is the same as the trend direction // look for good spots to add hedged pending orders if (ProfitSign == TrendSign) { // If the current price is at a SnR level, add pending orders on either side. // Hopefully price will reverse at the SnR level and only trigger one of the pending orders. // If price then reverses we can sell an existing profitable trade and be left with the // profit direction going in the right direction var snr = new SnR(Instrument, ep); var lvl_above = snr.Nearest(ep, +1, min_dist: 0.5 * mcs, min_strength: 0.7); var lvl_below = snr.Nearest(ep, -1, min_dist: 0.5 * mcs, min_strength: 0.7); // Choose price levels for the pending orders var above = lvl_above != null ? lvl_above.Price + 0.5 * mcs : ep + mcs; var below = lvl_below != null ? lvl_below.Price - 0.5 * mcs : ep - mcs; // Only recreate if necessary if (!PendingOrders.Any(x => x.TradeType == TradeType.Buy && Maths.FEql(x.TargetPrice, above, 5 * Instrument.PipSize)) || !PendingOrders.Any(x => x.TradeType == TradeType.Sell && Maths.FEql(x.TargetPrice, below, 5 * Instrument.PipSize))) { Dump(); Debugging.Dump(snr); Debugging.Trace("Idx={0},Tick={1} - Adjusting pending orders - Equity = ${2}".Fmt(Instrument.Count, Bot.TickNumber, Equity)); // Cancel any other pending orders further away than these too Broker.CancelAllPendingOrders(Label); var buy = new Trade(Instrument, TradeType.Buy, Label, above, null, null, vol); var sel = new Trade(Instrument, TradeType.Sell, Label, below, null, null, vol); Broker.CreatePendingOrder(buy); Broker.CreatePendingOrder(sel); } } // If the profit is against the Trend, do nothing until losing a fraction of last equity if (ProfitSign != TrendSign && Equity < ReverseFrac * LastEquity) { Dump(); Debugging.Trace("Idx={0},Tick={1} - Changing profit sign to ({2}) - Equity = ${3}".Fmt(Instrument.Count, Bot.TickNumber, TrendSign, Equity)); var sign = -ProfitSign; // Try to reverse the sign by closing profitable positions foreach (var pos in Positions.Where(x => x.Sign() != sign && x.NetProfit > 0).OrderBy(x => - x.NetProfit).ToArray()) { Broker.ClosePosition(pos); // Break out when the profit direction has been reversed if (ProfitSign == sign) { break; } } // Couldn't reverse the sign by closing positions, open a new one if (ProfitSign != sign) { var tt = CAlgo.SignToTradeType(sign); var reverse_vol = Math.Abs(NetVolume) + vol; var trade = new Trade(Instrument, tt, Label, ep, null, null, reverse_vol); Broker.CreateOrder(trade); } LastEquity = Equity; } // If equity is greater than a threshold, take profits if (Equity > TakeProfitFrac * LastEquity) { Dump(); Debugging.Trace("Idx={0},Tick={1} - Profit taking - Equity = ${2}".Fmt(Instrument.Count, Bot.TickNumber, Equity)); var sign = ProfitSign; foreach (var pos in Positions.Where(x => x.Sign() == sign && x.NetProfit > 0).OrderBy(x => - x.NetProfit).ToArray()) { if (pos.Volume >= Math.Abs(NetVolume)) { continue; } Broker.ClosePosition(pos); } LastEquity = Equity; } } }