/// <summary> /// Return the nearest significant support level above or below 'price'. /// 'sign' is the direction to look for a level. +1 means above, -1 means below, 0 means either side. /// 'min_dist' means the nearest SnR level >= price + sign * min_dist (default: 0 if sign=0, 0.25*MCS if sign=±1) /// 'range' is a bound on the levels to check (default: no range limit) /// 'radius' is a shortcut for Range(price - radius, price + radius). (Default: ignored) </summary> public Level Nearest(QuoteCurrency price, int sign, QuoteCurrency?min_dist = null, RangeF?range = null, QuoteCurrency?radius = null, double?min_strength = 0.5) { // Set the range if (radius != null && range == null) { range = new RangeF((double)(price - radius), (double)(price + radius)); } // Set the minimum if not provided min_dist = min_dist ?? (sign == 0 ? 0 : 0.25 * Instrument.MCS); // Get the threshold price var thresh = price + sign * min_dist.Value; // Find levels above/below the threshold var levels = (IEnumerable <Level>)SnRLevels; if (range != null) { levels = levels.Where(x => range.Value.Contains((double)x.Price)); } if (sign != 0) { levels = levels.Where(x => Math.Sign(x.Price - thresh) == sign); } if (min_strength != null) { levels = levels.Where(x => x.Strength >= min_strength.Value); } // Return the levels closed to 'price' return(levels.MinByOrDefault(x => Math.Abs(x.Price - price))); }
/// <summary>Single candle type. Relative to the mean candle size (total size)</summary> public EType Type(QuoteCurrency mcs) { // A candle is a doji if the body is very small if (BodyLength < 0.03 * mcs) { return(EType.Doji); } // A candle is a hammer/inverse hammer if it has a small body that is near one end of the candle if (BodyLength < 0.125 * mcs || BodyLength < 0.1 * TotalLength) { if (UpperWickLength < 0.1 * TotalLength) { return(EType.Hammer); } if (LowerWickLength < 0.1 * TotalLength) { return(EType.InvHammer); } return(EType.SpinningTop); } // A candle is a Marubozu if it has a large body and is a large portion of the total length if (BodyLength > 0.9 * mcs && BodyLength > 0.5 * TotalLength) { // A strengthening or weakening marubozu return (Strengthening ? EType.MarubozuStrengthening : Weakening?EType.MarubozuWeakening: EType.Marubozu); } // No particular type return(EType.Unknown); }
public CandlePattern(ECandlePattern pattern, TradeType tt, RangeF range, Idx index, QuoteCurrency ep) { Pattern = pattern; TT = tt; Range = range; Index = index; EP = ep; }
public PriceTick(double calgo_index, long timestamp, QuoteCurrency ask, QuoteCurrency bid) { if (ask < bid) { throw new Exception("Negative spread"); } Index = calgo_index; Timestamp = timestamp; Ask = ask; Bid = bid; }
/// <summary>Return the value (in quote currency) of this order when the price is at 'price' (not scaled by volume) /// Positive values mean in profit. Negative values mean loss</summary> public QuoteCurrency ValueAt(QuoteCurrency price, bool consider_sl, bool consider_tp) { var sign = TradeType.Sign(); // If 'price' is beyond the stop loss, clamp at the stop loss value if (consider_sl && SL != null && Maths.Sign(SL.Value - price) == sign) { price = SL.Value; } // If 'price' is beyond the take profit, clamp at the take profit value if (consider_tp && TP != null && Maths.Sign(price - TP.Value) == sign) { price = TP.Value; } // Return the position value in quote currency return(sign * (price - EP)); }
/// <summary>Return the maximum volume to trade given a relative stop loss value</summary> public long ChooseVolume(Instrument instr, QuoteCurrency sl_rel, double?risk = null) { // Find the amount we're allowed to risk // Scale down a little bit to prevent issues comparing the returned SL to BalanceToRisk var balance_to_risk = BalanceToRisk * 0.99; if (balance_to_risk == 0) { return(0); } // Set a hard lower limit on 'SL' if (sl_rel < 5.0 * instr.PipSize) { sl_rel = 5.0 * instr.PipSize; } // Get this amount in quote currency var volume = (risk ?? 1.0) * balance_to_risk / instr.Symbol.QuoteToAcct(sl_rel); return(instr.Symbol.NormalizeVolume(volume, RoundingMode.Down)); }
/// <summary>Create a trade with explicit values</summary> public Trade(Instrument instr, TradeType tt, string label, QuoteCurrency ep, QuoteCurrency?sl, QuoteCurrency?tp, long volume, string comment = null, Idx?idx = null, EResult result = EResult.Unknown) { CAlgoId = null; TradeIndex = m_trade_index++; TradeType = tt; Instrument = instr; Result = result; EntryIndex = (double)(idx ?? instr.IdxNow) - instr.IdxFirst; ExitIndex = EntryIndex; Expiration = null; Label = label ?? string.Empty; Comment = comment ?? string.Empty; EP = ep; SL = sl; TP = tp; Volume = volume; MaxFavourableExcursion = 0.0; MaxAdverseExcursion = 0.0; NetProfit = 0.0; GrossProfit = 0.0; }
/// <summary>Convert a price in quote currency to pips</summary> public static Pips QuoteToPips(this Symbol sym, QuoteCurrency price) { return((double)price / sym.PipSize); }
/// <summary>Returns the price peaks using a window with size 'window_size'</summary> public IEnumerable <Peak> FindPeaks(Idx iend) { // Create window buffers for the high/low prices var price_hi = new QuoteCurrency[WindowSize]; var price_lo = new QuoteCurrency[WindowSize]; for (int i = 0; i != WindowSize; ++i) { price_hi[i] = -double.MaxValue; price_lo[i] = +double.MaxValue; } // Look for peaks int d = 0, hh = -1, ll = -1, hcount = 0, lcount = 0; for (Idx i = iend; i-- != Instrument.IdxFirst; d = (d + 1) % WindowSize) { var candle = Instrument[i]; Beg = i; // Add the new price values price_hi[d] = candle.High; price_lo[d] = candle.Low; // Find the ring buffer index of the highest and lowest price var h = price_hi.IndexOfMaxBy(x => x); var l = price_lo.IndexOfMinBy(x => x); // If the high is the highest for the full window size, output it if (hh == h) { if (++hcount == WindowSize) { // Skip index == 0 because it's not a complete candle var idx = i + (d - hh + WindowSize) % WindowSize; if (Instrument.IdxLast - idx > 1) { yield return(new Peak(idx, price_hi[hh], true)); } hh = -1; hcount = 0; } } else { hh = h; hcount = 1; } // If the low is the lowest for the full window size, output it if (ll == l) { if (++lcount == WindowSize) { // Skip index == 0 because it's not a complete candle var idx = i + (d - ll + WindowSize) % WindowSize; if (Instrument.IdxLast - idx > 1) { yield return(new Peak(idx, price_lo[ll], false)); } ll = -1; lcount = 0; } } else { ll = l; lcount = 1; } } }
public Level(QuoteCurrency price) { Price = price; Strength = 0; Average = new Avr(); }
public StationaryPoint(Idx index, QuoteCurrency price, int sign) { Index = index; Price = price; Sign = sign; }
public Peak(Idx index, QuoteCurrency price, bool high) { Index = index; Price = price; High = high; }
/// <summary>Return the value (in quote currency) of this position when the price is at 'price'</summary> public static QuoteCurrency ValueAt(this PendingOrder pos, QuoteCurrency price, bool consider_sl, bool consider_tp) { return(new Order(pos).ValueAt(price, consider_sl, consider_tp)); }
/// <summary>True if there are open positions within a range of 'price'</summary> public bool NearbyPositions(string label, QuoteCurrency price, double min_trade_dist, int sign = 0) { return(sign == 0 ? Positions.Any(x => Math.Abs(x.EntryPrice - price) < min_trade_dist) : Positions.Any(x => Math.Abs(x.EntryPrice - price) < min_trade_dist && x.Sign() == sign)); }
/// <summary>Set the SL or TP for a position such that it will close when the price hits 'price'</summary> public void CloseAt(Instrument instr, Position position, QuoteCurrency price) { Debug.Assert(instr.SymbolCode == position.SymbolCode); Bot.TradeStats.Event(position, "CloseAt"); var trade = (Trade)null; if (position.TradeType == TradeType.Buy) { // If the current price is not between 'price' and 'position.StopLoss' move the SL to 'price' if (!instr.CurrentPrice(-1).Within(position.StopLoss ?? -double.MaxValue, price)) { trade = new Trade(instr, position) { SL = price - instr.Spread } } ; // If the current price is not between 'price' and 'position.TakeProfit' move the TP to 'price' else if (!instr.CurrentPrice(+1).Within(price, position.TakeProfit ?? +double.MaxValue)) { trade = new Trade(instr, position) { TP = price } } ; // If the current price spans 'price' close immediately else { trade = null; } } else { // If the current price is not between 'price' and 'position.StopLoss' move the SL to 'price' if (!instr.CurrentPrice(+1).Within(price, position.StopLoss ?? +double.MaxValue)) { trade = new Trade(instr, position) { SL = price + instr.Spread } } ; // If the current price is not between 'price' and 'position.TakeProfit' move the TP to 'price' else if (!instr.CurrentPrice(-1).Within(position.TakeProfit ?? -double.MaxValue, price)) { trade = new Trade(instr, position) { TP = price } } ; // If the current price spans 'price' close immediately else { trade = null; } } // Update or close the trade if (trade != null) { // Move the SL or TP ModifyOrder(position, trade); } else { // Close immediately if the price spread spans 'price' ClosePosition(position, "CloseAt at current Price"); } }
/// <summary>Convert a price in quote currency to account currency. Remember to multiple by trade volume</summary> public static AcctCurrency QuoteToAcct(this Symbol sym, QuoteCurrency price) { return((double)(sym.QuoteToPips(price) * sym.PipValue)); }
/// <summary>Return the value (in quote currency) of this position when the price is at 'price'</summary> public static QuoteCurrency ValueAt(this ITrade pos, QuoteCurrency price, bool consider_sl, bool consider_tp) { return(new Order(pos, true).ValueAt(price, consider_sl, consider_tp)); }
/// <summary> /// 'rel_price' is the price in the profit direction that trips the break even. e.g. MCS would trip break even when the price is in profit by MCS. /// 'bias' is the offset from break even</summary> public PositionManagerMoveToBreakEven(Rylobot bot, Position position, QuoteCurrency rel_price, QuoteCurrency?bias = null) : base(bot, position) { m_rel_price = rel_price; m_bias = bias; }
public TargetEntryData(QuoteCurrency price, TradeType tt) { Price = price; TT = tt; }