Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e247689f | ||
|
|
297207badb | ||
|
|
ecb8e8da41 | ||
|
|
c6c4c1edec | ||
|
|
c02a8faedb | ||
|
|
e620d3fd7b | ||
|
|
4b6643312e | ||
|
|
4b2f203322 | ||
|
|
7eefdebae5 | ||
|
|
7f17838ad0 | ||
|
|
5a1ebcbaad |
@@ -119,9 +119,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected holdings cost {btcUsdHoldings.HoldingsCost}");
|
||||
}
|
||||
// margin used is based on the maintenance rate
|
||||
if (Math.Abs(btcUsdHoldings.AbsoluteHoldingsCost * 0.05m - marginUsed) > 1
|
||||
|| _btcUsd.BuyingPowerModel.GetMaintenanceMargin(_btcUsd) != marginUsed)
|
||||
if (_btcUsd.BuyingPowerModel.GetMaintenanceMargin(_btcUsd) != marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected margin used {marginUsed}");
|
||||
}
|
||||
@@ -142,8 +140,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected holdings cost {adaUsdtHoldings.HoldingsCost}");
|
||||
}
|
||||
if (Math.Abs(adaUsdtHoldings.AbsoluteHoldingsCost * 0.05m - marginUsed) > 1
|
||||
|| _adaUsdt.BuyingPowerModel.GetMaintenanceMargin(_adaUsdt) != marginUsed)
|
||||
if (_adaUsdt.BuyingPowerModel.GetMaintenanceMargin(_adaUsdt) != marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected margin used {marginUsed}");
|
||||
}
|
||||
@@ -273,7 +270,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$0.65"},
|
||||
{"Estimated Strategy Capacity", "$500000000.00"},
|
||||
{"Estimated Strategy Capacity", "$620000000.00"},
|
||||
{"Lowest Capacity Asset", "ADAUSDT 18R"},
|
||||
{"Portfolio Turnover", "0.16%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
if (!Portfolio.Invested && Transactions.OrdersCount == 0)
|
||||
{
|
||||
var ticket = Buy(_adaUsdt.Symbol, 100000);
|
||||
if(ticket.Status != OrderStatus.Invalid)
|
||||
if (ticket.Status != OrderStatus.Invalid)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected valid order {ticket}, should fail due to margin not sufficient");
|
||||
}
|
||||
@@ -114,8 +114,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected holdings cost {adaUsdtHoldings.HoldingsCost}");
|
||||
}
|
||||
if (Math.Abs(adaUsdtHoldings.AbsoluteHoldingsCost * 0.05m - marginUsed) > 1
|
||||
|| _adaUsdt.BuyingPowerModel.GetMaintenanceMargin(_adaUsdt) != marginUsed)
|
||||
if (_adaUsdt.BuyingPowerModel.GetMaintenanceMargin(_adaUsdt) != marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected margin used {marginUsed}");
|
||||
}
|
||||
@@ -236,7 +235,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$0.61"},
|
||||
{"Estimated Strategy Capacity", "$370000000.00"},
|
||||
{"Estimated Strategy Capacity", "$460000000.00"},
|
||||
{"Lowest Capacity Asset", "ADAUSDT 18R"},
|
||||
{"Portfolio Turnover", "0.12%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
|
||||
@@ -112,9 +112,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected holdings cost {btcUsdHoldings.HoldingsCost}");
|
||||
}
|
||||
// margin used is based on the maintenance rate
|
||||
if (Math.Abs(btcUsdHoldings.AbsoluteHoldingsCost * 0.05m - marginUsed) > 1
|
||||
|| _btcUsd.BuyingPowerModel.GetMaintenanceMargin(_btcUsd) != marginUsed)
|
||||
if (_btcUsd.BuyingPowerModel.GetMaintenanceMargin(_btcUsd) != marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected margin used {marginUsed}");
|
||||
}
|
||||
@@ -135,8 +133,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected holdings cost {btcUsdtHoldings.HoldingsCost}");
|
||||
}
|
||||
if (Math.Abs(btcUsdtHoldings.AbsoluteHoldingsCost * 0.05m - marginUsed) > 1
|
||||
|| _btcUsdt.BuyingPowerModel.GetMaintenanceMargin(_btcUsdt) != marginUsed)
|
||||
if (_btcUsdt.BuyingPowerModel.GetMaintenanceMargin(_btcUsdt) != marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected margin used {marginUsed}");
|
||||
}
|
||||
@@ -261,7 +258,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$0.60"},
|
||||
{"Estimated Strategy Capacity", "$200000000.00"},
|
||||
{"Estimated Strategy Capacity", "$100000000.00"},
|
||||
{"Lowest Capacity Asset", "BTCUSDT 2V3"},
|
||||
{"Portfolio Turnover", "1.08%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$0.15"},
|
||||
{"Estimated Strategy Capacity", "$3400000000.00"},
|
||||
{"Estimated Strategy Capacity", "$4300000000.00"},
|
||||
{"Lowest Capacity Asset", "ADAUSDT 18R"},
|
||||
{"Portfolio Turnover", "0.02%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$0.15"},
|
||||
{"Estimated Strategy Capacity", "$330000000.00"},
|
||||
{"Estimated Strategy Capacity", "$410000000.00"},
|
||||
{"Lowest Capacity Asset", "ADAUSDT 18R"},
|
||||
{"Portfolio Turnover", "0.02%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities.CryptoFuture;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm asserting that margin used and margin remaining update correctly when
|
||||
/// changing leverage on a crypto future
|
||||
/// </summary>
|
||||
public class CryptoFutureLeverageBasedMarginRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private CryptoFuture _cryptoFuture;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2022, 12, 13);
|
||||
SetEndDate(2022, 12, 13);
|
||||
|
||||
SetTimeZone(TimeZones.Utc);
|
||||
|
||||
SetAccountCurrency("USDT");
|
||||
SetCash(200);
|
||||
|
||||
SetBrokerageModel(BrokerageName.BinanceFutures, AccountType.Margin);
|
||||
|
||||
_cryptoFuture = AddCryptoFuture("ADAUSDT");
|
||||
_cryptoFuture.SetLeverage(10);
|
||||
}
|
||||
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
if (_cryptoFuture.Price == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
SetHoldings(_cryptoFuture.Symbol, 10); // Buy all we can with our margin (leverage is 10)
|
||||
|
||||
var marginUsed = Portfolio.TotalMarginUsed;
|
||||
var marginRemaining = Portfolio.MarginRemaining;
|
||||
|
||||
if (marginRemaining > 0)
|
||||
{
|
||||
throw new RegressionTestException($"Expected no margin remaining after buying with full leverage. " +
|
||||
$"Actual margin remaining is {marginRemaining}");
|
||||
}
|
||||
|
||||
_cryptoFuture.SetLeverage(20);
|
||||
|
||||
var newMarginUsed = Portfolio.TotalMarginUsed;
|
||||
var newMarginRemaining = Portfolio.MarginRemaining;
|
||||
|
||||
if (newMarginUsed >= marginUsed)
|
||||
{
|
||||
throw new RegressionTestException($"Expected margin used to decrease after increasing leverage. " +
|
||||
$"Previous margin used: {marginUsed}, new margin used: {newMarginUsed}");
|
||||
}
|
||||
|
||||
if (newMarginRemaining <= 0 || newMarginRemaining <= marginRemaining)
|
||||
{
|
||||
throw new RegressionTestException($"Expected margin remaining to increase after increasing leverage. " +
|
||||
$"Previous margin remaining: {marginRemaining}, new margin remaining: {newMarginRemaining}");
|
||||
}
|
||||
|
||||
var holdingsQuantity = _cryptoFuture.Holdings.AbsoluteQuantity;
|
||||
|
||||
SetHoldings(_cryptoFuture.Symbol, 20); // Buy all we can with our margin (new leverage is 20)
|
||||
|
||||
var newHoldingsQuantity = _cryptoFuture.Holdings.AbsoluteQuantity;
|
||||
|
||||
if (newHoldingsQuantity <= holdingsQuantity)
|
||||
{
|
||||
throw new RegressionTestException($"Expected holdings quantity to increase after increasing leverage and buying more. " +
|
||||
$"Previous holdings quantity: {holdingsQuantity}, new holdings quantity: {newHoldingsQuantity}");
|
||||
}
|
||||
|
||||
newMarginRemaining = Portfolio.MarginRemaining;
|
||||
|
||||
if (marginRemaining > 0)
|
||||
{
|
||||
throw new RegressionTestException($"Expected no margin remaining after buying with full leverage. " +
|
||||
$"Actual margin remaining is {newMarginRemaining}");
|
||||
}
|
||||
|
||||
// We are done testing, exit the algorithm
|
||||
Quit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public List<Language> Languages { get; } = new() { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public long DataPoints => 4;
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Final status of the algorithm
|
||||
/// </summary>
|
||||
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Start Equity", "200"},
|
||||
{"End Equity", "195.58"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Sortino Ratio", "0"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0"},
|
||||
{"Beta", "0"},
|
||||
{"Annual Standard Deviation", "0"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "₮1.57"},
|
||||
{"Estimated Strategy Capacity", "₮0"},
|
||||
{"Lowest Capacity Asset", "ADAUSDT 18R"},
|
||||
{"Portfolio Turnover", "2009.51%"},
|
||||
{"Drawdown Recovery", "0"},
|
||||
{"OrderListHash", "f92ad762f77fbf4ee13b1e89a78cb1eb"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,17 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
throw new RegressionTestException($"Unexpected {nameof(Fundamental)} data count {history[0].Values.Count}, expected 2!");
|
||||
}
|
||||
|
||||
// assert all fundamental API data match
|
||||
foreach (var ticker in new[] {"AAPL", "SPY"})
|
||||
{
|
||||
if (!history[0].TryGetValue(ticker, out var fundamental) || fundamental.Price == 0)
|
||||
var fundamentalThroughSecurity = Securities[ticker].Fundamentals;
|
||||
var fundamentalThroughAlgo = Fundamentals(ticker);
|
||||
|
||||
if (!history[1].TryGetValue(ticker, out var fundamental) || fundamental.Price == 0
|
||||
|| fundamentalThroughSecurity.Price != fundamental.Price
|
||||
|| fundamentalThroughSecurity.EndTime != fundamental.EndTime
|
||||
|| fundamentalThroughAlgo.Price != fundamental.Price
|
||||
|| fundamentalThroughAlgo.EndTime != fundamental.EndTime)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected {ticker} fundamental data");
|
||||
}
|
||||
@@ -142,7 +150,24 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
var sortedByPeRatio = sortedByDollarVolume.OrderByDescending(x => x.ValuationRatios.PERatio);
|
||||
|
||||
// take the top entries from our sorted collection
|
||||
var topFine = sortedByPeRatio.Take(NumberOfSymbolsFundamental);
|
||||
var topFine = sortedByPeRatio.Take(NumberOfSymbolsFundamental).ToArray();
|
||||
|
||||
// selection fundamental data should match all other APIs
|
||||
foreach (var fundamentalPoint in topFine)
|
||||
{
|
||||
var symbol = fundamentalPoint.Symbol;
|
||||
var fundamentalThroughSecurity = Securities.ContainsKey(symbol) ? Securities[symbol].Fundamentals : null;
|
||||
var fundamentalThroughAlgo = Fundamentals(symbol);
|
||||
|
||||
if (fundamentalPoint.Price == 0
|
||||
|| fundamentalThroughSecurity != null && (fundamentalThroughSecurity.Price != fundamentalPoint.Price
|
||||
|| fundamentalThroughSecurity.EndTime != fundamentalPoint.EndTime)
|
||||
|| fundamentalThroughAlgo.Price != fundamentalPoint.Price
|
||||
|| fundamentalThroughAlgo.EndTime != fundamentalPoint.EndTime)
|
||||
{
|
||||
throw new RegressionTestException($"Unexpected {symbol} fundamental data in selection");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to return only the symbol objects
|
||||
return topFine.Select(x => x.Symbol);
|
||||
|
||||
@@ -82,7 +82,7 @@ class BasicTemplateCryptoFutureAlgorithm(QCAlgorithm):
|
||||
raise AssertionError(f"Unexpected holdings cost {self.btc_usd_holdings.holdings_cost}")
|
||||
|
||||
# margin used is based on the maintenance rate
|
||||
if (abs(self.btc_usd_holdings.absolute_holdings_cost * 0.05 - self.margin_used) > 1) or (BuyingPowerModelExtensions.get_maintenance_margin(self.btc_usd.buying_power_model, self.btc_usd) != self.margin_used):
|
||||
if BuyingPowerModelExtensions.get_maintenance_margin(self.btc_usd.buying_power_model, self.btc_usd) != self.margin_used:
|
||||
raise AssertionError(f"Unexpected margin used {self.margin_used}")
|
||||
|
||||
self.buy(self.ada_usdt.symbol, 1000)
|
||||
@@ -99,7 +99,7 @@ class BasicTemplateCryptoFutureAlgorithm(QCAlgorithm):
|
||||
if abs(self.ada_usdt_holdings.absolute_holdings_cost - self.holdings_value_usdt) > 1:
|
||||
raise AssertionError(f"Unexpected holdings cost {self.ada_usdt_holdings.holdings_cost}")
|
||||
|
||||
if (abs(self.ada_usdt_holdings.absolute_holdings_cost * 0.05 - self.margin_used) > 1) or (BuyingPowerModelExtensions.get_maintenance_margin(self.ada_usdt.buying_power_model, self.ada_usdt) != self.margin_used):
|
||||
if BuyingPowerModelExtensions.get_maintenance_margin(self.ada_usdt.buying_power_model, self.ada_usdt) != self.margin_used:
|
||||
raise AssertionError(f"Unexpected margin used {self.margin_used}")
|
||||
|
||||
# position just opened should be just spread here
|
||||
|
||||
@@ -81,7 +81,7 @@ class BasicTemplateCryptoFutureHourlyAlgorithm(QCAlgorithm):
|
||||
if abs(self.ada_usdt_holdings.absolute_holdings_cost - self.holdings_value_usdt) > 1:
|
||||
raise AssertionError(f"Unexpected holdings cost {self.ada_usdt_holdings.holdings_cost}")
|
||||
|
||||
if (abs(self.ada_usdt_holdings.absolute_holdings_cost * 0.05 - self.margin_used) > 1) or (BuyingPowerModelExtensions.get_maintenance_margin(self.ada_usdt.buying_power_model, self.ada_usdt) != self.margin_used):
|
||||
if BuyingPowerModelExtensions.get_maintenance_margin(self.ada_usdt.buying_power_model, self.ada_usdt) != self.margin_used:
|
||||
raise AssertionError(f"Unexpected margin used {self.margin_used}")
|
||||
|
||||
# position just opened should be just spread here
|
||||
|
||||
@@ -79,9 +79,7 @@ class BybitCryptoFuturesRegressionAlgorithm(QCAlgorithm):
|
||||
raise AssertionError(f"Unexpected TotalSaleVolume {btc_usd_holdings.total_sale_volume}")
|
||||
if abs(btc_usd_holdings.absolute_holdings_cost - holdings_value_btc_usd) > 1:
|
||||
raise AssertionError(f"Unexpected holdings cost {btc_usd_holdings.holdings_cost}")
|
||||
# margin used is based on the maintenance rate
|
||||
if (abs(btc_usd_holdings.absolute_holdings_cost * 0.05 - margin_used) > 1 or
|
||||
not isclose(self.btc_usd.buying_power_model.get_maintenance_margin(MaintenanceMarginParameters.for_current_holdings(self.btc_usd)).value, margin_used)):
|
||||
if not isclose(self.btc_usd.buying_power_model.get_maintenance_margin(MaintenanceMarginParameters.for_current_holdings(self.btc_usd)).value, margin_used):
|
||||
raise AssertionError(f"Unexpected margin used {margin_used}")
|
||||
|
||||
self.buy(self.btc_usdt.symbol, 0.01)
|
||||
@@ -96,8 +94,7 @@ class BybitCryptoFuturesRegressionAlgorithm(QCAlgorithm):
|
||||
raise AssertionError(f"Unexpected TotalSaleVolume {btc_usdt_holdings.total_sale_volume}")
|
||||
if abs(btc_usdt_holdings.absolute_holdings_cost - holdings_value_usdt) > 1:
|
||||
raise AssertionError(f"Unexpected holdings cost {btc_usdt_holdings.holdings_cost}")
|
||||
if (abs(btc_usdt_holdings.absolute_holdings_cost * 0.05 - margin_used) > 1 or
|
||||
not isclose(self.btc_usdt.buying_power_model.get_maintenance_margin(MaintenanceMarginParameters.for_current_holdings(self.btc_usdt)).value, margin_used)):
|
||||
if not isclose(self.btc_usdt.buying_power_model.get_maintenance_margin(MaintenanceMarginParameters.for_current_holdings(self.btc_usdt)).value, margin_used):
|
||||
raise AssertionError(f"Unexpected margin used {margin_used}")
|
||||
|
||||
# position just opened should be just spread here
|
||||
|
||||
@@ -3379,7 +3379,7 @@ namespace QuantConnect.Algorithm
|
||||
[DocumentationAttribute(SecuritiesAndPortfolio)]
|
||||
public Fundamental Fundamentals(Symbol symbol)
|
||||
{
|
||||
return new Fundamental(Time, symbol) { EndTime = Time };
|
||||
return Fundamental.ForDate(Time, symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Brokerages
|
||||
{
|
||||
@@ -25,6 +26,18 @@ namespace QuantConnect.Brokerages
|
||||
/// </summary>
|
||||
public class WolverineBrokerageModel : DefaultBrokerageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported order types
|
||||
/// </summary>
|
||||
private HashSet<OrderType> SupportedOrderTypes { get; } =
|
||||
[
|
||||
OrderType.Market,
|
||||
OrderType.MarketOnClose,
|
||||
OrderType.Limit,
|
||||
OrderType.StopMarket,
|
||||
OrderType.StopLimit
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for Wolverine brokerage model
|
||||
/// </summary>
|
||||
@@ -51,23 +64,18 @@ namespace QuantConnect.Brokerages
|
||||
return false;
|
||||
}
|
||||
|
||||
message = null;
|
||||
if (security.Type != SecurityType.Equity)
|
||||
if (security.Type != SecurityType.Equity && security.Type != SecurityType.Option)
|
||||
{
|
||||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
|
||||
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (order.Type != OrderType.Market)
|
||||
if (!SupportedOrderTypes.Contains(order.Type))
|
||||
{
|
||||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
|
||||
Messages.WolverineBrokerageModel.UnsupportedOrderType(order));
|
||||
|
||||
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, SupportedOrderTypes));
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanSubmitOrder(security, order, out message);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,38 @@ namespace QuantConnect
|
||||
{ Market.DYDX , _stableCoinsWithoutPairsdYdX}
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _dollarStablePairs = ["USDT", "USDC", USD];
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not certain symbol is a StableCoin without pair in a given market
|
||||
/// </summary>
|
||||
/// <param name="accountCurrency">The account currency</param>
|
||||
/// <param name="cashSymbol">The target cash symbol</param>
|
||||
/// <param name="market">The market in which we want to search for that StableCoin</param>
|
||||
/// <returns>True if the given symbol is a StableCoin without pair in the given market</returns>
|
||||
public static bool IsStableCoinWithoutPair(string accountCurrency, string cashSymbol, string market)
|
||||
{
|
||||
IEnumerable<string> _targets;
|
||||
if (_dollarStablePairs.Contains(accountCurrency))
|
||||
{
|
||||
// let's be polite and handle USDT/USDC/USD, this is internal
|
||||
_targets = _dollarStablePairs.Where(x => x != cashSymbol).SelectMany(x => new[] { x + cashSymbol, cashSymbol + x }).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
_targets = [accountCurrency + cashSymbol, cashSymbol + accountCurrency];
|
||||
}
|
||||
|
||||
foreach (var target in _targets)
|
||||
{
|
||||
if (IsStableCoinWithoutPair(target, market))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not certain symbol is a StableCoin without pair in a given market
|
||||
/// </summary>
|
||||
|
||||
@@ -71,6 +71,18 @@ namespace QuantConnect.Data.Fundamental
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance
|
||||
/// </summary>
|
||||
/// <param name="time">The current time</param>
|
||||
/// <param name="symbol">The associated symbol</param>
|
||||
public static Fundamental ForDate(DateTime time, Symbol symbol)
|
||||
{
|
||||
// Important: set EndTime to time so that time is previous day midnight, if we just set time, EndTime would be NEXT day midnight.
|
||||
// Note: data for T date is available on T+1 date, fundamental selection also handles this, see BaseDataCollectionSubscriptionEnumeratorFactory
|
||||
return new Fundamental(time, symbol) { EndTime = time };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the URL string source of the file. This will be converted to a stream
|
||||
/// </summary>
|
||||
|
||||
@@ -25,5 +25,10 @@ namespace QuantConnect.Orders
|
||||
/// The exchange post fix to apply if any
|
||||
/// </summary>
|
||||
public string ExchangePostFix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can optionally specify the position side in the order direction (buy-to-open, sell-to-close, etc.) instead of the default handling
|
||||
/// </summary>
|
||||
public OrderPosition? PositionSide { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,15 @@ namespace QuantConnect.Securities
|
||||
.Concat(cryptoEntries)
|
||||
.ToList();
|
||||
|
||||
// Special case for crypto markets without direct pairs (They wont be found by the above)
|
||||
// This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security.
|
||||
// Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs
|
||||
if (marketMap.TryGetValue(SecurityType.Crypto, out var market) && Currencies.IsStableCoinWithoutPair(accountCurrency, Symbol, market))
|
||||
{
|
||||
CurrencyConversion = ConstantCurrencyConversion.Identity(accountCurrency, Symbol);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!potentialEntries.Any(x =>
|
||||
Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) ||
|
||||
Symbol == x.Value.QuoteCurrency))
|
||||
@@ -285,18 +294,6 @@ namespace QuantConnect.Securities
|
||||
return null;
|
||||
}
|
||||
|
||||
// Special case for crypto markets without direct pairs (They wont be found by the above)
|
||||
// This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security.
|
||||
// Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs
|
||||
if (marketMap.TryGetValue(SecurityType.Crypto, out var market)
|
||||
&&
|
||||
(Currencies.IsStableCoinWithoutPair(Symbol + accountCurrency, market)
|
||||
|| Currencies.IsStableCoinWithoutPair(accountCurrency + Symbol, market)))
|
||||
{
|
||||
CurrencyConversion = ConstantCurrencyConversion.Identity(accountCurrency, Symbol);
|
||||
return null;
|
||||
}
|
||||
|
||||
var requiredSecurities = new List<SubscriptionDataConfig>();
|
||||
|
||||
var potentials = potentialEntries
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace QuantConnect.Securities.CryptoFuture
|
||||
/// <returns>True if the security is a crypto coin future</returns>
|
||||
private static bool IsCryptoCoinFuture(string quoteCurrency)
|
||||
{
|
||||
return quoteCurrency != "USDT" && quoteCurrency != "BUSD";
|
||||
return quoteCurrency != "USDT" && quoteCurrency != "BUSD" && quoteCurrency != "USDC";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,20 +24,25 @@ namespace QuantConnect.Securities.CryptoFuture
|
||||
/// </summary>
|
||||
public class CryptoFutureMarginModel : SecurityMarginModel
|
||||
{
|
||||
private readonly decimal _maintenanceMarginRate;
|
||||
private readonly decimal _maintenanceAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance
|
||||
/// </summary>
|
||||
/// <param name="leverage">The leverage to use, used on initial margin requirements, default 25x</param>
|
||||
/// <param name="maintenanceMarginRate">The maintenance margin rate, default 5%</param>
|
||||
/// <param name="maintenanceAmount">The maintenance amount which will reduce maintenance margin requirements, default 0</param>
|
||||
public CryptoFutureMarginModel(decimal leverage = 25, decimal maintenanceMarginRate = 0.05m, decimal maintenanceAmount = 0)
|
||||
[Obsolete("This constructor is deprecated, please use the overload without maintenanceMarginRate and maintenanceAmount parameters.")]
|
||||
public CryptoFutureMarginModel(decimal leverage, decimal maintenanceMarginRate = 0.05m, decimal maintenanceAmount = 0)
|
||||
: base(leverage, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance
|
||||
/// </summary>
|
||||
/// <param name="leverage">The leverage to use, used on initial margin requirements, default 25x</param>
|
||||
public CryptoFutureMarginModel(decimal leverage = 25)
|
||||
: base(leverage, 0)
|
||||
{
|
||||
_maintenanceAmount = maintenanceAmount;
|
||||
_maintenanceMarginRate = maintenanceMarginRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,17 +52,7 @@ namespace QuantConnect.Securities.CryptoFuture
|
||||
/// <returns>The maintenance margin required for the option</returns>
|
||||
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
|
||||
{
|
||||
var security = parameters.Security;
|
||||
var quantity = parameters.Quantity;
|
||||
if (security?.GetLastData() == null || quantity == 0m)
|
||||
{
|
||||
return MaintenanceMargin.Zero;
|
||||
}
|
||||
|
||||
var positionValue = security.Holdings.GetQuantityValue(quantity, security.Price);
|
||||
var marginRequirementInCollateral = Math.Abs(positionValue.Amount) * _maintenanceMarginRate - _maintenanceAmount;
|
||||
|
||||
return new MaintenanceMargin(marginRequirementInCollateral * positionValue.Cash.ConversionRate);
|
||||
return new MaintenanceMargin(GetInitialMarginRequirement(new InitialMarginParameters(parameters.Security, parameters.Quantity)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -597,7 +597,7 @@ namespace QuantConnect.Securities
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Fundamental(LocalTime, Symbol);
|
||||
return Fundamental.ForDate(LocalTime, Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,10 +119,7 @@ namespace QuantConnect.Statistics
|
||||
/// <summary>
|
||||
/// Returns the amount of profit given back before the trade was closed
|
||||
/// </summary>
|
||||
public decimal EndTradeDrawdown
|
||||
{
|
||||
get { return ProfitLoss - MFE; }
|
||||
}
|
||||
public decimal EndTradeDrawdown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the trade was profitable (is a win) or not (a loss)
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Statistics
|
||||
{
|
||||
@@ -29,12 +29,40 @@ namespace QuantConnect.Statistics
|
||||
/// </summary>
|
||||
public class TradeBuilder : ITradeBuilder
|
||||
{
|
||||
private class TradeState
|
||||
{
|
||||
internal Trade Trade { get; set; }
|
||||
internal decimal MaxProfit { get; set; }
|
||||
internal decimal MaxDrawdown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the drawdown state given the current profit
|
||||
/// </summary>
|
||||
public void UpdateDrawdown(decimal currentProfit)
|
||||
{
|
||||
if (currentProfit < MaxProfit)
|
||||
{
|
||||
// There is a drawdown, but we only care about the maximum drawdown
|
||||
var drawdown = MaxProfit - currentProfit;
|
||||
if (drawdown > MaxDrawdown)
|
||||
{
|
||||
MaxDrawdown = drawdown;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// New maximum profit
|
||||
MaxProfit = currentProfit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to manage pending trades and market price updates for a symbol
|
||||
/// </summary>
|
||||
private class Position
|
||||
{
|
||||
internal List<Trade> PendingTrades { get; set; }
|
||||
internal List<TradeState> PendingTrades { get; set; }
|
||||
internal List<OrderEvent> PendingFills { get; set; }
|
||||
internal decimal TotalFees { get; set; }
|
||||
internal decimal MaxPrice { get; set; }
|
||||
@@ -42,7 +70,7 @@ namespace QuantConnect.Statistics
|
||||
|
||||
public Position()
|
||||
{
|
||||
PendingTrades = new List<Trade>();
|
||||
PendingTrades = new List<TradeState>();
|
||||
PendingFills = new List<OrderEvent>();
|
||||
}
|
||||
}
|
||||
@@ -130,6 +158,14 @@ namespace QuantConnect.Statistics
|
||||
position.MaxPrice = price;
|
||||
else if (price < position.MinPrice)
|
||||
position.MinPrice = price;
|
||||
|
||||
for (var i = 0; i < position.PendingTrades.Count; i++)
|
||||
{
|
||||
var tradeState = position.PendingTrades[i];
|
||||
var trade = tradeState.Trade;
|
||||
var currentProfit = trade.Direction == TradeDirection.Long ? price - trade.EntryPrice : trade.EntryPrice - price;
|
||||
tradeState.UpdateDrawdown(currentProfit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,11 +187,13 @@ namespace QuantConnect.Statistics
|
||||
position.MinPrice *= split.SplitFactor;
|
||||
position.MaxPrice *= split.SplitFactor;
|
||||
|
||||
foreach (var trade in position.PendingTrades)
|
||||
foreach (var tradeState in position.PendingTrades)
|
||||
{
|
||||
trade.Quantity /= split.SplitFactor;
|
||||
trade.EntryPrice *= split.SplitFactor;
|
||||
trade.ExitPrice *= split.SplitFactor;
|
||||
tradeState.Trade.Quantity /= split.SplitFactor;
|
||||
tradeState.Trade.EntryPrice *= split.SplitFactor;
|
||||
tradeState.Trade.ExitPrice *= split.SplitFactor;
|
||||
tradeState.MaxProfit *= split.SplitFactor;
|
||||
tradeState.MaxDrawdown *= split.SplitFactor;
|
||||
}
|
||||
|
||||
foreach (var pendingFill in position.PendingFills)
|
||||
@@ -223,17 +261,20 @@ namespace QuantConnect.Statistics
|
||||
// no pending trades for symbol
|
||||
_positions[fill.Symbol] = new Position
|
||||
{
|
||||
PendingTrades = new List<Trade>
|
||||
PendingTrades = new List<TradeState>
|
||||
{
|
||||
new Trade
|
||||
new TradeState
|
||||
{
|
||||
Symbols = [fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = orderFee,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
Trade = new Trade
|
||||
{
|
||||
Symbols = [fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = orderFee,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
}
|
||||
}
|
||||
},
|
||||
MinPrice = fill.FillPrice,
|
||||
@@ -246,18 +287,21 @@ namespace QuantConnect.Statistics
|
||||
|
||||
var index = _matchingMethod == FillMatchingMethod.FIFO ? 0 : position.PendingTrades.Count - 1;
|
||||
|
||||
if (Math.Sign(fill.FillQuantity) == (position.PendingTrades[index].Direction == TradeDirection.Long ? +1 : -1))
|
||||
if (Math.Sign(fill.FillQuantity) == (position.PendingTrades[index].Trade.Direction == TradeDirection.Long ? +1 : -1))
|
||||
{
|
||||
// execution has same direction of trade
|
||||
position.PendingTrades.Add(new Trade
|
||||
position.PendingTrades.Add(new TradeState
|
||||
{
|
||||
Symbols = [fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = orderFee,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
Trade = new Trade
|
||||
{
|
||||
Symbols = [fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = orderFee,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -267,7 +311,8 @@ namespace QuantConnect.Statistics
|
||||
var orderFeeAssigned = false;
|
||||
while (position.PendingTrades.Count > 0 && Math.Abs(totalExecutedQuantity) < fill.AbsoluteFillQuantity)
|
||||
{
|
||||
var trade = position.PendingTrades[index];
|
||||
var tradeState = position.PendingTrades[index];
|
||||
var trade = tradeState.Trade;
|
||||
var absoluteUnexecutedQuantity = fill.AbsoluteFillQuantity - Math.Abs(totalExecutedQuantity);
|
||||
|
||||
if (absoluteUnexecutedQuantity >= trade.Quantity)
|
||||
@@ -285,6 +330,7 @@ namespace QuantConnect.Statistics
|
||||
trade.TotalFees += orderFeeAssigned ? 0 : orderFee;
|
||||
trade.MAE = Math.Round((trade.Direction == TradeDirection.Long ? position.MinPrice - trade.EntryPrice : trade.EntryPrice - position.MaxPrice) * trade.Quantity * conversionRate * multiplier, 2);
|
||||
trade.MFE = Math.Round((trade.Direction == TradeDirection.Long ? position.MaxPrice - trade.EntryPrice : trade.EntryPrice - position.MinPrice) * trade.Quantity * conversionRate * multiplier, 2);
|
||||
trade.EndTradeDrawdown = Math.Round(tradeState.MaxDrawdown * trade.Quantity * conversionRate * multiplier, 2);
|
||||
|
||||
AddNewTrade(trade, fill);
|
||||
}
|
||||
@@ -306,6 +352,7 @@ namespace QuantConnect.Statistics
|
||||
TotalFees = trade.TotalFees + (orderFeeAssigned ? 0 : orderFee),
|
||||
MAE = Math.Round((trade.Direction == TradeDirection.Long ? position.MinPrice - trade.EntryPrice : trade.EntryPrice - position.MaxPrice) * absoluteUnexecutedQuantity * conversionRate * multiplier, 2),
|
||||
MFE = Math.Round((trade.Direction == TradeDirection.Long ? position.MaxPrice - trade.EntryPrice : trade.EntryPrice - position.MinPrice) * absoluteUnexecutedQuantity * conversionRate * multiplier, 2),
|
||||
EndTradeDrawdown = Math.Round(tradeState.MaxDrawdown * absoluteUnexecutedQuantity * conversionRate * multiplier, 2),
|
||||
OrderIds = new HashSet<int>([..trade.OrderIds, fill.OrderId])
|
||||
};
|
||||
|
||||
@@ -325,17 +372,20 @@ namespace QuantConnect.Statistics
|
||||
{
|
||||
// direction reversal
|
||||
fill.FillQuantity -= totalExecutedQuantity;
|
||||
position.PendingTrades = new List<Trade>
|
||||
position.PendingTrades = new List<TradeState>
|
||||
{
|
||||
new Trade
|
||||
new TradeState
|
||||
{
|
||||
Symbols =[fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = 0,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
Trade = new Trade
|
||||
{
|
||||
Symbols =[fill.Symbol],
|
||||
EntryTime = fill.UtcTime,
|
||||
EntryPrice = fill.FillPrice,
|
||||
Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short,
|
||||
Quantity = fill.AbsoluteFillQuantity,
|
||||
TotalFees = 0,
|
||||
OrderIds = new HashSet<int>() { fill.OrderId }
|
||||
}
|
||||
}
|
||||
};
|
||||
position.MinPrice = fill.FillPrice;
|
||||
@@ -421,9 +471,12 @@ namespace QuantConnect.Statistics
|
||||
ExitPrice = exitAveragePrice,
|
||||
ProfitLoss = Math.Round((exitAveragePrice - entryAveragePrice) * Math.Abs(totalEntryQuantity) * Math.Sign(totalEntryQuantity) * conversionRate * multiplier, 2),
|
||||
TotalFees = position.TotalFees,
|
||||
MAE = Math.Round((direction == TradeDirection.Long ? position.MinPrice - entryAveragePrice : entryAveragePrice - position.MaxPrice) * Math.Abs(totalEntryQuantity) * conversionRate * multiplier, 2),
|
||||
MFE = Math.Round((direction == TradeDirection.Long ? position.MaxPrice - entryAveragePrice : entryAveragePrice - position.MinPrice) * Math.Abs(totalEntryQuantity) * conversionRate * multiplier, 2),
|
||||
OrderIds = relatedOrderIds
|
||||
// MAE, MFE, EndTradeDrawdown are zero for FlatToFlat grouping method.
|
||||
// WE can fix this in the future if needed, but it might require tracking market prices
|
||||
// during the life of the trade, so that we can compute these metrics accurately accounting for
|
||||
// time, each fill entry price and quantity, which affect profit and drawdown and
|
||||
// adds complexity and memory overhead.
|
||||
};
|
||||
|
||||
AddNewTrade(trade, fill);
|
||||
@@ -524,9 +577,10 @@ namespace QuantConnect.Statistics
|
||||
ExitPrice = fill.FillPrice,
|
||||
ProfitLoss = Math.Round((fill.FillPrice - entryPrice) * Math.Abs(totalExecutedQuantity) * Math.Sign(-totalExecutedQuantity) * conversionRate * multiplier, 2),
|
||||
TotalFees = position.TotalFees,
|
||||
MAE = Math.Round((direction == TradeDirection.Long ? position.MinPrice - entryPrice : entryPrice - position.MaxPrice) * Math.Abs(totalExecutedQuantity) * conversionRate * multiplier, 2),
|
||||
MFE = Math.Round((direction == TradeDirection.Long ? position.MaxPrice - entryPrice : entryPrice - position.MinPrice) * Math.Abs(totalExecutedQuantity) * conversionRate * multiplier, 2),
|
||||
OrderIds = relatedOrderIds
|
||||
|
||||
// MAE, MFE, EndTradeDrawdown are zero for FlatToReduce grouping method.
|
||||
// See comment in FlatToFlat method for more details.541
|
||||
};
|
||||
|
||||
AddNewTrade(trade, fill);
|
||||
|
||||
@@ -402,7 +402,7 @@ namespace QuantConnect.Statistics
|
||||
if (trade.MFE > LargestMFE)
|
||||
LargestMFE = trade.MFE;
|
||||
|
||||
if (trade.EndTradeDrawdown < MaximumEndTradeDrawdown)
|
||||
if (trade.EndTradeDrawdown > MaximumEndTradeDrawdown)
|
||||
MaximumEndTradeDrawdown = trade.EndTradeDrawdown;
|
||||
|
||||
TotalFees += trade.TotalFees;
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace QuantConnect.Util
|
||||
public static bool ShouldAddCashBalance(CashAmount balance, string accountCurrency)
|
||||
{
|
||||
// Don't add zero quantity currencies except the account currency
|
||||
return balance.Amount != 0 || balance.Currency == accountCurrency;
|
||||
// we do add 'BNFCR' even if zero as it's used to track brokerage fees, we need lean to setup conversion rates for it
|
||||
return balance.Amount != 0 || balance.Currency == accountCurrency || balance.Currency == "BNFCR";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ using System.Threading.Tasks;
|
||||
using ICSharpCode.SharpZipLib.Core;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using Ionic.Zip;
|
||||
using QuantConnect.Logging;
|
||||
using ZipEntry = ICSharpCode.SharpZipLib.Zip.ZipEntry;
|
||||
using ZipFile = Ionic.Zip.ZipFile;
|
||||
@@ -177,26 +176,7 @@ namespace QuantConnect
|
||||
/// <returns>True on success</returns>
|
||||
public static bool ZipCreateAppendData(string path, string entry, string data, bool overrideEntry = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var zip = File.Exists(path) ? ZipFile.Read(path) : new ZipFile(path))
|
||||
{
|
||||
if (zip.ContainsEntry(entry) && overrideEntry)
|
||||
{
|
||||
zip.RemoveEntry(entry);
|
||||
}
|
||||
|
||||
zip.AddEntry(entry, data);
|
||||
zip.UseZip64WhenSaving = Zip64Option.Always;
|
||||
zip.Save();
|
||||
}
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return ZipCreateAppendData(path, entry, Encoding.UTF8.GetBytes(data), overrideEntry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -208,20 +188,39 @@ namespace QuantConnect
|
||||
/// <param name="overrideEntry">True if should override entry if it already exists</param>
|
||||
/// <returns>True on success</returns>
|
||||
public static bool ZipCreateAppendData(string path, string entry, byte[] data, bool overrideEntry = false)
|
||||
{
|
||||
return ZipCreateAppendData(path, entry, s => s.Write(data, 0, data.Length), overrideEntry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append the zip data to the file-entry specified.
|
||||
/// </summary>
|
||||
/// <param name="path">The zip file path</param>
|
||||
/// <param name="entry">The entry name</param>
|
||||
/// <param name="write">Write data callback</param>
|
||||
/// <param name="overrideEntry">True if should override entry if it already exists</param>
|
||||
/// <returns>True on success</returns>
|
||||
private static bool ZipCreateAppendData(string path, string entry, Action<Stream> write, bool overrideEntry = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var zip = File.Exists(path) ? ZipFile.Read(path) : new ZipFile(path))
|
||||
{
|
||||
if (overrideEntry && zip.ContainsEntry(entry))
|
||||
{
|
||||
zip.RemoveEntry(entry);
|
||||
}
|
||||
using var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||
using var archive = new ZipArchive(fs, ZipArchiveMode.Update, leaveOpen: false);
|
||||
|
||||
zip.AddEntry(entry, data);
|
||||
zip.UseZip64WhenSaving = Zip64Option.Always;
|
||||
zip.Save();
|
||||
var existing = archive.GetEntry(entry);
|
||||
if (existing != null)
|
||||
{
|
||||
if (!overrideEntry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
existing.Delete();
|
||||
}
|
||||
|
||||
var zipEntry = archive.CreateEntry(entry, CompressionLevel.Optimal);
|
||||
|
||||
using var entryStream = zipEntry.Open();
|
||||
write(entryStream);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
|
||||
78
Engine/HistoricalData/MappedSynchronizingHistoryProvider.cs
Normal file
78
Engine/HistoricalData/MappedSynchronizingHistoryProvider.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2026 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using NodaTime;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.HistoricalData
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for history providers that resolve symbol mappings
|
||||
/// and synchronize multiple data streams into time-aligned slices.
|
||||
/// </summary>
|
||||
public abstract class MappedSynchronizingHistoryProvider : SynchronizingHistoryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves map files to correctly handle current and historical ticker symbols.
|
||||
/// </summary>
|
||||
private static readonly Lazy<IMapFileProvider> _mapFileProvider = new(Composer.Instance.GetPart<IMapFileProvider>);
|
||||
|
||||
/// <summary>
|
||||
/// Gets historical data for a single resolved history request.
|
||||
/// Implementations should assume the symbol is already correctly mapped.
|
||||
/// </summary>
|
||||
/// <param name="request">The resolved history request.</param>
|
||||
/// <returns>The historical data.</returns>
|
||||
public abstract IEnumerable<BaseData>? GetHistory(HistoryRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the history for the requested securities
|
||||
/// </summary>
|
||||
/// <param name="requests">The historical data requests</param>
|
||||
/// <param name="sliceTimeZone">The time zone used when time stamping the slice instances</param>
|
||||
/// <returns>An enumerable of the slices of data covering the span specified in each request</returns>
|
||||
public override IEnumerable<Slice>? GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
|
||||
{
|
||||
var subscriptions = new List<Subscription>();
|
||||
foreach (var request in requests)
|
||||
{
|
||||
var history = request
|
||||
.SplitHistoryRequestWithUpdatedMappedSymbol(_mapFileProvider.Value)
|
||||
.SelectMany(x => GetHistory(x) ?? []);
|
||||
var subscription = CreateSubscription(request, history);
|
||||
if (!subscription.MoveNext())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
subscriptions.Add(subscription);
|
||||
}
|
||||
|
||||
if (subscriptions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ownership of subscription is transferred to CreateSliceEnumerableFromSubscriptions
|
||||
return CreateSliceEnumerableFromSubscriptions(subscriptions, sliceTimeZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,14 +107,17 @@ namespace QuantConnect.Indicators
|
||||
var f = _previousInputs[0].High - _previousInputs[0].Low;
|
||||
var g = _previousInputs[1].High - _previousInputs[1].Low;
|
||||
var h = _previousInputs[2].High - _previousInputs[2].Low;
|
||||
CloseBand.Update(input.EndTime, (a + 2 * (b + c) + d) / 6);
|
||||
RangeBand.Update(input.EndTime, (e + 2 * (f + g) + h) / 6);
|
||||
|
||||
if (CloseBand.IsReady && RangeBand.IsReady && RangeBand != 0m)
|
||||
CloseBand.Update(input.EndTime, (a + 2 * (b + c) + d) / 6m);
|
||||
RangeBand.Update(input.EndTime, (e + 2 * (f + g) + h) / 6m);
|
||||
|
||||
if (CloseBand.IsReady && RangeBand.IsReady)
|
||||
{
|
||||
_previousInputs.Add(input);
|
||||
var rvi = CloseBand / RangeBand;
|
||||
Signal?.Update(input.EndTime, rvi); // Checks for null before updating.
|
||||
var rvi = RangeBand != 0m ? CloseBand / RangeBand : 0m;
|
||||
|
||||
Signal.Update(input.EndTime, rvi);
|
||||
|
||||
return rvi;
|
||||
}
|
||||
}
|
||||
@@ -132,7 +135,7 @@ namespace QuantConnect.Indicators
|
||||
CloseBand.Reset();
|
||||
RangeBand.Reset();
|
||||
_previousInputs.Reset();
|
||||
Signal?.Reset();
|
||||
Signal.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace QuantConnect.Tests.API
|
||||
|
||||
// Create a backtest
|
||||
Log.Debug("ApiTestBase.Setup(): Creating test backtest");
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("u")} API Backtest";
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")} API Backtest";
|
||||
var backtest = ApiClient.CreateBacktest(TestProject.ProjectId, compile.CompileId, backtestName);
|
||||
if (!backtest.Success)
|
||||
{
|
||||
|
||||
@@ -278,7 +278,7 @@ namespace QuantConnect.Tests.API
|
||||
Assert.AreEqual(CompileState.BuildError, compileError.State); //Resulting in build fail.
|
||||
|
||||
// Using our successful compile; launch a backtest!
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("u")} API Backtest";
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")} API Backtest";
|
||||
var backtest = ApiClient.CreateBacktest(project.Projects.First().ProjectId, compileSuccess.CompileId, backtestName);
|
||||
Assert.IsTrue(backtest.Success);
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace QuantConnect.Tests.API
|
||||
Assert.AreEqual(backtestName, backtestRead.Name);
|
||||
|
||||
//Update the note and make sure its been updated:
|
||||
var newNote = DateTime.Now.ToStringInvariant("u");
|
||||
var newNote = DateTime.Now.ToStringInvariant("yyyy-MM-dd HH-mm-ss");
|
||||
var noteBacktest = ApiClient.UpdateBacktest(project.Projects.First().ProjectId, backtest.BacktestId, note: newNote);
|
||||
Assert.IsTrue(noteBacktest.Success);
|
||||
backtestRead = ApiClient.ReadBacktest(project.Projects.First().ProjectId, backtest.BacktestId);
|
||||
@@ -370,7 +370,7 @@ namespace QuantConnect.Tests.API
|
||||
backtestRead = WaitForBacktestCompletion(ApiClient, project.ProjectId, backtest.BacktestId);
|
||||
var backtestOrdersRead = ApiClient.ReadBacktestOrders(project.ProjectId, backtest.BacktestId);
|
||||
string stringRepresentation;
|
||||
foreach(var backtestOrder in backtestOrdersRead)
|
||||
foreach (var backtestOrder in backtestOrdersRead)
|
||||
{
|
||||
stringRepresentation = backtestOrder.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
@@ -409,7 +409,7 @@ namespace QuantConnect.Tests.API
|
||||
{
|
||||
// We will be using the existing TestBacktest for this test
|
||||
var originalName = TestBacktest.Name;
|
||||
var newName = $"{originalName} - Amended - {DateTime.UtcNow.ToStringInvariant("u")}";
|
||||
var newName = $"{originalName} - Amended - {DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")}";
|
||||
|
||||
// Update the backtest name
|
||||
var updateResult = ApiClient.UpdateBacktest(TestProject.ProjectId, TestBacktest.BacktestId, name: newName);
|
||||
@@ -643,7 +643,7 @@ namespace QuantConnect.Tests.API
|
||||
Assert.IsTrue(readLiveLogs.Length >= 0, "The length of the logs was negative!");
|
||||
Assert.IsTrue(readLiveLogs.DeploymentOffset >= 0, "The deploymentOffset");
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete the project in case of an error
|
||||
Assert.IsTrue(ApiClient.DeleteProject(projectId).Success);
|
||||
|
||||
@@ -629,7 +629,7 @@ namespace QuantConnect.Tests.Common.Securities
|
||||
public void CryptoStableCoinMappingIsCorrect(IBrokerageModel brokerageModel, string accountCurrency, string stableCoin, bool shouldThrow, Symbol[] expectedConversionSymbols)
|
||||
{
|
||||
var cashBook = new CashBook() {AccountCurrency = accountCurrency};
|
||||
var cash = new Cash(stableCoin, 10m, 1m);
|
||||
var cash = new Cash(stableCoin, 10m, 0);
|
||||
cashBook.Add(cash.Symbol, cash);
|
||||
|
||||
var subscriptions = new SubscriptionManager(NullTimeKeeper.Instance);
|
||||
@@ -716,6 +716,9 @@ namespace QuantConnect.Tests.Common.Securities
|
||||
|
||||
// *** Binance ***
|
||||
// USDC Cases
|
||||
new object[] { new BinanceBrokerageModel(), "USDT", "BNFCR", false, null },
|
||||
new object[] { new BinanceBrokerageModel(), "USDC", "BNFCR", false, null },
|
||||
new object[] { new BinanceBrokerageModel(), Currencies.USD, "BNFCR", false, null },
|
||||
new object[] { new BinanceBrokerageModel(), Currencies.USD, "USDC", false, null }, // No USDCUSD, but does not throw! Conversion 1-1
|
||||
new object[] { new BinanceBrokerageModel(), Currencies.EUR, "USDC", false, new[] { Symbol.Create("EURUSDC", SecurityType.Crypto, Market.Binance) } },
|
||||
new object[] { new BinanceBrokerageModel(), Currencies.GBP, "USDC", false, new[] { Symbol.Create("ADAUSDC", SecurityType.Crypto, Market.Binance), Symbol.Create("ADAGBP", SecurityType.Crypto, Market.Binance) } }, // No USDCGBP, but indirect conversion exists
|
||||
|
||||
@@ -54,28 +54,6 @@ namespace QuantConnect.Tests.Common.Securities.CryptoFuture
|
||||
Assert.AreEqual((double)expectedMargin, (double)result.Value, (double)(0.05m * expectedMargin));
|
||||
}
|
||||
|
||||
[TestCase("BTCUSDT", 0.5, 0.005, 87)] // Bybit value: 86.69. Margin rate 0.5%
|
||||
[TestCase("BTCUSDT", -0.5, 0.005, 87)]
|
||||
[TestCase("BTCUSDT", 0.5, 0.02, 320)] // Bybit value: 323.2. Margin rate 2%
|
||||
[TestCase("BTCUSDT", -0.5, 0.02, 320)]
|
||||
[TestCase("BTCUSD", 15000, 0.005, 75)] // Margin rate 0.5%
|
||||
[TestCase("BTCUSD", -15000, 0.005, 75)]
|
||||
[TestCase("BTCUSD", 15000, 0.02, 300)] // Margin rate 2%
|
||||
[TestCase("BTCUSD", -15000, 0.02, 300)]
|
||||
public void BybitMaintenanceMargin(string ticker, decimal quantity, decimal marginRate, decimal expectedMargin)
|
||||
{
|
||||
var algo = GetAlgorithm();
|
||||
var cryptoFuture = algo.AddCryptoFuture(ticker);
|
||||
cryptoFuture.SetBuyingPowerModel(new CryptoFutureMarginModel(25m, marginRate));
|
||||
SetPrice(cryptoFuture, 31300);
|
||||
cryptoFuture.Holdings.SetHoldings(0.5m, quantity);
|
||||
|
||||
var parameters = MaintenanceMarginParameters.ForCurrentHoldings(cryptoFuture);
|
||||
var result = cryptoFuture.BuyingPowerModel.GetMaintenanceMargin(parameters);
|
||||
|
||||
Assert.AreEqual((double)expectedMargin, (double)result.Value, (double)(0.15m * expectedMargin));
|
||||
}
|
||||
|
||||
private static QCAlgorithm GetAlgorithm()
|
||||
{
|
||||
var algo = new AlgorithmStub();
|
||||
|
||||
@@ -64,37 +64,6 @@ namespace QuantConnect.Tests.Common.Securities.CryptoFuture
|
||||
Assert.AreEqual(Math.Abs(marginRequirement), result.Value);
|
||||
}
|
||||
|
||||
[TestCase("BTCUSD", 10)]
|
||||
[TestCase("BTCUSDT", 10)]
|
||||
[TestCase("BTCUSD", -10)]
|
||||
[TestCase("BTCUSDT", -10)]
|
||||
public void GetMaintenanceMargin(string ticker, decimal quantity)
|
||||
{
|
||||
var algo = GetAlgorithm();
|
||||
var cryptoFuture = algo.AddCryptoFuture(ticker);
|
||||
SetPrice(cryptoFuture, 16000);
|
||||
// entry price 1000, shouldn't matter
|
||||
cryptoFuture.Holdings.SetHoldings(1000, quantity);
|
||||
|
||||
var parameters = MaintenanceMarginParameters.ForCurrentHoldings(cryptoFuture);
|
||||
var result = cryptoFuture.BuyingPowerModel.GetMaintenanceMargin(parameters);
|
||||
|
||||
decimal marginRequirement;
|
||||
if (ticker == "BTCUSD")
|
||||
{
|
||||
// ((quantity * contract mutiplier * price) * MaintenanceMarginRate) * conversion rate (BTC -> USD)
|
||||
marginRequirement = ((parameters.Quantity * 100m * cryptoFuture.Price) * 0.05m) * 1 / cryptoFuture.Price;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ((quantity * contract mutiplier * price) * MaintenanceMarginRate) * conversion rate (USDT ~= USD)
|
||||
marginRequirement = ((parameters.Quantity * 1m * cryptoFuture.Price) * 0.05m) * 1;
|
||||
}
|
||||
|
||||
Assert.AreEqual(Math.Abs(marginRequirement), result.Value);
|
||||
}
|
||||
|
||||
|
||||
private static QCAlgorithm GetAlgorithm()
|
||||
{
|
||||
// Initialize algorithm
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
@@ -92,8 +93,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(10, trade.ProfitLoss);
|
||||
Assert.AreEqual(2, trade.TotalFees);
|
||||
Assert.AreEqual(-5, trade.MAE);
|
||||
Assert.AreEqual(20m, trade.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-5, trade.MAE);
|
||||
Assert.AreEqual(20m, trade.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade.OrderIds);
|
||||
}
|
||||
|
||||
@@ -150,8 +159,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-10, trade.ProfitLoss);
|
||||
Assert.AreEqual(2, trade.TotalFees);
|
||||
Assert.AreEqual(-20, trade.MAE);
|
||||
Assert.AreEqual(5, trade.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-20, trade.MAE);
|
||||
Assert.AreEqual(5, trade.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade.OrderIds);
|
||||
}
|
||||
|
||||
@@ -255,8 +272,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(30, trade.ProfitLoss);
|
||||
Assert.AreEqual(3, trade.TotalFees);
|
||||
Assert.AreEqual(-20, trade.MAE);
|
||||
Assert.AreEqual(50, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3 }, trade.OrderIds);
|
||||
}
|
||||
}
|
||||
@@ -360,8 +377,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-30, trade.ProfitLoss);
|
||||
Assert.AreEqual(3, trade.TotalFees);
|
||||
Assert.AreEqual(-50, trade.MAE);
|
||||
Assert.AreEqual(20, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3 }, trade.OrderIds);
|
||||
}
|
||||
}
|
||||
@@ -431,8 +448,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.085m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(30, trade.ProfitLoss);
|
||||
Assert.AreEqual(3, trade.TotalFees);
|
||||
Assert.AreEqual(-10, trade.MAE);
|
||||
Assert.AreEqual(60, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3 }, trade.OrderIds);
|
||||
}
|
||||
else
|
||||
@@ -451,8 +468,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(1.08m, trade1.ExitPrice);
|
||||
Assert.AreEqual(10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(2, trade1.TotalFees);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(10, trade1.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(10, trade1.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -466,8 +491,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(20, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(-5, trade2.MAE);
|
||||
Assert.AreEqual(30, trade2.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-5, trade2.MAE);
|
||||
Assert.AreEqual(30, trade2.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(groupingMethod == FillGroupingMethod.FlatToReduced ? [1, 3] : new[] { 1, 3 }, trade2.OrderIds);
|
||||
}
|
||||
}
|
||||
@@ -537,8 +570,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.085m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-30, trade.ProfitLoss);
|
||||
Assert.AreEqual(3, trade.TotalFees);
|
||||
Assert.AreEqual(-60, trade.MAE);
|
||||
Assert.AreEqual(10, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3 }, trade.OrderIds);
|
||||
}
|
||||
else
|
||||
@@ -557,8 +590,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(1.08m, trade1.ExitPrice);
|
||||
Assert.AreEqual(-10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(2, trade1.TotalFees);
|
||||
Assert.AreEqual(-10, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-10, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -572,8 +613,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(-20, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(-30, trade2.MAE);
|
||||
Assert.AreEqual(5, trade2.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-30, trade2.MAE);
|
||||
Assert.AreEqual(5, trade2.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 3 }, trade2.OrderIds);
|
||||
}
|
||||
}
|
||||
@@ -642,8 +691,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(1.08m, trade1.ExitPrice);
|
||||
Assert.AreEqual(10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(2, trade1.TotalFees);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(10, trade1.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(10, trade1.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -657,8 +714,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(-10, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(-20, trade2.MAE);
|
||||
Assert.AreEqual(15, trade2.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-20, trade2.MAE);
|
||||
Assert.AreEqual(15, trade2.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 2, 3 }, trade2.OrderIds);
|
||||
}
|
||||
|
||||
@@ -726,8 +791,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(1.08m, trade1.ExitPrice);
|
||||
Assert.AreEqual(-10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(2, trade1.TotalFees);
|
||||
Assert.AreEqual(-10, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-10, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -741,8 +814,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(10, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(-15, trade2.MAE);
|
||||
Assert.AreEqual(20, trade2.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-15, trade2.MAE);
|
||||
Assert.AreEqual(20, trade2.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 2, 3 }, trade2.OrderIds);
|
||||
}
|
||||
|
||||
@@ -896,8 +977,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(40, trade.ProfitLoss);
|
||||
Assert.AreEqual(5, trade.TotalFees);
|
||||
Assert.AreEqual(-35, trade.MAE);
|
||||
Assert.AreEqual(70, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -923,8 +1004,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 20 : 10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -5 : -15, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 30 : 20, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [3, 1] : new[] { 3, 2 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -944,8 +1025,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 20 : 30, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -30 : -20, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 40 : 50, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4, 5] : new[] { 1, 4, 5 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1098,8 +1179,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-40, trade.ProfitLoss);
|
||||
Assert.AreEqual(5, trade.TotalFees);
|
||||
Assert.AreEqual(-70, trade.MAE);
|
||||
Assert.AreEqual(35, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1123,8 +1204,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -20 : -10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -30 : -20, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 5 : 15, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 3] : new[] { 2, 3 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -1142,8 +1223,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -20 : -30, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -40 : -50, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 30 : 20, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4, 5] : new[] { 1, 4, 5 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1336,8 +1417,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(50, trade.ProfitLoss);
|
||||
Assert.AreEqual(5, trade.TotalFees);
|
||||
Assert.AreEqual(-50, trade.MAE);
|
||||
Assert.AreEqual(90, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1361,8 +1442,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 20 : 10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -5 : -15, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 30 : 20, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 3] : new[] { 2, 3 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -1385,8 +1466,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 30 : 40, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -45 : -35, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 60 : 70, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4, 5] : new[] { 1, 2, 4, 5 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1594,8 +1675,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-50, trade.ProfitLoss);
|
||||
Assert.AreEqual(5, trade.TotalFees);
|
||||
Assert.AreEqual(-90, trade.MAE);
|
||||
Assert.AreEqual(50, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1619,8 +1700,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -20 : -10, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -30 : -20, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 5 : 15, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [3, 1] : new[] { 2, 3 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -1643,8 +1724,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -30 : -40, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -60 : -70, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 45 : 35, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4, 5] : new[] { 1, 2, 4, 5 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1802,8 +1883,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.095m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(60, trade.ProfitLoss);
|
||||
Assert.AreEqual(6, trade.TotalFees);
|
||||
Assert.AreEqual(-60, trade.MAE);
|
||||
Assert.AreEqual(80, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5, 6 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -1827,8 +1908,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.10m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 50 : 30, trade1.ProfitLoss);
|
||||
Assert.AreEqual(4, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -20 : -40, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 50 : 30, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 2, 4] : new[] { 2, 3, 4 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -1846,8 +1927,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 10 : 30, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -40 : -20, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 30 : 50, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [3, 5, 6] : new[] { 1, 5, 6 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2023,8 +2104,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.095m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-60, trade.ProfitLoss);
|
||||
Assert.AreEqual(6, trade.TotalFees);
|
||||
Assert.AreEqual(-80, trade.MAE);
|
||||
Assert.AreEqual(60, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4, 5, 6 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2048,8 +2129,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.10m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -50 : -30, trade1.ProfitLoss);
|
||||
Assert.AreEqual(4, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -50 : -30, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 20 : 40, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 2, 4] : new[] { 2, 3, 4 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -2067,8 +2148,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -10 : -30, trade2.ProfitLoss);
|
||||
Assert.AreEqual(2, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -30 : -50, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 40 : 20, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [3, 5, 6] : new[] { 1, 5, 6 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2213,8 +2294,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.0925m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(35, trade.ProfitLoss);
|
||||
Assert.AreEqual(4, trade.TotalFees);
|
||||
Assert.AreEqual(-20, trade.MAE);
|
||||
Assert.AreEqual(50, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2236,8 +2317,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 25 : 20, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -12.5 : -17.5, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 40 : 35, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 2, 3] : new[] { 1, 2, 3 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -2255,8 +2336,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.10m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 10 : 15, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -7.5 : -2.5, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 10 : 15, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4] : new[] { 1, 4 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2401,8 +2482,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.0925m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(-35, trade.ProfitLoss);
|
||||
Assert.AreEqual(4, trade.TotalFees);
|
||||
Assert.AreEqual(-50, trade.MAE);
|
||||
Assert.AreEqual(20, trade.MFE);
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2, 3, 4 }, trade.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2424,8 +2505,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade1.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -25 : -20, trade1.ProfitLoss);
|
||||
Assert.AreEqual(3, trade1.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -40 : -35, trade1.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 12.5 : 17.5, trade1.MFE);
|
||||
Assert.AreEqual(0, trade1.MAE);
|
||||
Assert.AreEqual(0, trade1.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [1, 2, 3] : new[] { 1, 2, 3 }, trade1.OrderIds);
|
||||
|
||||
var trade2 = builder.ClosedTrades[1];
|
||||
@@ -2443,8 +2524,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.10m, split), trade2.ExitPrice);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -10 : -15, trade2.ProfitLoss);
|
||||
Assert.AreEqual(1, trade2.TotalFees);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? -10 : -15, trade2.MAE);
|
||||
Assert.AreEqual(matchingMethod == FillMatchingMethod.FIFO ? 7.5 : 2.5, trade2.MFE);
|
||||
Assert.AreEqual(0, trade2.MAE);
|
||||
Assert.AreEqual(0, trade2.MFE);
|
||||
CollectionAssert.AreEquivalent(matchingMethod == FillMatchingMethod.FIFO ? [2, 4] : new[] { 1, 4 }, trade2.OrderIds);
|
||||
}
|
||||
break;
|
||||
@@ -2506,8 +2587,16 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(AdjustPriceToSplit(1.09m, split), trade.ExitPrice);
|
||||
Assert.AreEqual(10 * multiplier, trade.ProfitLoss);
|
||||
Assert.AreEqual(2, trade.TotalFees);
|
||||
Assert.AreEqual(-5 * multiplier, trade.MAE);
|
||||
Assert.AreEqual(20m * multiplier, trade.MFE);
|
||||
if (groupingMethod == FillGroupingMethod.FillToFill)
|
||||
{
|
||||
Assert.AreEqual(-5 * multiplier, trade.MAE);
|
||||
Assert.AreEqual(20m * multiplier, trade.MFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, trade.MAE);
|
||||
Assert.AreEqual(0, trade.MFE);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade.OrderIds);
|
||||
}
|
||||
|
||||
@@ -2704,6 +2793,203 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
CollectionAssert.AreEquivalent(new[] { 1, 2 }, trade.OrderIds);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(DrawdownTestCases))]
|
||||
public void DrawdownCalculation(PositionSide entrySide, decimal[] prices, decimal expectedDrawdown)
|
||||
{
|
||||
if (prices.Length < 2)
|
||||
{
|
||||
Assert.Fail("At least two prices are required to perform the test.");
|
||||
}
|
||||
|
||||
// Buy 1k, Sell 1k (entrySide == Long) or Sell 1k, Buy 1k (entrySide == Short)
|
||||
|
||||
var builder = new TradeBuilder(FillGroupingMethod.FillToFill, FillMatchingMethod.FIFO);
|
||||
builder.SetSecurityManager(_securityManager);
|
||||
var time = _startTime;
|
||||
|
||||
var quantity = (entrySide == PositionSide.Long ? 1 : -1) * 1000m;
|
||||
|
||||
// Open position
|
||||
builder.ProcessFill(
|
||||
new OrderEvent(1, Symbols.SPY, time, OrderStatus.Filled, entrySide == PositionSide.Long ? OrderDirection.Buy : OrderDirection.Sell,
|
||||
fillPrice: prices[0], fillQuantity: quantity, orderFee: _orderFee),
|
||||
ConversionRate, _orderFee.Value.Amount);
|
||||
|
||||
Assert.IsTrue(builder.HasOpenPosition(Symbols.SPY));
|
||||
|
||||
for (int i = 1; i < prices.Length - 1; i++)
|
||||
{
|
||||
builder.SetMarketPrice(Symbols.SPY, prices[i]);
|
||||
}
|
||||
|
||||
// Close position
|
||||
builder.ProcessFill(
|
||||
new OrderEvent(2, Symbols.SPY, time.AddMinutes(10), OrderStatus.Filled, entrySide == PositionSide.Long ? OrderDirection.Sell : OrderDirection.Buy,
|
||||
fillPrice: prices[^1], fillQuantity: -quantity, orderFee: _orderFee),
|
||||
ConversionRate, _orderFee.Value.Amount);
|
||||
|
||||
Assert.IsFalse(builder.HasOpenPosition(Symbols.SPY));
|
||||
|
||||
Assert.AreEqual(1, builder.ClosedTrades.Count);
|
||||
|
||||
var trade = builder.ClosedTrades[0];
|
||||
|
||||
Assert.AreEqual(expectedDrawdown * Math.Abs(quantity), trade.EndTradeDrawdown);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> DrawdownTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
// Long trades
|
||||
// -------------------------------
|
||||
|
||||
// Price 100 -> 120 -> 110
|
||||
// /\
|
||||
// / \
|
||||
// / ----
|
||||
// /
|
||||
// ----
|
||||
// We expect a drawdown of 10 (from 120 to 110)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 120m, 110m }, 10m).SetName($"DrawdownLongTrade_SingleDrawdown");
|
||||
|
||||
// Price 100 -> 140 -> 120 -> 130 -> 110
|
||||
// /\
|
||||
// / \
|
||||
// / \ /\
|
||||
// / \/ \
|
||||
// / \
|
||||
// / \
|
||||
// / ----
|
||||
// /
|
||||
// ----
|
||||
// We expect a drawdown of 30 (from 140 to 110)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 140m, 120m, 130m, 110m }, 30m).SetName($"DrawdownLongTrade_MultipleDrawdownsOnSingleHighestPrice");
|
||||
|
||||
// Price 100 -> 120 -> 110 -> 120 -> 140 -> 115
|
||||
// /\
|
||||
// / \
|
||||
// / \
|
||||
// / \
|
||||
// /\ / \
|
||||
// / \/ \
|
||||
// / \
|
||||
// / ----
|
||||
// ----
|
||||
// We expect a drawdown of 25 (from 140 to 115)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 120m, 110m, 120m, 140m, 115m }, 25m).SetName($"DrawdownLongTrade_HighestDrawdownOnNewHighestPrice");
|
||||
|
||||
// Price 100 -> 120 -> 110 -> 120 -> 130 -> 125
|
||||
// /\
|
||||
// / ----
|
||||
// /\ /
|
||||
// / \/
|
||||
// /
|
||||
// /
|
||||
// ----
|
||||
// We expect a drawdown of 10 (from 120 to 110)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 120m, 110m, 120m, 130m, 125m }, 10m).SetName($"DrawdownLongTrade_LowerDrawdownOnNewHighestPrice");
|
||||
|
||||
// Price 100 -> 80 -> 110
|
||||
// ----
|
||||
// /
|
||||
// ---- /
|
||||
// \ /
|
||||
// \ /
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 20 (from 100 to 80)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 80m, 110m }, 20m).SetName($"DrawdownLongTrade_PriceGoesBelowEntryPrice");
|
||||
|
||||
// Price 100 -> 90 -> 130 -> 110
|
||||
// /\
|
||||
// / \
|
||||
// / \
|
||||
// / \
|
||||
// / ----
|
||||
// /
|
||||
// ---- /
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 20 (from 130 to 110 which is higher than the first one from 100 to 90)
|
||||
yield return new TestCaseData(PositionSide.Long, new[] { 100m, 90m, 130m, 110m }, 20m).SetName($"DrawdownLongTrade_HigherDrawdownAfterPriceGoesBelowEntryPrice");
|
||||
|
||||
// Short trades
|
||||
// -------------------------------
|
||||
|
||||
// Price 100 -> 80 -> 90
|
||||
// ----
|
||||
// \
|
||||
// \ ----
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 10 (from 80 to 90)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 80m, 90m }, 10m).SetName($"DrawdownShortTrade_SingleDrawdown");
|
||||
|
||||
// Price 100 -> 60 -> 80 -> 70 -> 90
|
||||
// ----
|
||||
// \
|
||||
// \ ----
|
||||
// \ /
|
||||
// \ /
|
||||
// \ /\ /
|
||||
// \ / \/
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 30 (from 60 to 90)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 60m, 80m, 70m, 90m }, 30m).SetName($"DrawdownShortTrade_MultipleDrawdownsOnSingleLowestPrice");
|
||||
|
||||
// Price 100 -> 80 -> 90 -> 80 -> 60 -> 85
|
||||
// ----
|
||||
// \ ----
|
||||
// \ /
|
||||
// \ /\ /
|
||||
// \/ \ /
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 25 (from 60 to 85)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 80m, 90m, 80m, 60m, 85m }, 25m).SetName($"DrawdownShortTrade_HighestDrawdownOnNewLowestPrice");
|
||||
|
||||
// Price 100 -> 80 -> 90 -> 80 -> 70 -> 75
|
||||
// ----
|
||||
// \
|
||||
// \
|
||||
// \ /\
|
||||
// \/ \
|
||||
// \ ----
|
||||
// \/
|
||||
|
||||
// We expect a drawdown of 10 (from 80 to 90)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 80m, 90m, 80m, 70m, 75m }, 10m).SetName($"DrawdownShortTrade_LowerDrawdownOnNewLowestPrice");
|
||||
|
||||
// Price 100 -> 120 -> 90
|
||||
// /\
|
||||
// / \
|
||||
// / \
|
||||
// / \
|
||||
// ---- \
|
||||
// \
|
||||
// \
|
||||
// ----
|
||||
// We expect a drawdown of 20 (from 100 to 120)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 120m, 90m }, 20m).SetName($"DrawdownShortTrade_PriceGoesAboveEntryPrice");
|
||||
|
||||
// Price 100 -> 110 -> 70 -> 90
|
||||
// /\
|
||||
// / \
|
||||
// ---- \
|
||||
// \
|
||||
// \ ----
|
||||
// \ /
|
||||
// \ /
|
||||
// \ /
|
||||
// \/
|
||||
// We expect a drawdown of 20 (from 70 to 90 which is higher than the first one from 100 to 110)
|
||||
yield return new TestCaseData(PositionSide.Short, new[] { 100m, 110m, 70m, 90m }, 20m).SetName($"DrawdownShortTrade_HigherDrawdownAfterPriceGoesAboveEntryPrice");
|
||||
}
|
||||
}
|
||||
|
||||
private Option GetOption()
|
||||
{
|
||||
var underlying = new Security(
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(2.8867513459481276450914878051m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(0, statistics.SortinoRatio);
|
||||
Assert.AreEqual(10, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-20, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(20, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-16.666666666666666666666666666m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.Zero, statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(6, statistics.TotalFees);
|
||||
@@ -134,7 +134,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -5,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 10
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -148,7 +149,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 40
|
||||
MFE = 40,
|
||||
EndTradeDrawdown = 20
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -162,7 +164,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 10,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -15,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 20
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -206,7 +209,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(-2.8867513459481276450914878051m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(-2.8867513459481276450914878051m, statistics.SortinoRatio);
|
||||
Assert.AreEqual(-1, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-33.333333333333333333333333334m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.Zero, statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(6, statistics.TotalFees);
|
||||
@@ -230,7 +233,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 5
|
||||
MFE = 5,
|
||||
EndTradeDrawdown = 25
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -244,7 +248,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -40,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 50
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -258,7 +263,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -10,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 15
|
||||
MFE = 15,
|
||||
EndTradeDrawdown = 25
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -302,7 +308,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(-0.5773502691896248623516308943m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(0, statistics.SortinoRatio);
|
||||
Assert.AreEqual(-0.75m, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-31.666666666666666666666666666667m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.Zero, statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(6, statistics.TotalFees);
|
||||
@@ -326,7 +332,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 5
|
||||
MFE = 5,
|
||||
EndTradeDrawdown = 30
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -340,7 +347,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -40,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 50
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -354,7 +362,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 10,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -15,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 20
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -398,7 +407,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(-0.5773502691896248623516308943m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(0, statistics.SortinoRatio);
|
||||
Assert.AreEqual(-0.75m, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(50, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-31.666666666666666666666666666667m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.Zero, statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(6, statistics.TotalFees);
|
||||
@@ -422,7 +431,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 10,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -15,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 20
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -436,7 +446,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 5
|
||||
MFE = 5,
|
||||
EndTradeDrawdown = 25
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -450,7 +461,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -40,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 50
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -494,7 +506,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(0.1601281538050873438895842626m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(0, statistics.SortinoRatio);
|
||||
Assert.AreEqual(0.5m, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-25, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(25, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-18.333333333333333333333333334m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.FromMinutes(40), statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(6, statistics.TotalFees);
|
||||
@@ -518,7 +530,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = -20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -30,
|
||||
MFE = 5
|
||||
MFE = 5,
|
||||
EndTradeDrawdown = 25
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -532,7 +545,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 20,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -40,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 10
|
||||
},
|
||||
new Trade
|
||||
{
|
||||
@@ -546,7 +560,8 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
ProfitLoss = 10,
|
||||
TotalFees = TradeFee,
|
||||
MAE = -15,
|
||||
MFE = 30
|
||||
MFE = 30,
|
||||
EndTradeDrawdown = 20
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -604,7 +619,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
Assert.AreEqual(0.1053137759214006433027413265m, statistics.SharpeRatio);
|
||||
Assert.AreEqual(0m, statistics.SortinoRatio);
|
||||
Assert.AreEqual(0.35m, statistics.ProfitToMaxDrawdownRatio);
|
||||
Assert.AreEqual(-80000, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(80000, statistics.MaximumEndTradeDrawdown);
|
||||
Assert.AreEqual(-40000m, statistics.AverageEndTradeDrawdown);
|
||||
Assert.AreEqual(TimeSpan.FromMinutes(30), statistics.MaximumDrawdownDuration);
|
||||
Assert.AreEqual(4, statistics.TotalFees);
|
||||
@@ -629,6 +644,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
TotalFees = TradeFee,
|
||||
MAE = -80000m,
|
||||
MFE = 0,
|
||||
EndTradeDrawdown = 80000m,
|
||||
IsWin = win,
|
||||
},
|
||||
new Trade
|
||||
@@ -644,6 +660,7 @@ namespace QuantConnect.Tests.Common.Statistics
|
||||
TotalFees = TradeFee,
|
||||
MAE = 0,
|
||||
MFE = 108000m,
|
||||
EndTradeDrawdown = 0m,
|
||||
IsWin = true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -617,6 +617,7 @@ namespace QuantConnect.Tests.Engine.Setup
|
||||
{
|
||||
new CashAmount(0, "USD"),
|
||||
new CashAmount(0, "EUR"),
|
||||
new CashAmount(0, "BNFCR"),
|
||||
new CashAmount(123, "ETH")
|
||||
});
|
||||
|
||||
@@ -639,6 +640,8 @@ namespace QuantConnect.Tests.Engine.Setup
|
||||
Assert.IsFalse(algorithm.Portfolio.CashBook.ContainsKey("EUR"));
|
||||
// ETH should be present
|
||||
Assert.IsTrue(algorithm.Portfolio.CashBook.ContainsKey("ETH"));
|
||||
// special case used in binance future fees
|
||||
Assert.IsTrue(algorithm.Portfolio.CashBook.ContainsKey("BNFCR"));
|
||||
}
|
||||
|
||||
private void TestLoadExistingHoldingsAndOrders(IAlgorithm algorithm, Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace QuantConnect.Tests.Indicators
|
||||
{
|
||||
var rvi = CreateIndicator();
|
||||
TestHelper.TestIndicator(rvi, TestFileName, "RVI_S",
|
||||
(ind, expected) => Assert.AreEqual(expected,
|
||||
(double) ((RelativeVigorIndex) ind).Signal.Current.Value, 0.06));
|
||||
(ind, expected) => Assert.AreEqual(expected,
|
||||
(double)((RelativeVigorIndex)ind).Signal.Current.Value, 0.06));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -48,17 +48,40 @@ namespace QuantConnect.Tests.Indicators
|
||||
for (int i = 0; i < 13; i++)
|
||||
{
|
||||
var tradeBar = new TradeBar
|
||||
{
|
||||
Open = 0m,
|
||||
Close = 0m,
|
||||
High = 0m,
|
||||
Low = 0m,
|
||||
Volume = 1
|
||||
};
|
||||
rvi.Update(tradeBar);
|
||||
{
|
||||
Open = 0m,
|
||||
Close = 0m,
|
||||
High = 0m,
|
||||
Low = 0m,
|
||||
Volume = 1
|
||||
};
|
||||
rvi.Update(tradeBar);
|
||||
}
|
||||
Assert.AreEqual(rvi.Current.Value, 0m);
|
||||
Assert.AreEqual(((RelativeVigorIndex) rvi).Signal.Current.Value, 0m);
|
||||
Assert.AreEqual(((RelativeVigorIndex)rvi).Signal.Current.Value, 0m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SignalUpdatesOnFlatMarket()
|
||||
{
|
||||
var rvi = new RelativeVigorIndex("RVI", 10);
|
||||
var referenceTime = System.DateTime.Today;
|
||||
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
rvi.Update(new TradeBar
|
||||
{
|
||||
Time = referenceTime.AddMinutes(i),
|
||||
Open = 105m,
|
||||
High = 105m,
|
||||
Low = 105m,
|
||||
Close = 105m,
|
||||
Volume = 1000
|
||||
});
|
||||
}
|
||||
|
||||
var signalSamples = rvi.Signal.Samples;
|
||||
Assert.AreEqual(18, signalSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,25 @@ namespace QuantConnect.Tests.ToolBox
|
||||
Assert.AreEqual(data.First().Value.Count, 3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LeanDataWriter_CanSupportUtf8Chars()
|
||||
{
|
||||
var symbol = Symbol.Create("币安人生usdt", SecurityType.CryptoFuture, Market.Binance);
|
||||
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, symbol, _date, Resolution.Tick, TickType.Trade);
|
||||
|
||||
var leanDataWriter = new LeanDataWriter(Resolution.Tick, symbol, _dataDirectory);
|
||||
leanDataWriter.Write(GetTicks(symbol));
|
||||
|
||||
Assert.IsTrue(File.Exists(filePath));
|
||||
Assert.IsFalse(File.Exists(filePath + ".tmp"));
|
||||
|
||||
var data = QuantConnect.Compression.Unzip(filePath);
|
||||
|
||||
var entry = data.First();
|
||||
Assert.AreEqual(entry.Key, "20170316_币安人生usdt_tick_trade_perp.csv");
|
||||
Assert.AreEqual(entry.Value.Count, 3);
|
||||
}
|
||||
|
||||
[TestCase("CON")]
|
||||
[TestCase("PRN")]
|
||||
[TestCase("AUX")]
|
||||
|
||||
Reference in New Issue
Block a user