///// <summary>Incorporate a candle into this trade</summary> //public void AddCandle(Candle candle) //{ // // Adding a candle "open"s an unknown trade // if (Result == EResult.Unknown) // Result = EResult.Open; // // If the trade is pending, look for an entry trigger // if (Result == EResult.LimitOrder) // { // if ((TradeType == TradeType.Buy && candle.Close >= EP) || // (TradeType == TradeType.Sell && candle.Close <= EP)) // { // Result = EResult.Open; // EntryIndex = candle.Index; // } // } // if (Result == EResult.StopEntryOrder) // { // } // // Trade has closed out // if (Result == EResult.HitSL || Result == EResult.HitTP) // return; // // Otherwise, if this is an open trade. // if (Result == EResult.Open) // { // // Assume the worst for order of prices within a candle // var prices = TradeType.Sign() > 0 // ? new[] {candle.Open, candle.Low, candle.High, candle.Close } // : new[] {candle.Open, candle.High, candle.Low, candle.Close }; // // Check each price value against the trade // var sign = TradeType.Sign(); // foreach (var p in prices) // { // // If the trade is a buy, then it closes at the bid price. // var price = sign > 0 ? (QuoteCurrency)p : p + Instrument.Spread; // // SL hit // if (SL != null && sign * (price - SL.Value) < 0) // { // NetProfit = sign * Instrument.Symbol.QuoteToAcct(SL.Value - EP) * Volume; // MaxAdverseExcursion = sign * (EP - SL.Value); // Result = EResult.HitSL; // Expiration = candle.TimestampUTC.DateTime; // break; // } // // TP hit // if (TP != null && sign * (price - TP.Value) > 0) // { // NetProfit = sign * Instrument.Symbol.QuoteToAcct(TP.Value - EP) * Volume; // MaxFavourableExcursion = sign * (TP.Value - EP); // Result = EResult.HitTP; // Expiration = candle.TimestampUTC.DateTime; // break; // } // // Update the current profit // NetProfit = sign * Instrument.Symbol.QuoteToAcct(price - EP) * Volume; // // Record the peaks // MaxFavourableExcursion = Math.Max(MaxFavourableExcursion, sign * (price - EP)); // MaxAdverseExcursion = Math.Max(MaxAdverseExcursion, sign * (EP - price)); // } // ExitIndex = candle.Index; // } //} /// <summary>Simulate the behaviour of this trade by adding a stream of price ticks</summary> public void Simulate(PriceTick price) { var sign = TradeType.Sign(); // Adding a price tick 'open's an unknown trade if (Result == EResult.Unknown) { Result = EResult.Open; } // If the trade is pending, look for an entry trigger if ((Result == EResult.LimitOrder && Math.Sign(EP - price.Price(sign)) == sign) || (Result == EResult.StopEntryOrder && Math.Sign(price.Price(sign) - EP) == sign)) { Result = EResult.Open; EntryIndex = price.Index; } // Trade has closed out if (Result == EResult.HitSL || Result == EResult.HitTP) { return; } // Otherwise, if this is an open trade. if (Result == EResult.Open) { // SL hit if (SL != null && sign * (price.Price(-sign) - SL.Value) < 0) { NetProfit = sign * Instrument.Symbol.QuoteToAcct(SL.Value - EP) * Volume; MaxAdverseExcursion = sign * (EP - SL.Value); Result = EResult.HitSL; Expiration = price.TimestampUTC.DateTime; } // TP hit else if (TP != null && sign * (price.Price(-sign) - TP.Value) > 0) { NetProfit = sign * Instrument.Symbol.QuoteToAcct(TP.Value - EP) * Volume; MaxFavourableExcursion = sign * (TP.Value - EP); Result = EResult.HitTP; Expiration = price.TimestampUTC.DateTime; } // Update the current profit else { NetProfit = sign * Instrument.Symbol.QuoteToAcct(price.Price(-sign) - EP) * Volume; // Record the peaks MaxFavourableExcursion = Math.Max(MaxFavourableExcursion, sign * (price.Price(-sign) - EP)); MaxAdverseExcursion = Math.Max(MaxAdverseExcursion, sign * (EP - price.Price(-sign))); } ExitIndex = price.Index; } }
/// <summary> /// Create a trade with automatic SL and TP levels set. /// SL/TP levels are set based on the current account balance (even if 'idx' != 0)</summary> /// <param name="instr">The instrument to be traded</param> /// <param name="tt">Whether to buy or sell</param> /// <param name="label">Optional. An identifying name for the trade</param> /// <param name="ep">Optional. The price at which the trade was entered. (default is current ask/bid price)</param> /// <param name="sl">Optional. The stop loss (absolute) to use instead of automatically finding one</param> /// <param name="tp">Optional. The take profit (absolute) to use instead of automatically finding one</param> /// <param name="risk">Optional. Scaling factor for the amount to risk. (default is 1.0)</param> /// <param name="comment">Optional. A comment/tag associated with the trade</param> /// <param name="idx">Optional. The instrument index of when the trade was created. (default is the current time)</param> public Trade(Instrument instr, TradeType tt, string label = null, QuoteCurrency?ep = null, QuoteCurrency?sl = null, QuoteCurrency?tp = null, double?risk = null, string comment = null, Idx?idx = null, EResult result = EResult.Unknown) : this(instr, tt, label, 0, null, null, 0, comment : comment, idx : idx, result : result) { try { var bot = instr.Bot; var sign = tt.Sign(); Idx index = idx ?? 0; bot.Debugging.Trace("Creating Trade (Index = {0})".Fmt(TradeIndex)); // If the index == 0, add the fractional index amount if (index == 0) { var ticks_elapsed = bot.UtcNow.Ticks - instr.Latest.Timestamp; var ticks_per_candle = instr.TimeFrame.ToTicks(); EntryIndex += Maths.Clamp((double)ticks_elapsed / ticks_per_candle, 0.0, 1.0); ExitIndex = EntryIndex; } // Set the trade entry price EP = ep ?? (index == 0 ? (QuoteCurrency)instr.CurrentPrice(sign) // Use the latest price : (QuoteCurrency)instr[index].Open + (sign > 0 ? instr.Symbol.Spread : 0)); // Use the open price of the candle at 'index' // Choose a risk scaler risk = risk ?? 1.0; // Find the account currency value of the available risk var balance_to_risk = bot.Broker.BalanceToRisk * risk.Value; if (balance_to_risk == 0) { throw new Exception("Insufficient available risk. Current Risk: {0}%, Maximum Risk: {1}%".Fmt(100 * bot.Broker.TotalRiskFrac, 100.0 * bot.Settings.MaxRiskFrac)); } // Require the SL to be at least 2 * the median candle size var volatility = instr.Symbol.QuoteToAcct(2 * instr.MCS * instr.Symbol.VolumeMin); if (balance_to_risk < volatility) { throw new Exception("Insufficient available risk. Volatility: {0}, Balance To Risk: {1}".Fmt(volatility, balance_to_risk)); } // Get the instrument to recommend trade exit conditions var exit = instr.ChooseTradeExit(tt, EP, idx: index, risk: risk); TP = tp != null ? tp.Value : exit.TP; SL = sl != null ? sl.Value : exit.SL; Volume = sl != null?instr.Bot.Broker.ChooseVolume(instr, sl.Value / risk.Value) : exit.Volume; } catch (Exception ex) { Error = ex; } }
/// <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 normalised value ([-1,+1]) of this order at 'price'. Only valid if the order has SL and TP levels</summary> public double ValueFrac(PriceTick price) { if (SL == null || TP == null) { return(0.0); } var sign = TradeType.Sign(); var win = Maths.Frac(EP, price.Price(+sign), TP.Value); var los = Maths.Frac(EP, price.Price(-sign), SL.Value); if (win < 0) { return(-los); } if (los < 0) { return(+win); } return(win > los ? win : -los); }