// Notes: // Determine the SnR levels within the given price range // - ignore candles that aren't within the range. /// <summary>Find support and resistance level data within the given price range</summary> /// <param name="instrument">The instrument to find levels data in</param> /// <param name="iend">The last candle, i.e. look backwards from here</param> /// <param name="price">The centre price to centre the SnR level data around</param> /// <param name="count">The number of candles that contribute to the SnR levels (only candles intersecting the range)</param> /// <param name="range">The range of prices (above and below 'price') to check</param> public SnR(Instrument instrument, QuoteCurrency?price = null, Idx?iend = null, int?count = null, QuoteCurrency?range = null, TimeFrame tf = null, QuoteCurrency?bucket_size = null) { price = price ?? instrument.LatestPrice.Mid; iend = iend ?? 1; // Set the range based on the recent span of candles if (range == null) { // Use a minimum of 5*MCS var r = instrument.MCS * 5; foreach (var c in instrument.CandleRange(iend.Value - 200, iend.Value)) { r = Math.Max(r, Math.Abs(c.High - price.Value)); r = Math.Max(r, Math.Abs(price.Value - c.Low)); } range = r; } ; SnRLevels = new List <Level>(); Instrument = instrument; Beg = iend.Value; End = iend.Value; Count = count ?? instrument.Bot.Settings.SnRHistoryLength; Range = range.Value; Price = price.Value; BucketSize = bucket_size ?? instrument.PipSize * 3; //instrument.MCS; TimeFrame = tf ?? Instrument.TimeFrame.GetRelativeTimeFrame(16); CalculateLevels(); }
/// <summary>True if a break-out to the 'high' side is detected</summary> private bool IsBreakOutInternal(Monic trend, List <Peak> peaks, bool high) { // A break-out is when the latest candle is significantly above the upper trend line // or below the lower trend line and showing signs of going further. Also, the preceding candles // must be below the trend line. // No trend, no break-out if (trend == null) { return(false); } // The latest candle must be in the break-out direction var sign = high ? +1 : -1; var latest = Instrument.Latest; if (latest.Sign != sign) { return(false); } // The price must be beyond the trend by a significant amount var price_threshold = trend.F(0.0) + sign * Instrument.MCS; if (Math.Sign(latest.Close - price_threshold) != sign) { return(false); } // Only the latest few candles can be beyond the trend line // and all must be in the direction of the break out. if (peaks[0].Index < -2) { // Allow the last two candles to be part of the break out foreach (var c in Instrument.CandleRange(peaks[0].Index, -2)) { // If more than half the candle is beyond the trend line, not a breakout var ratio = sign * Instrument.Compare(c, trend, false); if (ratio > 0) { return(false); } } } return(true); }
/// <summary>Determine a measure of trend strength</summary> private double TrendStrength(Monic trend, List <Peak> peaks, bool high) { if (trend == null) { return(0.0); } // Trend strength has to be a measure of how often price approached the trend line // and bounced off. Candles miles away from the trend line don't count, only consider // candles that span or are within a tolerance range of the trend line. var above = 0.0; var below = 0.0; var count = 0; var threshold = ConfirmTrend * Instrument.MCS; foreach (var c in Instrument.CandleRange(peaks.Back().Index, Instrument.IdxLast - WindowSize)) { var p = trend.F(c.Index + Instrument.IdxFirst); if (c.High < p - threshold) { continue; } if (c.Low > p + threshold) { continue; } above += Math.Max(0, c.High - p); below += Math.Max(0, p - c.Low); ++count; } // There must be some candles contributing var total = above + below; if (total == 0) { return(0.0); } // Return the proportion of above to below var strength = (high ? +1 : -1) * (below - above) / total; // Weight the strength based on the number of candles that contribute var weighted_count = Maths.Sigmoid(count, 6); return(strength * weighted_count); }
/// <summary>Manage an open trade. This should be called on each tick</summary> protected override void StepCore() { // Move SL to break even when profit > BreakEvenThreshold (adjusted for Spread) var adjusted_profit = Position.NetProfit - Instrument.Symbol.QuoteToAcct(Instrument.Spread * Position.Volume); if (adjusted_profit > BreakEvenThreshold && Position.StopLossRel() > 0) { Bot.Debugging.Trace("Moving SL to break even - Profit ${0} > Threshold ${1}".Fmt(Position.NetProfit, BreakEvenThreshold)); var trade = new Trade(Instrument, Position) { SL = Position.EntryPrice + Position.Sign() * 2.0 * Instrument.Spread }; Broker.ModifyOrder(Position, trade); } // What about candle follow mode: // If the price is heading for the TP and the last tops/bottoms of the last few candles // have been steadily increasing/decreasing. Move the SL to just above/below the previous candle // A state machine for managing the position switch (State) { // In this state, the trade looks good. Keep it open. case EState.TradeLooksGood: #region { // Enter scalp mode when profit > scalp threshold if (Profit > ScalpThreshold) { Bot.Debugging.Trace("Entering scalp mode - Profit ${0} > Threshold ${1}".Fmt(Profit, ScalpThreshold)); State = EState.Scalp; } else if (CandlesSincePeak >= ColdFeetCount) { // It's been a while since a profit peak was set... Bot.Debugging.Trace("Getting nervous... no peaks for a while"); State = EState.LookForExitAfterNextPeak; } break; } #endregion case EState.Scalp: { // In this mode we've made a decent profit. var close = false; for (;;) { //// Close if the profit drops to fraction of the peak //const double ScalpFraction = 0.5; //if (Position.NetProfit < ScalpFraction * PeakProfit) //{ // close = true; // Debugging.Trace("Position closed - profit dropped by {0} - ${1}".Fmt(ScalpFraction, Position.NetProfit)); //} // Close if the closed candle is an indecision candle. if (Instrument.NewCandle && Instrument[-1].Type(Instrument.MCS).IsIndecision()) { Bot.Debugging.Trace("Closing position - indecision candle"); close = true; break; } // Close if the peak of the candle just closed is not better than the candle before's peak i.e. Not higher lows, or lower highs if (Instrument.NewCandle && Position.EntryTime < Instrument[-1].TimestampUTC && Instrument.SequentialPeaks(Position.Sign(), 0) < 1) { Bot.Debugging.Trace("Closing position - peaks oppose trend direction"); close = true; break; } // Close if the candle closes worse then the close of the -2 candle if (Instrument.NewCandle && Position.EntryTime < Instrument[-1].TimestampUTC && Math.Sign(Instrument[-1].Close - Instrument[-2].Close) != Position.Sign()) { Bot.Debugging.Trace("Closing position - candle close was worse that close of -2 candle"); close = true; break; } // Close if the profit is huge if (Position.NetProfit > 2.0 * ScalpThreshold && TicksSincePeak > 10) { Bot.Debugging.Trace("Closing position - huge spike detected!"); close = true; break; } // Don't close. break; } if (close) { Broker.ClosePosition(Position, "{0} - Scalp".Fmt(R <PositionManagerNervious> .NameOf)); State = EState.PositionClosed; } break; } case EState.TrailingSL: { // In this mode, we move the SL up to just above/below a recently closed candle if (Instrument.NewCandle) { var sign = Position.TradeType.Sign(); var limit = Instrument.CurrentPrice(-sign); foreach (var c in Instrument.CandleRange(-3, 0)) { limit = sign > 0 ? Math.Min(limit, c.Low) : Math.Max(limit, c.High); } // Move the SL if in the correct direction if (sign > 0 && limit > Position.StopLoss || sign < 0 && limit < Position.StopLoss) { var trade = new Trade(Instrument, Position) { SL = limit }; Broker.ModifyOrder(Position, trade); } } break; } case EState.LookForExitAfterNextPeak: { // In this state, the profit hit a peak a while ago, // and we're waiting for it to come back to that peak // level so we can bail out. if (NewPeak) { State = EState.CloseAtNextNonPeak; Bot.Debugging.Trace("Closing at next non peak"); } else if (CandlesSincePeak >= 2 * ColdFeetCount) { State = EState.BailIfProfitable; Bot.Debugging.Trace("Closing when profitable"); } break; } case EState.CloseAtNextNonPeak: { // In this state we're looking to exit the trade the // next time the profit peak is not set if (!NewPeak) { Broker.ClosePosition(Position, "{0} - Non-Peak".Fmt(R <PositionManagerNervious> .NameOf)); State = EState.PositionClosed; Bot.Debugging.Trace("Position closed - non-peak"); } break; } case EState.CloseAtNextProfitDrop: { // In this state we just want out with the minimum of loss. // Bail as soon as the profit decreases if (!IsGain) { Broker.ClosePosition(Position, "{0} - Next Profit Drop".Fmt(R <PositionManagerNervious> .NameOf)); State = EState.PositionClosed; Bot.Debugging.Trace("Position closed - profit dropped"); } break; } case EState.BailIfProfitable: { // In this state, it's been too long since the peak was last // hit, bail out if profitable if (NewPeak) { State = EState.CloseAtNextNonPeak; } else if (Position.NetProfit > 0) { Broker.ClosePosition(Position, "{0} - Bail With Profit".Fmt(R <PositionManagerNervious> .NameOf)); State = EState.PositionClosed; } else if (CandlesSinceProfitable > 4 * ColdFeetCount) { State = EState.CloseAtNextProfitDrop; Bot.Debugging.Trace("Closing at next drop in profit"); } break; } } }