Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Common/Securities/SecurityHolding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public virtual decimal HoldingsCost
/// </summary>
public virtual decimal UnleveredHoldingsCost
{
get { return HoldingsCost/Leverage; }
get { return HoldingsCost / Leverage; }
}

/// <summary>
Expand Down Expand Up @@ -358,7 +358,7 @@ public virtual decimal UnrealizedProfitPercent
get
{
if (AbsoluteHoldingsCost == 0) return 0m;
return UnrealizedProfit/AbsoluteHoldingsCost;
return UnrealizedProfit / AbsoluteHoldingsCost;
}
}

Expand Down Expand Up @@ -420,7 +420,7 @@ public void SetLastTradeProfit(decimal lastTradeProfit)
/// </summary>
public virtual void SetHoldings(decimal averagePrice, int quantity)
{
SetHoldings(averagePrice, (decimal) quantity);
SetHoldings(averagePrice, (decimal)quantity);
}

/// <summary>
Expand Down Expand Up @@ -494,8 +494,13 @@ public virtual decimal TotalCloseProfit(bool includeFees = true, decimal? exitPr
feesInAccountCurrency = _currencyConverter.ConvertToAccountCurrency(liquidationFees).Amount;
}

// if we are long, we would need to sell against the bid
var price = IsLong ? _security.BidPrice : _security.AskPrice;
// Outside market hours, bid/ask spreads are unreliable so price stays 0 and falls back to last trade price. Futures are excluded since they support extended hours trading.
var price = 0m;
if (_security.Type == SecurityType.Future || _security.Exchange.ExchangeOpen)
{
// if we are long, we would need to sell against the bid
price = IsLong ? _security.BidPrice : _security.AskPrice;
}
if (price == 0)
{
// Bid/Ask prices can both be equal to 0. This usually happens when we request our holdings from
Expand Down
50 changes: 47 additions & 3 deletions Tests/Common/Securities/SecurityHoldingTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
Expand Down Expand Up @@ -82,7 +82,35 @@ public void Raises_QuantityChanged_WhenSetHoldingsCalled()
Assert.AreEqual(firstPrice, second.PreviousAveragePrice);
}

private Security GetSecurity<T>(Symbol symbol, Resolution resolution)
[TestCase(true)]
[TestCase(false)]
public void TotalCloseProfitRespectMarketHours(bool isMarketOpen)
{
var security = GetSecurity<QuantConnect.Securities.Equity.Equity>(Symbols.SPY, Resolution.Daily, isMarketOpen);
var holding = new SecurityHolding(security, new IdentityCurrencyConverter(Currencies.USD));

var averagePrice = 100m;
var bid = 90m;
var ask = 110m;
var lastTradePrice = 105m;

var quoteTick = new Tick(DateTime.Now, security.Symbol, 0m, bid, ask);
var tradeTick = new Tick { Time = DateTime.Now, Symbol = security.Symbol, TickType = TickType.Trade, Value = lastTradePrice };

security.SetMarketPrice(quoteTick);
security.SetMarketPrice(tradeTick);

var expectedLong = isMarketOpen ? (bid - averagePrice) * 100m : (lastTradePrice - averagePrice) * 100m;
var expectedShort = isMarketOpen ? (ask - averagePrice) * -100m : (lastTradePrice - averagePrice) * -100m;

holding.SetHoldings(averagePrice, 100m);
Assert.AreEqual(expectedLong, holding.TotalCloseProfit(includeFees: false));

holding.SetHoldings(averagePrice, -100m);
Assert.AreEqual(expectedShort, holding.TotalCloseProfit(includeFees: false));
}

private Security GetSecurity<T>(Symbol symbol, Resolution resolution, bool marketAlwaysOpen = true)
{
var subscriptionDataConfig = new SubscriptionDataConfig(
typeof(T),
Expand All @@ -94,8 +122,24 @@ private Security GetSecurity<T>(Symbol symbol, Resolution resolution)
true,
false);

SecurityExchangeHours exchangeHours;
if (marketAlwaysOpen)
{
exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.Utc);
}
else
{
var days = Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>();
exchangeHours = new SecurityExchangeHours(
TimeZones.Utc,
Enumerable.Empty<DateTime>(),
days.ToDictionary(d => d, d => LocalMarketHours.ClosedAllDay(d)),
new Dictionary<DateTime, TimeSpan>(),
new Dictionary<DateTime, TimeSpan>());
}

var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
exchangeHours,
subscriptionDataConfig,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
Expand Down
Loading