Compare commits

..

15 Commits
9385 ... 9464

Author SHA1 Message Date
Stefano Raggi
027fde8f09 Fix IBAutomater restarting after Dispose (#4814)
* Fix IBAutomater restarting after Dispose

* Address review
2020-10-05 12:34:02 -03:00
Martin-Molinero
c7ccd60bf2 Fix unit test stack overflow (#4803)
- PortfolioLooper: implement IDisposable and handle the clean up of
  resources. Remove unused methods.
- BaseResultHandler will reset Console on exit
2020-10-01 14:49:43 -03:00
adam-may
b30bb3fcf5 Bug #4790 - AddBusinessDays ignores holidays when DateTime being compared has time specified (#4791)
Co-authored-by: Adam May <adam.may@lifetrading.com.au>
2020-10-01 09:56:08 -03:00
Gerardo Salazar
40a87eb056 Fixes issue where BidPrice/AskPrice were not adjusted for Quote Ticks (#4789)
* Fixes issue where BidPrice/AskPrice were not adjusted for Quote Ticks

  * Previously, ticks would have their prices (Tick.Value) adjusted whenever
    TickType == TickType.Quote, but would not have their
    BidPrice/AskPrice fields adjusted, thus potentially being orders
    of magnitude such as 4x from the actual Bid/Ask prices.

    This commit applies the pricing scaling factor in a critical
    path where Ticks are adjusted to their scaled price. This issue
    only applied to Resolution.Tick && SecurityType.Equity data.

* Refactors Extensions Tick Scale extension method

  * Adjusts unit test to dispose of resources and assert history count

* Replaces use of FileSystemDataFeed for NullDataFeed in Adjustment test

* Adds regression algorithm testing BidPrice & AskPrice adjustment

* Address review: remove SecurityType check on TickType.Trade adjustments
2020-09-30 19:43:20 -03:00
Jatin Kumar
035b29fdf5 Update readme.md (#4793)
* Update readme.md

American English sadly.
2020-09-30 13:17:59 -07:00
michael-sena
f2fc1aae9e Append the full stacktrace to the algorithm loading exception message. (#4775)
* Append the full stacktrace to the algorithm loading exception message.

* Remove exception message loader duplication

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-09-30 12:42:21 -03:00
Martin-Molinero
ff5fc5db5d HistoryRequestFactory Hour ExtendedMarketHours (#4786)
- `HistoryRequestFactory` will not sure extended market hours for hour
  resolution when determining the start time using quantity of bars.
  Adding regression test
2020-09-29 18:16:27 -03:00
Gerardo Salazar
40c3062348 Improves Report Generator stability and fixes various bugs (#4759)
* Improves stability and fixes various bugs

  * Adds unit tests covering changes
  * Adds COVID-19 crisis plots
  * Adjusts styling of crisis plots for more pleasant viewing
  * Fixes bug where null BacktestResult caused application to crash
  * Order JSON bug fixes and stability improvements
  * MaxDrawdownReportElement now produces results for Live
  * Replaced Estimated Capacity w/ Days Live
  * Added Live marker to sharpe ratio
  * Added support for MOO and MOC orders in PortfolioLooper

* Address review: adds new unit tests and cleans up code

  * Bug fix: use LastFillTime instead of Order.Time for MOO and MOC

* Address review: Fixes tests and cleans up code
2020-09-29 10:03:26 -03:00
michael-sena
371f2cd469 Upgrade NLog from 4.4.11 to 4.4.13 (#4772) 2020-09-28 21:52:22 -03:00
Aaron Janeiro Stone
090ceb131e Install instructions: Non-Windows (#4777)
* Added warning for paths in non-Windows

As per https://github.com/dotnet/msbuild/issues/4216

* reserved character instructions - clarity
2020-09-28 21:51:21 -03:00
nazbrok
6885fd130b fix decimal conversion for exponential number (#4750) 2020-09-28 21:50:58 -03:00
Adalyat Nazirov
a450cea8d0 keep actual exchange hours even it different from hours database (#4781) 2020-09-28 20:22:08 -03:00
Stefano Raggi
934128cfa0 Binance Brokerage implementation (#4688)
* Binance Brokerage skeleton

* Market hours

* Implement Symbol Mapper

- known symbols available on /api/v1/exchangeInfo
- fiat currencies are pegged

* Implement GetCashBalance

* Implement GetAccountHoldings

- there are no pre-existing currency swaps
- cash balances are pulled and stored in the cashbook

* Implement GetOpenOrders

* Manage orders: PlaceOrder

* Manage orders: UpdateOrder

Update operation is not supported

* Manage orders: CancelOrder

* Messaging: order book

* Messaging: trades

* Messaging: combine streams

- connect to fake /ws/open channel on init
- case by channel name, but not event type

* Messaging: order depth updates

- ticker symbol is not enough as it pushes updates only once a second, this would be a very incomplete data stream
- fetch ticker snapshot if lastUpdateId == 0
- follow Binance instructions for keeping local orderbook fresh

* Messaging: user data streaming

- Request userDataStream endpoint to get listenKey
- keep listenkey alive
- handle order close event
- handle order fill event

* DataDownloader: get history

- we can aggregate minute candles for higher resolutions

* fix data stream

* Tests: FeeModel tests

* Tests: base brokerage tests

* Tests: download history

* Tests: symbol mapper

* Support StopLimit andd StopMarket orders

* StopMarket orders disabled

Take profit and Stop loss orders are not supported for any symbols (tested with BTCUSDT, ETHUSDT)

* Tests: StopLimit order

* Tests: crypto parsing

* Reissue user data listen key

* comment custom currency limitation

* rework websocket connections

* implement delayed subscription

* adapt ignore message

* add license banner

* use better suited exception type

* avoid message double parsing

* support custom fee values

* extract BinanceApiClient to manage the request/response between lean and binance

* use api events to invoke brokerage events

* do not allow to terminate session if it wasn't allocated.

* update binance exchange info

* tool to add or update binance exchange info

* ExchangeInfo basic test

* Rebase + Resharp

* Binance brokerage updates

- Fix sign bug in sell order fills
- Fix bug in GetHistory
- Remove duplicate symbol from symbol properties db

* Remove unused code

* Revert removal of account currency check

* Update symbols properties database

* Address review

* Address review

- Upgrade API endpoints from v1 to v3
- Updated sub/unsub for new subscription manager
- Subscribe best bid/ask quotes instead of full order book
- Added handling of websocket error messages
- Cleanup + refactor

* Update symbol properties database

* Remove list from symbol mapper

* Fix symbol mapper tests

* Address review

- Fix resubscribe after reconnect
- Fix quote tick edge case

* Fix EnsureCurrencyDataFeed for non-tradeable currencies

* Fix check in EnsureCurrencyDataFeed

* Reuse base class subscribe on reconnect

Co-authored-by: Adalyat Nazirov <aenazirov@gmail.com>
Co-authored-by: Martin-Molinero <martin@quantconnect.com>
2020-09-28 15:57:10 -03:00
Michael Handschuh
c7a74306fb Bug 4731 Fix Option Expiration Order Tags and Order Event IsAssignment Flag (#4743)
* Add OrderRight.GetExerciseDirection(isShort) extension

Returns the OrderDirection resulting from exercise/assignment of a particular
option right

See: BUG #4731

* Fix option exercise/assignment order tags and order event messages

The algorithm manager was doing work to determine whether or not the option ended
in exercise or assignment at expiration. This decision should be left for the exercise
model to decide -- from the algorithm manager's perspective, all that matters is that
the option was expired. The DefaultExerciseModel was updated to properly track whether
the option expired with automatic assignment or exercise, dependending on whether or
not we wrote or bought the option (held liability or right, respectively). Updated unit
tests to check for order event counts and order event messages for option exercise cases.

Fixes: #4731

* Fix typo in algorithm documentation

* Update regression tests order hash

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-09-28 15:29:09 -03:00
Aaron Janeiro Stone
cfacc755fa Removal of depreciated module called in test, addition of __init__ (#4769)
* Removal of depreciated module called in test, addition of __init__ to module.

* Delete __init__.py

Currently unnecessary
2020-09-28 11:36:02 -03:00
88 changed files with 5937 additions and 384 deletions

View File

@@ -75,7 +75,7 @@ namespace QuantConnect.Algorithm.CSharp
/// </summary>
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
{
if (!_equityBought && data.ContainsKey(_spy)) {
//Buy our Equity
var quantity = CalculateOrderQuantity(_spy, .1m);
@@ -114,7 +114,7 @@ namespace QuantConnect.Algorithm.CSharp
/// </summary>
/// <param name="orderEvent">OrderEvent object that contains all the information about the event</param>
public override void OnOrderEvent(OrderEvent orderEvent)
{
{
// Get the order from our transactions
var order = Transactions.GetOrderById(orderEvent.OrderId);
@@ -147,7 +147,7 @@ namespace QuantConnect.Algorithm.CSharp
// All PartiallyFilled orders should have a LastFillTime
case OrderStatus.PartiallyFilled:
if (order.LastFillTime == null)
if (order.LastFillTime == null)
{
throw new Exception("LastFillTime should not be null");
}
@@ -317,7 +317,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Treynor Ratio", "-0.018"},
{"Total Fees", "$2.00"},
{"Fitness Score", "0.213"},
{"OrderListHash", "-1514011542"}
{"OrderListHash", "-2119400842"}
};
}
}

View File

@@ -141,7 +141,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "1935621950"}
{"OrderListHash", "-1438496252"}
};
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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 System;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Interfaces;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Checks that the Tick BidPrice and AskPrices are adjusted like Value.
/// </summary>
public class EquityTickQuoteAdjustedModeRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _ibm;
private bool _bought;
private bool _sold;
public override void Initialize()
{
SetStartDate(2013, 10, 7);
SetEndDate(2013, 10, 11);
SetCash(100000);
_ibm = AddEquity("IBM", Resolution.Tick).Symbol;
}
public override void OnData(Slice data)
{
if (!data.Ticks.ContainsKey(_ibm))
{
return;
}
var security = Securities[_ibm];
if (!security.HasData)
{
return;
}
foreach (var tick in data.Ticks[_ibm])
{
if (tick.BidPrice != 0 && !_bought && ((tick.Value - tick.BidPrice) <= 0.05m))
{
SetHoldings(_ibm, 1);
_bought = true;
return;
}
if (tick.AskPrice != 0 && _bought && !_sold && Math.Abs((double)tick.Value - (double)tick.AskPrice) <= 0.05)
{
Liquidate(_ibm);
_sold = true;
return;
}
}
}
/// <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 Language[] Languages { get; } = { Language.CSharp };
/// <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 Trades", "2"},
{"Average Win", "0%"},
{"Average Loss", "-0.01%"},
{"Compounding Annual Return", "-0.500%"},
{"Drawdown", "0.000%"},
{"Expectancy", "-1"},
{"Net Profit", "-0.006%"},
{"Sharpe Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0"},
{"Beta", "0"},
{"Annual Standard Deviation", "0"},
{"Annual Variance", "0"},
{"Information Ratio", "-8.769"},
{"Tracking Error", "0.22"},
{"Treynor Ratio", "0"},
{"Total Fees", "$6.41"},
{"Fitness Score", "0.248"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "-82.815"},
{"Portfolio Turnover", "0.497"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
{"Total Insights Analysis Completed", "0"},
{"Long Insight Count", "0"},
{"Short Insight Count", "0"},
{"Long/Short Ratio", "100%"},
{"Estimated Monthly Alpha Value", "$0"},
{"Total Accumulated Estimated Alpha Value", "$0"},
{"Mean Population Estimated Insight Value", "$0"},
{"Mean Population Direction", "0%"},
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "1213851303"}
};
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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 System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Regression algorithm testing doing some history requests outside market hours, reproducing GH issue #4783
/// </summary>
public class ExtendedMarketHoursHistoryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private int _minuteHistoryCount;
private int _hourHistoryCount;
private int _dailyHistoryCount;
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
/// </summary>
public override void Initialize()
{
SetStartDate(2013, 10, 07);
SetEndDate(2013, 10, 09);
SetCash(100000);
AddEquity("SPY", Resolution.Minute, extendedMarketHours:true, fillDataForward:false);
Schedule.On("RunHistoryCall", DateRules.EveryDay(), TimeRules.Every(TimeSpan.FromHours(1)), RunHistoryCall);
}
private void RunHistoryCall()
{
var spy = Securities["SPY"];
var regularHours = spy.Exchange.Hours.IsOpen(Time, false);
var extendedHours = !regularHours && spy.Exchange.Hours.IsOpen(Time, true);
if (regularHours)
{
_minuteHistoryCount++;
var history = History(spy.Symbol, 5, Resolution.Minute).Count();
if (history != 5)
{
throw new Exception($"Unexpected Minute data count: {history}");
}
}
else
{
if (extendedHours)
{
_hourHistoryCount++;
var history = History(spy.Symbol, 5, Resolution.Hour).Count();
if (history != 5)
{
throw new Exception($"Unexpected Hour data count {history}");
}
}
else
{
_dailyHistoryCount++;
var history = History(spy.Symbol, 5, Resolution.Daily).Count();
if (history != 5)
{
throw new Exception($"Unexpected Daily data count {history}");
}
}
}
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
if (!Portfolio.Invested)
{
SetHoldings("SPY", 1);
}
}
public override void OnEndOfAlgorithm()
{
if (_minuteHistoryCount != 3 * 6)
{
throw new Exception($"Unexpected minute history requests count {_minuteHistoryCount}");
}
// 6 pre market from 4am to 9am + 4 post market 4pm to 7pm
if (_hourHistoryCount != 3 * 10)
{
throw new Exception($"Unexpected hour history requests count {_hourHistoryCount}");
}
// 0am to 3am + 8pm to 11pm, last day ends at 8pm
if (_dailyHistoryCount != (2 * 8 + 5))
{
throw new Exception($"Unexpected Daily history requests count: {_dailyHistoryCount}");
}
}
/// <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 Language[] Languages { get; } = { Language.CSharp };
/// <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 Trades", "20"},
{"Average Win", "0%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "-74.182%"},
{"Drawdown", "2.200%"},
{"Expectancy", "-1"},
{"Net Profit", "-1.046%"},
{"Sharpe Ratio", "-8.269"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.19"},
{"Beta", "0.579"},
{"Annual Standard Deviation", "0.065"},
{"Annual Variance", "0.004"},
{"Information Ratio", "1.326"},
{"Tracking Error", "0.049"},
{"Treynor Ratio", "-0.934"},
{"Total Fees", "$22.26"},
{"Fitness Score", "0.002"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-11.855"},
{"Return Over Maximum Drawdown", "-70.945"},
{"Portfolio Turnover", "0.342"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
{"Total Insights Analysis Completed", "0"},
{"Long Insight Count", "0"},
{"Short Insight Count", "0"},
{"Long/Short Ratio", "100%"},
{"Estimated Monthly Alpha Value", "$0"},
{"Total Accumulated Estimated Alpha Value", "$0"},
{"Mean Population Estimated Insight Value", "$0"},
{"Mean Population Direction", "0%"},
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "-1961710414"}
};
}
}

View File

@@ -21,7 +21,7 @@ using QuantConnect.Interfaces;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Demonstration of the Option Chain Provider -- a much faster mechanism for manually specifying the option contracts you'd like to recieve
/// Demonstration of the Option Chain Provider -- a much faster mechanism for manually specifying the option contracts you'd like to receive
/// data for and manually subscribing to them.
/// </summary>
/// <meta name="tag" content="strategy example" />

View File

@@ -161,7 +161,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "1038273097"}
{"OrderListHash", "-1143450142"}
};
}
}

View File

@@ -144,6 +144,8 @@
<Compile Include="AddAlphaModelAlgorithm.cs" />
<Compile Include="DailyHistoryForDailyResolutionRegressionAlgorithm.cs" />
<Compile Include="DailyHistoryForMinuteResolutionRegressionAlgorithm.cs" />
<Compile Include="ExtendedMarketHoursHistoryRegressionAlgorithm.cs" />
<Compile Include="EquityTickQuoteAdjustedModeRegressionAlgorithm.cs" />
<Compile Include="SwitchDataModeRegressionAlgorithm.cs" />
<Compile Include="AddRemoveOptionUniverseRegressionAlgorithm.cs" />
<Compile Include="AddRemoveSecurityRegressionAlgorithm.cs" />
@@ -457,4 +459,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -696,18 +696,21 @@ namespace QuantConnect.Algorithm
private SecurityExchangeHours GetExchangeHours(Symbol symbol)
{
Security security;
if (Securities.TryGetValue(symbol, out security))
{
return security.Exchange.Hours;
}
return GetMarketHours(symbol).ExchangeHours;
}
private MarketHoursDatabase.Entry GetMarketHours(Symbol symbol)
{
return MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
var hoursEntry = MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
// user can override the exchange hours in algorithm, i.e. HistoryAlgorithm
Security security;
if (Securities.TryGetValue(symbol, out security))
{
return new MarketHoursDatabase.Entry(hoursEntry.DataTimeZone, security.Exchange.Hours);
}
return hoursEntry;
}
private Resolution GetResolution(Symbol symbol, Resolution? resolution)

View File

@@ -780,7 +780,7 @@ namespace QuantConnect.Algorithm
var res = GetResolution(x, resolution);
var exchange = GetExchangeHours(x);
var start = _historyRequestFactory.GetStartTimeAlgoTz(x, periods, res, exchange, config.DataTimeZone);
return _historyRequestFactory.CreateHistoryRequest(config, start, Time.RoundDown(res.ToTimeSpan()), exchange, res);
return _historyRequestFactory.CreateHistoryRequest(config, start, Time, exchange, res);
});
return PandasConverter.GetDataFrame(History(requests.Where(x => x != null)).Memoize());
@@ -843,8 +843,7 @@ namespace QuantConnect.Algorithm
var res = GetResolution(symbol, resolution);
var marketHours = GetMarketHours(symbol);
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, res, marketHours.ExchangeHours, marketHours.DataTimeZone);
var end = Time.RoundDown(res.ToTimeSpan());
return History(type, symbol, start, end, resolution);
return History(type, symbol, start, Time, resolution);
}
/// <summary>

View File

@@ -288,8 +288,7 @@ logging.captureWarnings(True)"
catch (Exception err)
{
errorMessage = "Algorithm type name not found, or unable to resolve multiple algorithm types to a single type. Please verify algorithm type name matches the algorithm name in the configuration file and that there is one and only one class derived from QCAlgorithm.";
errorMessage += err.InnerException == null ? err.Message : err.InnerException.Message;
Log.Error($"Loader.TryCreateILAlgorithm(): {errorMessage}");
Log.Error($"Loader.TryCreateILAlgorithm(): {errorMessage}\n{err.InnerException ?? err}");
return false;
}

View File

@@ -0,0 +1,226 @@
/*
* 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.Binance.Messages;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using System;
using System.Collections.Concurrent;
using System.Linq;
using Newtonsoft.Json.Linq;
using QuantConnect.Data;
namespace QuantConnect.Brokerages.Binance
{
public partial class BinanceBrokerage
{
private readonly ConcurrentQueue<WebSocketMessage> _messageBuffer = new ConcurrentQueue<WebSocketMessage>();
private volatile bool _streamLocked;
private readonly IDataAggregator _aggregator;
/// <summary>
/// Locking object for the Ticks list in the data queue handler
/// </summary>
protected readonly object TickLocker = new object();
/// <summary>
/// Lock the streaming processing while we're sending orders as sometimes they fill before the REST call returns.
/// </summary>
private void LockStream()
{
_streamLocked = true;
}
/// <summary>
/// Unlock stream and process all backed up messages.
/// </summary>
private void UnlockStream()
{
while (_messageBuffer.Any())
{
WebSocketMessage e;
_messageBuffer.TryDequeue(out e);
OnMessageImpl(e);
}
// Once dequeued in order; unlock stream.
_streamLocked = false;
}
private void WithLockedStream(Action code)
{
try
{
LockStream();
code();
}
finally
{
UnlockStream();
}
}
private void OnMessageImpl(WebSocketMessage e)
{
try
{
var obj = JObject.Parse(e.Message);
var objError = obj["error"];
if (objError != null)
{
var error = objError.ToObject<ErrorMessage>();
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, error.Code, error.Message));
return;
}
var objData = obj;
var objEventType = objData["e"];
if (objEventType != null)
{
var eventType = objEventType.ToObject<string>();
switch (eventType)
{
case "executionReport":
var upd = objData.ToObject<Execution>();
if (upd.ExecutionType.Equals("TRADE", StringComparison.OrdinalIgnoreCase))
{
OnFillOrder(upd);
}
break;
case "trade":
var trade = objData.ToObject<Trade>();
EmitTradeTick(
_symbolMapper.GetLeanSymbol(trade.Symbol),
Time.UnixMillisecondTimeStampToDateTime(trade.Time),
trade.Price,
trade.Quantity);
break;
}
}
else if (objData["u"] != null)
{
var quote = objData.ToObject<BestBidAskQuote>();
EmitQuoteTick(
_symbolMapper.GetLeanSymbol(quote.Symbol),
quote.BestBidPrice,
quote.BestBidSize,
quote.BestAskPrice,
quote.BestAskSize);
}
}
catch (Exception exception)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1, $"Parsing wss message failed. Data: {e.Message} Exception: {exception}"));
throw;
}
}
private void EmitQuoteTick(Symbol symbol, decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize)
{
var tick = new Tick
{
AskPrice = askPrice,
BidPrice = bidPrice,
Time = DateTime.UtcNow,
Symbol = symbol,
TickType = TickType.Quote,
AskSize = askSize,
BidSize = bidSize
};
tick.SetValue();
lock (TickLocker)
{
_aggregator.Update(tick);
}
}
private void EmitTradeTick(Symbol symbol, DateTime time, decimal price, decimal quantity)
{
var tick = new Tick
{
Symbol = symbol,
Value = price,
Quantity = Math.Abs(quantity),
Time = time,
TickType = TickType.Trade
};
lock (TickLocker)
{
_aggregator.Update(tick);
}
}
private void OnFillOrder(Execution data)
{
try
{
var order = FindOrderByExternalId(data.OrderId);
if (order == null)
{
// not our order, nothing else to do here
return;
}
var fillPrice = data.LastExecutedPrice;
var fillQuantity = data.Direction == OrderDirection.Sell ? -data.LastExecutedQuantity : data.LastExecutedQuantity;
var updTime = Time.UnixMillisecondTimeStampToDateTime(data.TransactionTime);
var orderFee = new OrderFee(new CashAmount(data.Fee, data.FeeCurrency));
var status = ConvertOrderStatus(data.OrderStatus);
var orderEvent = new OrderEvent
(
order.Id, order.Symbol, updTime, status,
data.Direction, fillPrice, fillQuantity,
orderFee, $"Binance Order Event {data.Direction}"
);
if (status == OrderStatus.Filled)
{
Orders.Order outOrder;
CachedOrderIDs.TryRemove(order.Id, out outOrder);
}
OnOrderEvent(orderEvent);
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
private Orders.Order FindOrderByExternalId(string brokerId)
{
var order = CachedOrderIDs
.FirstOrDefault(o => o.Value.BrokerId.Contains(brokerId))
.Value;
if (order == null)
{
order = _algorithm.Transactions.GetOrderByBrokerageId(brokerId);
}
return order;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.Orders;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Binance utility methods
/// </summary>
public partial class BinanceBrokerage
{
private static OrderStatus ConvertOrderStatus(string raw)
{
switch (raw.LazyToUpper())
{
case "NEW":
return OrderStatus.New;
case "PARTIALLY_FILLED":
return OrderStatus.PartiallyFilled;
case "FILLED":
return OrderStatus.Filled;
case "PENDING_CANCEL":
return OrderStatus.CancelPending;
case "CANCELED":
return OrderStatus.Canceled;
case "REJECTED":
case "EXPIRED":
return OrderStatus.Invalid;
default:
return OrderStatus.None;
}
}
}
}

View File

@@ -0,0 +1,491 @@
/*
* 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.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using QuantConnect.Util;
using Timer = System.Timers.Timer;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Binance brokerage implementation
/// </summary>
[BrokerageFactory(typeof(BinanceBrokerageFactory))]
public partial class BinanceBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
{
private const string WebSocketBaseUrl = "wss://stream.binance.com:9443/ws";
private readonly IAlgorithm _algorithm;
private readonly BinanceSymbolMapper _symbolMapper = new BinanceSymbolMapper();
private readonly RateGate _webSocketRateLimiter = new RateGate(5, TimeSpan.FromSeconds(1));
private long _lastRequestId;
private readonly Timer _keepAliveTimer;
private readonly Timer _reconnectTimer;
private readonly BinanceRestApiClient _apiClient;
/// <summary>
/// Constructor for brokerage
/// </summary>
/// <param name="apiKey">api key</param>
/// <param name="apiSecret">api secret</param>
/// <param name="algorithm">the algorithm instance is required to retrieve account type</param>
/// <param name="aggregator">the aggregator for consolidating ticks</param>
public BinanceBrokerage(string apiKey, string apiSecret, IAlgorithm algorithm, IDataAggregator aggregator)
: base(WebSocketBaseUrl, new WebSocketClientWrapper(), null, apiKey, apiSecret, "Binance")
{
_algorithm = algorithm;
_aggregator = aggregator;
var subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
subscriptionManager.SubscribeImpl += (s, t) =>
{
Subscribe(s);
return true;
};
subscriptionManager.UnsubscribeImpl += (s, t) => Unsubscribe(s);
SubscriptionManager = subscriptionManager;
_apiClient = new BinanceRestApiClient(
_symbolMapper,
algorithm?.Portfolio,
apiKey,
apiSecret);
_apiClient.OrderSubmit += (s, e) => OnOrderSubmit(e);
_apiClient.OrderStatusChanged += (s, e) => OnOrderEvent(e);
_apiClient.Message += (s, e) => OnMessage(e);
// User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes.
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#pingkeep-alive-a-listenkey
_keepAliveTimer = new Timer
{
// 30 minutes
Interval = 30 * 60 * 1000
};
_keepAliveTimer.Elapsed += (s, e) => _apiClient.SessionKeepAlive();
WebSocket.Open += (s, e) => { _keepAliveTimer.Start(); };
WebSocket.Closed += (s, e) => { _keepAliveTimer.Stop(); };
// A single connection to stream.binance.com is only valid for 24 hours; expect to be disconnected at the 24 hour mark
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#general-wss-information
_reconnectTimer = new Timer
{
// 23.5 hours
Interval = 23.5 * 60 * 60 * 1000
};
_reconnectTimer.Elapsed += (s, e) =>
{
Log.Trace("Daily websocket restart: disconnect");
Disconnect();
Log.Trace("Daily websocket restart: connect");
Connect();
};
}
#region IBrokerage
/// <summary>
/// Checks if the websocket connection is connected or in the process of connecting
/// </summary>
public override bool IsConnected => WebSocket.IsOpen;
/// <summary>
/// Creates wss connection
/// </summary>
public override void Connect()
{
if (IsConnected)
return;
_apiClient.CreateListenKey();
_reconnectTimer.Start();
WebSocket.Initialize($"{WebSocketBaseUrl}/{_apiClient.SessionId}");
base.Connect();
}
/// <summary>
/// Closes the websockets connection
/// </summary>
public override void Disconnect()
{
_reconnectTimer.Stop();
WebSocket?.Close();
_apiClient.StopSession();
}
/// <summary>
/// Gets all open positions
/// </summary>
/// <returns></returns>
public override List<Holding> GetAccountHoldings()
{
return _apiClient.GetAccountHoldings();
}
/// <summary>
/// Gets the total account cash balance for specified account type
/// </summary>
/// <returns></returns>
public override List<CashAmount> GetCashBalance()
{
var account = _apiClient.GetCashBalance();
var balances = account.Balances?.Where(balance => balance.Amount > 0).ToList();
if (balances == null || !balances.Any())
return new List<CashAmount>();
return balances
.Select(b => new CashAmount(b.Amount, b.Asset.LazyToUpper()))
.ToList();
}
/// <summary>
/// Gets all orders not yet closed
/// </summary>
/// <returns></returns>
public override List<Order> GetOpenOrders()
{
var orders = _apiClient.GetOpenOrders();
List<Order> list = new List<Order>();
foreach (var item in orders)
{
Order order;
switch (item.Type.LazyToUpper())
{
case "MARKET":
order = new MarketOrder { Price = item.Price };
break;
case "LIMIT":
case "LIMIT_MAKER":
order = new LimitOrder { LimitPrice = item.Price };
break;
case "STOP_LOSS":
case "TAKE_PROFIT":
order = new StopMarketOrder { StopPrice = item.StopPrice, Price = item.Price };
break;
case "STOP_LOSS_LIMIT":
case "TAKE_PROFIT_LIMIT":
order = new StopLimitOrder { StopPrice = item.StopPrice, LimitPrice = item.Price };
break;
default:
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1,
"BinanceBrokerage.GetOpenOrders: Unsupported order type returned from brokerage: " + item.Type));
continue;
}
order.Quantity = item.Quantity;
order.BrokerId = new List<string> { item.Id };
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol);
order.Time = Time.UnixMillisecondTimeStampToDateTime(item.Time);
order.Status = ConvertOrderStatus(item.Status);
order.Price = item.Price;
if (order.Status.IsOpen())
{
var cached = CachedOrderIDs.Where(c => c.Value.BrokerId.Contains(order.BrokerId.First())).ToList();
if (cached.Any())
{
CachedOrderIDs[cached.First().Key] = order;
}
}
list.Add(order);
}
return list;
}
/// <summary>
/// Places a new order and assigns a new broker ID to the order
/// </summary>
/// <param name="order">The order to be placed</param>
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
public override bool PlaceOrder(Order order)
{
var submitted = false;
WithLockedStream(() =>
{
submitted = _apiClient.PlaceOrder(order);
});
return submitted;
}
/// <summary>
/// Updates the order with the same id
/// </summary>
/// <param name="order">The new order information</param>
/// <returns>True if the request was made for the order to be updated, false otherwise</returns>
public override bool UpdateOrder(Order order)
{
throw new NotSupportedException("BinanceBrokerage.UpdateOrder: Order update not supported. Please cancel and re-create.");
}
/// <summary>
/// Cancels the order with the specified ID
/// </summary>
/// <param name="order">The order to cancel</param>
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
public override bool CancelOrder(Order order)
{
var submitted = false;
WithLockedStream(() =>
{
submitted = _apiClient.CancelOrder(order);
});
return submitted;
}
/// <summary>
/// Gets the history for the requested security
/// </summary>
/// <param name="request">The historical data request</param>
/// <returns>An enumerable of bars covering the span specified in the request</returns>
public override IEnumerable<BaseData> GetHistory(Data.HistoryRequest request)
{
if (request.Resolution == Resolution.Tick || request.Resolution == Resolution.Second)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidResolution",
$"{request.Resolution} resolution is not supported, no history returned"));
yield break;
}
if (request.TickType != TickType.Trade)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidTickType",
$"{request.TickType} tick type not supported, no history returned"));
yield break;
}
var period = request.Resolution.ToTimeSpan();
foreach (var kline in _apiClient.GetHistory(request))
{
yield return new TradeBar()
{
Time = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime),
Symbol = request.Symbol,
Low = kline.Low,
High = kline.High,
Open = kline.Open,
Close = kline.Close,
Volume = kline.Volume,
Value = kline.Close,
DataType = MarketDataType.TradeBar,
Period = period,
EndTime = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime + (long)period.TotalMilliseconds)
};
}
}
/// <summary>
/// Wss message handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public override void OnMessage(object sender, WebSocketMessage e)
{
try
{
if (_streamLocked)
{
_messageBuffer.Enqueue(e);
return;
}
}
catch (Exception err)
{
Log.Error(err);
}
OnMessageImpl(e);
}
#endregion
#region IDataQueueHandler
/// <summary>
/// Sets the job we're subscribing for
/// </summary>
/// <param name="job">Job we're subscribing for</param>
public void SetJob(LiveNodePacket job)
{
}
/// <summary>
/// Subscribe to the specified configuration
/// </summary>
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
/// <returns>The new enumerator for this subscription request</returns>
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
if (!CanSubscribe(dataConfig.Symbol))
{
return Enumerable.Empty<BaseData>().GetEnumerator();
}
var enumerator = _aggregator.Add(dataConfig, newDataAvailableHandler);
SubscriptionManager.Subscribe(dataConfig);
return enumerator;
}
/// <summary>
/// Removes the specified configuration
/// </summary>
/// <param name="dataConfig">Subscription config to be removed</param>
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
SubscriptionManager.Unsubscribe(dataConfig);
_aggregator.Remove(dataConfig);
}
/// <summary>
/// Checks if this brokerage supports the specified symbol
/// </summary>
/// <param name="symbol">The symbol</param>
/// <returns>returns true if brokerage supports the specified symbol; otherwise false</returns>
private static bool CanSubscribe(Symbol symbol)
{
return !symbol.Value.Contains("UNIVERSE") &&
symbol.SecurityType == SecurityType.Crypto;
}
#endregion
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
_keepAliveTimer.DisposeSafely();
_reconnectTimer.DisposeSafely();
_apiClient.DisposeSafely();
_webSocketRateLimiter.DisposeSafely();
}
/// <summary>
/// Subscribes to the requested symbols (using an individual streaming channel)
/// </summary>
/// <param name="symbols">The list of symbols to subscribe</param>
public override void Subscribe(IEnumerable<Symbol> symbols)
{
foreach (var symbol in symbols)
{
Send(WebSocket,
new
{
method = "SUBSCRIBE",
@params = new[]
{
$"{symbol.Value.ToLowerInvariant()}@trade",
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
},
id = GetNextRequestId()
}
);
}
}
/// <summary>
/// Ends current subscriptions
/// </summary>
private bool Unsubscribe(IEnumerable<Symbol> symbols)
{
if (WebSocket.IsOpen)
{
foreach (var symbol in symbols)
{
Send(WebSocket,
new
{
method = "UNSUBSCRIBE",
@params = new[]
{
$"{symbol.Value.ToLowerInvariant()}@trade",
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
},
id = GetNextRequestId()
}
);
}
}
return true;
}
private void Send(IWebSocket webSocket, object obj)
{
var json = JsonConvert.SerializeObject(obj);
if (!_webSocketRateLimiter.WaitToProceed(TimeSpan.Zero))
{
_webSocketRateLimiter.WaitToProceed();
}
Log.Trace("Send: " + json);
webSocket.Send(json);
}
private long GetNextRequestId()
{
return Interlocked.Increment(ref _lastRequestId);
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="e">The OrderEvent</param>
private void OnOrderSubmit(BinanceOrderSubmitEventArgs e)
{
var brokerId = e.BrokerId;
var order = e.Order;
if (CachedOrderIDs.ContainsKey(order.Id))
{
CachedOrderIDs[order.Id].BrokerId.Clear();
CachedOrderIDs[order.Id].BrokerId.Add(brokerId);
}
else
{
order.BrokerId.Add(brokerId);
CachedOrderIDs.TryAdd(order.Id, order);
}
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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 System;
using System.Collections.Generic;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Factory method to create binance Websockets brokerage
/// </summary>
public class BinanceBrokerageFactory : BrokerageFactory
{
/// <summary>
/// Factory constructor
/// </summary>
public BinanceBrokerageFactory() : base(typeof(BinanceBrokerage))
{
}
/// <summary>
/// Not required
/// </summary>
public override void Dispose()
{
}
/// <summary>
/// provides brokerage connection data
/// </summary>
public override Dictionary<string, string> BrokerageData => new Dictionary<string, string>
{
{ "binance-api-key", Config.Get("binance-api-key")},
{ "binance-api-secret", Config.Get("binance-api-secret")}
};
/// <summary>
/// The brokerage model
/// </summary>
/// <param name="orderProvider">The order provider</param>
public override IBrokerageModel GetBrokerageModel(IOrderProvider orderProvider) => new BinanceBrokerageModel();
/// <summary>
/// Create the Brokerage instance
/// </summary>
/// <param name="job"></param>
/// <param name="algorithm"></param>
/// <returns></returns>
public override IBrokerage CreateBrokerage(Packets.LiveNodePacket job, IAlgorithm algorithm)
{
var required = new[] { "binance-api-secret", "binance-api-key" };
foreach (var item in required)
{
if (string.IsNullOrEmpty(job.BrokerageData[item]))
{
throw new Exception($"BinanceBrokerageFactory.CreateBrokerage: Missing {item} in config.json");
}
}
var brokerage = new BinanceBrokerage(
job.BrokerageData["binance-api-key"],
job.BrokerageData["binance-api-secret"],
algorithm,
Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager")));
Composer.Instance.AddPart<IDataQueueHandler>(brokerage);
return brokerage;
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.Orders;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Represents a binance submit order event data
/// </summary>
public class BinanceOrderSubmitEventArgs
{
/// <summary>
/// Order Event Constructor.
/// </summary>
/// <param name="brokerId">Binance order id returned from brokerage</param>
/// <param name="order">Order for this order placement</param>
public BinanceOrderSubmitEventArgs(string brokerId, Order order)
{
BrokerId = brokerId;
Order = order;
}
/// <summary>
/// Original brokerage id
/// </summary>
public string BrokerId { get; set; }
/// <summary>
/// The lean order
/// </summary>
public Order Order { get; set; }
}
}

View File

@@ -0,0 +1,590 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Util;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Binance REST API implementation
/// </summary>
public class BinanceRestApiClient : IDisposable
{
private const string RestApiUrl = "https://api.binance.com";
private const string UserDataStreamEndpoint = "/api/v3/userDataStream";
private readonly BinanceSymbolMapper _symbolMapper;
private readonly ISecurityProvider _securityProvider;
private readonly IRestClient _restClient;
private readonly RateGate _restRateLimiter = new RateGate(10, TimeSpan.FromSeconds(1));
private readonly object _listenKeyLocker = new object();
/// <summary>
/// Event that fires each time an order is filled
/// </summary>
public event EventHandler<BinanceOrderSubmitEventArgs> OrderSubmit;
/// <summary>
/// Event that fires each time an order is filled
/// </summary>
public event EventHandler<OrderEvent> OrderStatusChanged;
/// <summary>
/// Event that fires when an error is encountered in the brokerage
/// </summary>
public event EventHandler<BrokerageMessageEvent> Message;
/// <summary>
/// Key Header
/// </summary>
public readonly string KeyHeader = "X-MBX-APIKEY";
/// <summary>
/// The api secret
/// </summary>
protected string ApiSecret;
/// <summary>
/// The api key
/// </summary>
protected string ApiKey;
/// <summary>
/// Represents UserData Session listen key
/// </summary>
public string SessionId { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BinanceRestApiClient"/> class.
/// </summary>
/// <param name="symbolMapper">The symbol mapper.</param>
/// <param name="securityProvider">The holdings provider.</param>
/// <param name="apiKey">The Binance API key</param>
/// <param name="apiSecret">The The Binance API secret</param>
public BinanceRestApiClient(BinanceSymbolMapper symbolMapper, ISecurityProvider securityProvider, string apiKey, string apiSecret)
{
_symbolMapper = symbolMapper;
_securityProvider = securityProvider;
_restClient = new RestClient(RestApiUrl);
ApiKey = apiKey;
ApiSecret = apiSecret;
}
/// <summary>
/// Gets all open positions
/// </summary>
/// <returns></returns>
public List<Holding> GetAccountHoldings()
{
return new List<Holding>();
}
/// <summary>
/// Gets the total account cash balance for specified account type
/// </summary>
/// <returns></returns>
public Messages.AccountInformation GetCashBalance()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/account?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.AccountInformation>(response.Content);
}
/// <summary>
/// Gets all orders not yet closed
/// </summary>
/// <returns></returns>
public IEnumerable<Messages.OpenOrder> GetOpenOrders()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/openOrders?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.OpenOrder[]>(response.Content);
}
/// <summary>
/// Places a new order and assigns a new broker ID to the order
/// </summary>
/// <param name="order">The order to be placed</param>
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
public bool PlaceOrder(Order order)
{
// supported time in force values {GTC, IOC, FOK}
// use GTC as LEAN doesn't support others yet
IDictionary<string, object> body = new Dictionary<string, object>()
{
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) },
{ "quantity", Math.Abs(order.Quantity).ToString(CultureInfo.InvariantCulture) },
{ "side", ConvertOrderDirection(order.Direction) }
};
switch (order.Type)
{
case OrderType.Limit:
body["type"] = (order.Properties as BinanceOrderProperties)?.PostOnly == true
? "LIMIT_MAKER"
: "LIMIT";
body["price"] = ((LimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
// timeInForce is not required for LIMIT_MAKER
if (Equals(body["type"], "LIMIT"))
body["timeInForce"] = "GTC";
break;
case OrderType.Market:
body["type"] = "MARKET";
break;
case OrderType.StopLimit:
var ticker = GetTickerPrice(order);
var stopPrice = ((StopLimitOrder) order).StopPrice;
if (order.Direction == OrderDirection.Sell)
{
body["type"] = stopPrice <= ticker ? "STOP_LOSS_LIMIT" : "TAKE_PROFIT_LIMIT";
}
else
{
body["type"] = stopPrice <= ticker ? "TAKE_PROFIT_LIMIT" : "STOP_LOSS_LIMIT";
}
body["timeInForce"] = "GTC";
body["stopPrice"] = stopPrice.ToString(CultureInfo.InvariantCulture);
body["price"] = ((StopLimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
break;
default:
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderType: Unsupported order type: {order.Type}");
}
const string endpoint = "/api/v3/order";
body["timestamp"] = GetNonce();
body["signature"] = AuthenticationToken(body.ToQueryString());
var request = new RestRequest(endpoint, Method.POST);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes(body.ToQueryString()),
ParameterType.RequestBody
);
var response = ExecuteRestRequest(request);
if (response.StatusCode == HttpStatusCode.OK)
{
var raw = JsonConvert.DeserializeObject<Messages.NewOrder>(response.Content);
if (string.IsNullOrEmpty(raw?.Id))
{
var errorMessage = $"Error parsing response from place order: {response.Content}";
OnOrderEvent(new OrderEvent(
order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Invalid, Message = errorMessage });
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage));
return true;
}
OnOrderSubmit(raw, order);
return true;
}
var message = $"Order failed, Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}";
OnOrderEvent(new OrderEvent(
order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Invalid });
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, message));
return true;
}
/// <summary>
/// Cancels the order with the specified ID
/// </summary>
/// <param name="order">The order to cancel</param>
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
public bool CancelOrder(Order order)
{
var success = new List<bool>();
IDictionary<string, object> body = new Dictionary<string, object>()
{
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) }
};
foreach (var id in order.BrokerId)
{
if (body.ContainsKey("signature"))
{
body.Remove("signature");
}
body["orderId"] = id;
body["timestamp"] = GetNonce();
body["signature"] = AuthenticationToken(body.ToQueryString());
var request = new RestRequest("/api/v3/order", Method.DELETE);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes(body.ToQueryString()),
ParameterType.RequestBody
);
var response = ExecuteRestRequest(request);
success.Add(response.StatusCode == HttpStatusCode.OK);
}
var canceled = false;
if (success.All(a => a))
{
OnOrderEvent(new OrderEvent(order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Canceled });
canceled = true;
}
return canceled;
}
/// <summary>
/// Gets the history for the requested security
/// </summary>
/// <param name="request">The historical data request</param>
/// <returns>An enumerable of bars covering the span specified in the request</returns>
public IEnumerable<Messages.Kline> GetHistory(Data.HistoryRequest request)
{
var resolution = ConvertResolution(request.Resolution);
var resolutionInMs = (long)request.Resolution.ToTimeSpan().TotalMilliseconds;
var symbol = _symbolMapper.GetBrokerageSymbol(request.Symbol);
var startMs = (long)Time.DateTimeToUnixTimeStamp(request.StartTimeUtc) * 1000;
var endMs = (long)Time.DateTimeToUnixTimeStamp(request.EndTimeUtc) * 1000;
var endpoint = $"/api/v3/klines?symbol={symbol}&interval={resolution}&limit=1000";
while (endMs - startMs >= resolutionInMs)
{
var timeframe = $"&startTime={startMs}&endTime={endMs}";
var restRequest = new RestRequest(endpoint + timeframe, Method.GET);
var response = ExecuteRestRequest(restRequest);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetHistory: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var klines = JsonConvert.DeserializeObject<object[][]>(response.Content)
.Select(entries => new Messages.Kline(entries))
.ToList();
startMs = klines.Last().OpenTime + resolutionInMs;
foreach (var kline in klines)
{
yield return kline;
}
}
}
/// <summary>
/// Check User Data stream listen key is alive
/// </summary>
/// <returns></returns>
public bool SessionKeepAlive()
{
if (string.IsNullOrEmpty(SessionId))
{
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
}
var ping = new RestRequest(UserDataStreamEndpoint, Method.PUT);
ping.AddHeader(KeyHeader, ApiKey);
ping.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
ParameterType.RequestBody
);
var pong = ExecuteRestRequest(ping);
return pong.StatusCode == HttpStatusCode.OK;
}
/// <summary>
/// Stops the session
/// </summary>
public void StopSession()
{
if (string.IsNullOrEmpty(SessionId))
{
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
}
var request = new RestRequest(UserDataStreamEndpoint, Method.DELETE);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
ParameterType.RequestBody
);
ExecuteRestRequest(request);
}
/// <summary>
/// Provides the current tickers price
/// </summary>
/// <returns></returns>
public Messages.PriceTicker[] GetTickers()
{
const string endpoint = "/api/v3/ticker/price";
var req = new RestRequest(endpoint, Method.GET);
var response = ExecuteRestRequest(req);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetTick: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.PriceTicker[]>(response.Content);
}
/// <summary>
/// Start user data stream
/// </summary>
public void CreateListenKey()
{
var request = new RestRequest(UserDataStreamEndpoint, Method.POST);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.StartSession: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var content = JObject.Parse(response.Content);
lock (_listenKeyLocker)
{
SessionId = content.Value<string>("listenKey");
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_restRateLimiter.DisposeSafely();
}
/// <summary>
/// If an IP address exceeds a certain number of requests per minute
/// HTTP 429 return code is used when breaking a request rate limit.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private IRestResponse ExecuteRestRequest(IRestRequest request)
{
const int maxAttempts = 10;
var attempts = 0;
IRestResponse response;
do
{
if (!_restRateLimiter.WaitToProceed(TimeSpan.Zero))
{
Log.Trace("Brokerage.OnMessage(): " + new BrokerageMessageEvent(BrokerageMessageType.Warning, "RateLimit",
"The API request has been rate limited. To avoid this message, please reduce the frequency of API calls."));
_restRateLimiter.WaitToProceed();
}
response = _restClient.Execute(request);
// 429 status code: Too Many Requests
} while (++attempts < maxAttempts && (int)response.StatusCode == 429);
return response;
}
private decimal GetTickerPrice(Order order)
{
var security = _securityProvider.GetSecurity(order.Symbol);
var tickerPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
if (tickerPrice == 0)
{
var brokerageSymbol = _symbolMapper.GetBrokerageSymbol(order.Symbol);
var tickers = GetTickers();
var ticker = tickers.FirstOrDefault(t => t.Symbol == brokerageSymbol);
if (ticker == null)
{
throw new KeyNotFoundException($"BinanceBrokerage: Unable to resolve currency conversion pair: {order.Symbol}");
}
tickerPrice = ticker.Price;
}
return tickerPrice;
}
/// <summary>
/// Timestamp in milliseconds
/// </summary>
/// <returns></returns>
private long GetNonce()
{
return (long)(Time.TimeStamp() * 1000);
}
/// <summary>
/// Creates a signature for signed endpoints
/// </summary>
/// <param name="payload">the body of the request</param>
/// <returns>a token representing the request params</returns>
private string AuthenticationToken(string payload)
{
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ApiSecret)))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)).ToHexString();
}
}
private static string ConvertOrderDirection(OrderDirection orderDirection)
{
if (orderDirection == OrderDirection.Buy || orderDirection == OrderDirection.Sell)
{
return orderDirection.ToString().LazyToUpper();
}
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderDirection: Unsupported order direction: {orderDirection}");
}
private readonly Dictionary<Resolution, string> _knownResolutions = new Dictionary<Resolution, string>()
{
{ Resolution.Minute, "1m" },
{ Resolution.Hour, "1h" },
{ Resolution.Daily, "1d" }
};
private string ConvertResolution(Resolution resolution)
{
if (_knownResolutions.ContainsKey(resolution))
{
return _knownResolutions[resolution];
}
else
{
throw new ArgumentException($"BinanceBrokerage.ConvertResolution: Unsupported resolution type: {resolution}");
}
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="newOrder">The brokerage order submit result</param>
/// <param name="order">The lean order</param>
private void OnOrderSubmit(Messages.NewOrder newOrder, Order order)
{
try
{
OrderSubmit?.Invoke(
this,
new BinanceOrderSubmitEventArgs(newOrder.Id, order));
// Generate submitted event
OnOrderEvent(new OrderEvent(
order,
Time.UnixMillisecondTimeStampToDateTime(newOrder.TransactionTime),
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Submitted }
);
Log.Trace($"Order submitted successfully - OrderId: {order.Id}");
}
catch (Exception err)
{
Log.Error(err);
}
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="e">The OrderEvent</param>
private void OnOrderEvent(OrderEvent e)
{
try
{
Log.Debug("Brokerage.OnOrderEvent(): " + e);
OrderStatusChanged?.Invoke(this, e);
}
catch (Exception err)
{
Log.Error(err);
}
}
/// <summary>
/// Event invocator for the Message event
/// </summary>
/// <param name="e">The error</param>
protected virtual void OnMessage(BrokerageMessageEvent e)
{
try
{
if (e.Type == BrokerageMessageType.Error)
{
Log.Error("Brokerage.OnMessage(): " + e);
}
else
{
Log.Trace("Brokerage.OnMessage(): " + e);
}
Message?.Invoke(this, e);
}
catch (Exception err)
{
Log.Error(err);
}
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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 System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Securities;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Provides the mapping between Lean symbols and Binance symbols.
/// </summary>
public class BinanceSymbolMapper : ISymbolMapper
{
/// <summary>
/// The list of known Binance symbols.
/// </summary>
public static readonly HashSet<string> KnownTickers =
new HashSet<string>(SymbolPropertiesDatabase
.FromDataFolder()
.GetSymbolPropertiesList(Market.Binance, SecurityType.Crypto)
.Select(x => x.Key.Symbol));
/// <summary>
/// Converts a Lean symbol instance to an Binance symbol
/// </summary>
/// <param name="symbol">A Lean symbol instance</param>
/// <returns>The Binance symbol</returns>
public string GetBrokerageSymbol(Symbol symbol)
{
if (symbol == null || string.IsNullOrWhiteSpace(symbol.Value))
throw new ArgumentException("Invalid symbol: " + (symbol == null ? "null" : symbol.ToString()));
if (symbol.SecurityType != SecurityType.Crypto)
throw new ArgumentException("Invalid security type: " + symbol.SecurityType);
var brokerageSymbol = ConvertLeanSymbolToBrokerageSymbol(symbol.Value);
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException("Unknown symbol: " + symbol.Value);
return brokerageSymbol;
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <param name="securityType">The security type</param>
/// <param name="market">The market</param>
/// <param name="expirationDate">Expiration date of the security(if applicable)</param>
/// <param name="strike">The strike of the security (if applicable)</param>
/// <param name="optionRight">The option right of the security (if applicable)</param>
/// <returns>A new Lean Symbol instance</returns>
public Symbol GetLeanSymbol(string brokerageSymbol, SecurityType securityType, string market, DateTime expirationDate = default(DateTime), decimal strike = 0, OptionRight optionRight = 0)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
throw new ArgumentException($"Invalid Binance symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Binance symbol: {brokerageSymbol}");
if (securityType != SecurityType.Crypto)
throw new ArgumentException($"Invalid security type: {securityType}");
if (market != Market.Binance)
throw new ArgumentException($"Invalid market: {market}");
return Symbol.Create(ConvertBrokerageSymbolToLeanSymbol(brokerageSymbol), GetBrokerageSecurityType(brokerageSymbol), Market.Binance);
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>A new Lean Symbol instance</returns>
public Symbol GetLeanSymbol(string brokerageSymbol)
{
var securityType = GetBrokerageSecurityType(brokerageSymbol);
return GetLeanSymbol(brokerageSymbol, securityType, Market.Binance);
}
/// <summary>
/// Returns the security type for an Binance symbol
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>The security type</returns>
public SecurityType GetBrokerageSecurityType(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
throw new ArgumentException($"Invalid Binance symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Binance symbol: {brokerageSymbol}");
return SecurityType.Crypto;
}
/// <summary>
/// Returns the security type for a Lean symbol
/// </summary>
/// <param name="leanSymbol">The Lean symbol</param>
/// <returns>The security type</returns>
public SecurityType GetLeanSecurityType(string leanSymbol)
{
return GetBrokerageSecurityType(ConvertLeanSymbolToBrokerageSymbol(leanSymbol));
}
/// <summary>
/// Checks if the symbol is supported by Binance
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>True if Binance supports the symbol</returns>
public bool IsKnownBrokerageSymbol(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
return false;
return KnownTickers.Contains(brokerageSymbol);
}
/// <summary>
/// Checks if the symbol is supported by Binance
/// </summary>
/// <param name="symbol">The Lean symbol</param>
/// <returns>True if Binance supports the symbol</returns>
public bool IsKnownLeanSymbol(Symbol symbol)
{
if (string.IsNullOrWhiteSpace(symbol?.Value) || symbol.Value.Length <= 3)
return false;
var binanceSymbol = ConvertLeanSymbolToBrokerageSymbol(symbol.Value);
return IsKnownBrokerageSymbol(binanceSymbol) && GetBrokerageSecurityType(binanceSymbol) == symbol.SecurityType;
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol string
/// </summary>
private static string ConvertBrokerageSymbolToLeanSymbol(string binanceSymbol)
{
if (string.IsNullOrWhiteSpace(binanceSymbol))
throw new ArgumentException($"Invalid Binance symbol: {binanceSymbol}");
// return as it is due to Binance has similar Symbol format
return binanceSymbol.LazyToUpper();
}
/// <summary>
/// Converts a Lean symbol string to an Binance symbol
/// </summary>
private static string ConvertLeanSymbolToBrokerageSymbol(string leanSymbol)
{
if (string.IsNullOrWhiteSpace(leanSymbol))
throw new ArgumentException($"Invalid Lean symbol: {leanSymbol}");
// return as it is due to Binance has similar Symbol format
return leanSymbol.LazyToUpper();
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Orders;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Binance utility methods
/// </summary>
public class BinanceUtil
{
/// <summary>
/// Convert binance status string value to native Lean OrderStatus
/// </summary>
/// <param name="status">The Binance order status value</param>
/// <returns>Lean order status</returns>
public static OrderStatus ConvertOrderStatus(string status)
{
switch (status.LazyToUpper())
{
case "NEW":
return OrderStatus.New;
case "PARTIALLY_FILLED":
return OrderStatus.PartiallyFilled;
case "FILLED":
return OrderStatus.Filled;
case "PENDING_CANCEL":
return OrderStatus.CancelPending;
case "CANCELED":
return OrderStatus.Canceled;
case "REJECTED":
case "EXPIRED":
return OrderStatus.Invalid;
default:
return Orders.OrderStatus.None;
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 System;
namespace QuantConnect.Brokerages.Binance
{
/// <summary>
/// Wrapper class for a Binance websocket connection
/// </summary>
public class BinanceWebSocketWrapper : WebSocketClientWrapper
{
/// <summary>
/// The unique Id for the connection
/// </summary>
public string ConnectionId { get; }
/// <summary>
/// The handler for the connection
/// </summary>
public IConnectionHandler ConnectionHandler { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BinanceWebSocketWrapper"/> class.
/// </summary>
public BinanceWebSocketWrapper(IConnectionHandler connectionHandler)
{
ConnectionId = Guid.NewGuid().ToString();
ConnectionHandler = connectionHandler;
}
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Orders;
using System;
using System.Globalization;
namespace QuantConnect.Brokerages.Binance.Messages
{
#pragma warning disable 1591
public class AccountInformation
{
public Balance[] Balances { get; set; }
public class Balance
{
public string Asset { get; set; }
public decimal Free { get; set; }
public decimal Locked { get; set; }
public decimal Amount => Free + Locked;
}
}
public class PriceTicker
{
public string Symbol { get; set; }
public decimal Price { get; set; }
}
public class Order
{
[JsonProperty("orderId")]
public string Id { get; set; }
public string Symbol { get; set; }
public decimal Price { get; set; }
public decimal StopPrice { get; set; }
[JsonProperty("origQty")]
public decimal OriginalAmount { get; set; }
[JsonProperty("executedQty")]
public decimal ExecutedAmount { get; set; }
public string Status { get; set; }
public string Type { get; set; }
public string Side { get; set; }
public decimal Quantity => string.Equals(Side, "buy", StringComparison.OrdinalIgnoreCase) ? OriginalAmount : -OriginalAmount;
}
public class OpenOrder : Order
{
public long Time { get; set; }
}
public class NewOrder : Order
{
[JsonProperty("transactTime")]
public long TransactionTime { get; set; }
}
public enum EventType
{
None,
OrderBook,
Trade,
Execution
}
public class ErrorMessage
{
[JsonProperty("code")]
public int Code { get; set; }
[JsonProperty("msg")]
public string Message { get; set; }
}
public class BestBidAskQuote
{
[JsonProperty("u")]
public long OrderBookUpdateId { get; set; }
[JsonProperty("s")]
public string Symbol { get; set; }
[JsonProperty("b")]
public decimal BestBidPrice { get; set; }
[JsonProperty("B")]
public decimal BestBidSize { get; set; }
[JsonProperty("a")]
public decimal BestAskPrice { get; set; }
[JsonProperty("A")]
public decimal BestAskSize { get; set; }
}
public class BaseMessage
{
public virtual EventType @Event { get; } = EventType.None;
[JsonProperty("e")]
public string EventName { get; set; }
[JsonProperty("E")]
public long Time { get; set; }
[JsonProperty("s")]
public string Symbol { get; set; }
}
public class Trade : BaseMessage
{
public override EventType @Event => EventType.Trade;
[JsonProperty("T")]
public new long Time { get; set; }
[JsonProperty("p")]
public decimal Price { get; private set; }
[JsonProperty("q")]
public decimal Quantity { get; private set; }
}
public class Execution : BaseMessage
{
public override EventType @Event => EventType.Execution;
[JsonProperty("i")]
public string OrderId { get; set; }
[JsonProperty("t")]
public string TradeId { get; set; }
[JsonProperty("I")]
public string Ignore { get; set; }
[JsonProperty("x")]
public string ExecutionType { get; private set; }
[JsonProperty("X")]
public string OrderStatus { get; private set; }
[JsonProperty("T")]
public long TransactionTime { get; set; }
[JsonProperty("L")]
public decimal LastExecutedPrice { get; set; }
[JsonProperty("l")]
public decimal LastExecutedQuantity { get; set; }
[JsonProperty("S")]
public string Side { get; set; }
[JsonProperty("n")]
public decimal Fee { get; set; }
[JsonProperty("N")]
public string FeeCurrency { get; set; }
public OrderDirection Direction => Side.Equals("BUY", StringComparison.OrdinalIgnoreCase) ? OrderDirection.Buy : OrderDirection.Sell;
}
public class Kline
{
public long OpenTime { get; }
public decimal Open { get; }
public decimal Close { get; }
public decimal High { get; }
public decimal Low { get; }
public decimal Volume { get; }
public Kline() { }
public Kline(long msts, decimal close)
{
OpenTime = msts;
Open = Close = High = Low = close;
Volume = 0;
}
public Kline(object[] entries)
{
OpenTime = Convert.ToInt64(entries[0], CultureInfo.InvariantCulture);
Open = ((string)entries[1]).ToDecimal();
High = ((string)entries[2]).ToDecimal();
Low = ((string)entries[3]).ToDecimal();
Close = ((string)entries[4]).ToDecimal();
Volume = ((string)entries[5]).ToDecimal();
}
}
#pragma warning restore 1591
}

View File

@@ -40,7 +40,6 @@ using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities.Option;
using Bar = QuantConnect.Data.Market.Bar;
using HistoryRequest = QuantConnect.Data.HistoryRequest;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Brokerages.InteractiveBrokers
{
@@ -131,6 +130,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
private readonly bool _enableDelayedStreamingData = Config.GetBool("ib-enable-delayed-streaming-data");
private volatile bool _isDisposeCalled;
/// <summary>
/// Returns true if we're currently connected to the broker
/// </summary>
@@ -905,8 +906,15 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// </summary>
public override void Dispose()
{
if (_isDisposeCalled)
{
return;
}
Log.Trace("InteractiveBrokersBrokerage.Dispose(): Disposing of IB resources.");
_isDisposeCalled = true;
if (_client != null)
{
Disconnect();
@@ -3023,7 +3031,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var result = _ibAutomater.GetLastStartResult();
CheckIbAutomaterError(result, false);
if (!result.HasError)
if (!result.HasError && !_isDisposeCalled)
{
// IBGateway was closed by the v978+ automatic logoff or it was closed manually (less likely)
Log.Trace("InteractiveBrokersBrokerage.OnIbAutomaterExited(): IBGateway close detected, restarting IBAutomater and reconnecting...");

View File

@@ -337,6 +337,14 @@
<Compile Include="Backtesting\BasicOptionAssignmentSimulation.cs" />
<Compile Include="Backtesting\IBacktestingMarketSimulation.cs" />
<Compile Include="BaseWebsocketsBrokerage.cs" />
<Compile Include="Binance\BinanceBrokerage.Utility.cs" />
<Compile Include="Binance\BinanceBrokerage.cs" />
<Compile Include="Binance\BinanceBrokerageFactory.cs" />
<Compile Include="Binance\BinanceOrderSubmitEventArgs.cs" />
<Compile Include="Binance\BinanceRestApiClient.cs" />
<Compile Include="Binance\BinanceSymbolMapper.cs" />
<Compile Include="Binance\BinanceWebSocketWrapper.cs" />
<Compile Include="Binance\Messages.cs" />
<Compile Include="Bitfinex\BitfinexBrokerage.Messaging.cs" />
<Compile Include="Bitfinex\BitfinexBrokerage.cs" />
<Compile Include="Bitfinex\BitfinexBrokerageFactory.cs" />
@@ -681,6 +689,7 @@
<Compile Include="Tradier\UserProfile.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Binance\BinanceBrokerage.Messaging.cs" />
<None Include="InteractiveBrokers\IB-symbol-map.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@@ -0,0 +1,86 @@
/*
* 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.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Binance specific properties
/// </summary>
public class BinanceBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets();
/// <summary>
/// Initializes a new instance of the <see cref="BinanceBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Cash"/></param>
public BinanceBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
{
if (accountType == AccountType.Margin)
{
throw new ArgumentException("The Binance brokerage does not currently support Margin trading.");
}
}
/// <summary>
/// Gets a new buying power model for the security, returning the default model with the security's configured leverage.
/// For cash accounts, leverage = 1 is used.
/// Margin trading is not currently supported
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
public override IBuyingPowerModel GetBuyingPowerModel(Security security)
{
return new CashBuyingPowerModel();
}
/// <summary>
/// Binance global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
// margin trading is not currently supported by Binance
return 1m;
}
/// <summary>
/// Provides Binance fee model
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BinanceFeeModel();
}
private static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets()
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = Market.Binance;
return map.ToReadOnlyDictionary();
}
}
}

View File

@@ -56,6 +56,11 @@ namespace QuantConnect.Brokerages
/// </summary>
Bitfinex,
/// <summary>
/// Transaction and submit/execution rules will use binance models
/// </summary>
Binance,
/// <summary>
/// Transaction and submit/execution rules will use gdax models
/// </summary>

View File

@@ -189,6 +189,9 @@ namespace QuantConnect.Brokerages
case BrokerageName.Bitfinex:
return new BitfinexBrokerageModel(accountType);
case BrokerageName.Binance:
return new BinanceBrokerageModel(accountType);
case BrokerageName.GDAX:
return new GDAXBrokerageModel(accountType);

View File

@@ -77,7 +77,8 @@ namespace QuantConnect
{"XRP", "XRP"},
{"XLM", "XLM"},
{"ETC", "ETC"},
{"ZRX", "ZRX"}
{"ZRX", "ZRX"},
{"USDT", "USDT"}
};
/// <summary>

View File

@@ -91,7 +91,8 @@ namespace QuantConnect.Data
var configs = _algorithm.SubscriptionManager
.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(symbol);
var isExtendedMarketHours = configs.IsExtendedMarketHours();
// hour resolution does no have extended market hours data
var isExtendedMarketHours = resolution != Resolution.Hour && configs.IsExtendedMarketHours();
var timeSpan = resolution.ToTimeSpan();
// make this a minimum of one second

View File

@@ -683,7 +683,7 @@ namespace QuantConnect.Data.Market
/// <summary>
/// Sets the tick Value based on ask and bid price
/// </summary>
private void SetValue()
public void SetValue()
{
Value = BidPrice + AskPrice;
if (BidPrice * AskPrice != 0)

View File

@@ -518,7 +518,7 @@ namespace QuantConnect
/// <summary>
/// Lazy string to upper implementation.
/// Will first verify the string is not already upper and avoid
/// the call to <see cref="string.ToUpper()"/> if possible.
/// the call to <see cref="string.ToUpperInvariant()"/> if possible.
/// </summary>
/// <param name="data">The string to upper</param>
/// <returns>The upper string</returns>
@@ -2064,6 +2064,16 @@ namespace QuantConnect
task.ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <summary>
/// Convert dictionary to query string
/// </summary>
/// <param name="pairs"></param>
/// <returns></returns>
public static string ToQueryString(this IDictionary<string, object> pairs)
{
return string.Join("&", pairs.Select(pair => $"{pair.Key}={pair.Value}"));
}
/// <summary>
/// Returns a new string in which specified ending in the current instance is removed.
/// </summary>
@@ -2125,43 +2135,40 @@ namespace QuantConnect
break;
case MarketDataType.Tick:
var securityType = data.Symbol.SecurityType;
var tick = data as Tick;
if (tick != null)
if (securityType != SecurityType.Equity &&
securityType != SecurityType.Option &&
securityType != SecurityType.Future)
{
if (securityType == SecurityType.Equity)
{
tick.Value = factor(tick.Value);
}
if (securityType == SecurityType.Option
|| securityType == SecurityType.Future)
{
if (tick.TickType == TickType.Trade)
{
tick.Value = factor(tick.Value);
}
else if (tick.TickType != TickType.OpenInterest)
{
tick.BidPrice = tick.BidPrice != 0 ? factor(tick.BidPrice) : 0;
tick.AskPrice = tick.AskPrice != 0 ? factor(tick.AskPrice) : 0;
if (tick.BidPrice != 0)
{
if (tick.AskPrice != 0)
{
tick.Value = (tick.BidPrice + tick.AskPrice) / 2m;
}
else
{
tick.Value = tick.BidPrice;
}
}
else
{
tick.Value = tick.AskPrice;
}
}
}
break;
}
var tick = data as Tick;
if (tick == null || tick.TickType == TickType.OpenInterest)
{
break;
}
if (tick.TickType == TickType.Trade)
{
tick.Value = factor(tick.Value);
break;
}
tick.BidPrice = tick.BidPrice != 0 ? factor(tick.BidPrice) : 0;
tick.AskPrice = tick.AskPrice != 0 ? factor(tick.AskPrice) : 0;
if (tick.BidPrice == 0)
{
tick.Value = tick.AskPrice;
break;
}
if (tick.AskPrice == 0)
{
tick.Value = tick.BidPrice;
break;
}
tick.Value = (tick.BidPrice + tick.AskPrice) / 2m;
break;
case MarketDataType.QuoteBar:
var quoteBar = data as QuoteBar;
@@ -2216,5 +2223,45 @@ namespace QuantConnect
{
return data?.Scale(p => p * scale);
}
/// <summary>
/// Returns a hex string of the byte array.
/// </summary>
/// <param name="source">the byte array to be represented as string</param>
/// <returns>A new string containing the items in the enumerable</returns>
public static string ToHexString(this byte[] source)
{
if (source == null || source.Length == 0)
{
throw new ArgumentException($"Source cannot be null or empty.");
}
var hex = new StringBuilder(source.Length * 2);
foreach (var b in source)
{
hex.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", b);
}
return hex.ToString();
}
/// <summary>
/// Gets the option exercise order direction resulting from the specified <paramref name="right"/> and
/// whether or not we wrote the option (<paramref name="isShort"/> is <code>true</code>) or bought to
/// option (<paramref name="isShort"/> is <code>false</code>)
/// </summary>
/// <param name="right">The option right</param>
/// <param name="isShort">True if we wrote the option, false if we purchased the option</param>
/// <returns>The order direction resulting from an exercised option</returns>
public static OrderDirection GetExerciseDirection(this OptionRight right, bool isShort)
{
switch (right)
{
case OptionRight.Call:
return isShort ? OrderDirection.Sell : OrderDirection.Buy;
default:
return isShort ? OrderDirection.Buy : OrderDirection.Sell;
}
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.Interfaces;
namespace QuantConnect.Orders
{
/// <summary>
/// Contains additional properties and settings for an order submitted to Binance brokerage
/// </summary>
public class BinanceOrderProperties : OrderProperties
{
/// <summary>
/// This flag will ensure the order executes only as a maker (no fee) order.
/// If part of the order results in taking liquidity rather than providing,
/// it will be rejected and no part of the order will execute.
/// Note: this flag is only applied to Limit orders.
/// </summary>
public bool PostOnly { get; set; }
/// <summary>
/// Returns a new instance clone of this object
/// </summary>
public override IOrderProperties Clone()
{
return (BinanceOrderProperties)MemberwiseClone();
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.Securities;
namespace QuantConnect.Orders.Fees
{
/// <summary>
/// Provides an implementation of <see cref="FeeModel"/> that models Binance order fees
/// </summary>
public class BinanceFeeModel : FeeModel
{
/// <summary>
/// Tier 1 maker fees
/// https://www.binance.com/en/fee/schedule
/// </summary>
public const decimal MakerTier1Fee = 0.001m;
/// <summary>
/// Tier 1 taker fees
/// https://www.binance.com/en/fee/schedule
/// </summary>
public const decimal TakerTier1Fee = 0.001m;
private readonly decimal _makerFee;
private readonly decimal _takerFee;
/// <summary>
/// Creates Binance fee model setting fees values
/// </summary>
/// <param name="mFee">Maker fee value</param>
/// <param name="tFee">Taker fee value</param>
public BinanceFeeModel(decimal mFee = MakerTier1Fee, decimal tFee = TakerTier1Fee)
{
_makerFee = mFee;
_takerFee = tFee;
}
/// <summary>
/// Get the fee for this order in quote currency
/// </summary>
/// <param name="parameters">A <see cref="OrderFeeParameters"/> object containing the security and order</param>
/// <returns>The cost of the order in quote currency</returns>
public override OrderFee GetOrderFee(OrderFeeParameters parameters)
{
var security = parameters.Security;
var order = parameters.Order;
decimal fee = _takerFee;
var props = order.Properties as BinanceOrderProperties;
if (order.Type == OrderType.Limit &&
(props?.PostOnly == true || !order.IsMarketable))
{
// limit order posted to the order book
fee = _makerFee;
}
// get order value in quote currency
var unitPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
if (order.Type == OrderType.Limit)
{
// limit order posted to the order book
unitPrice = ((LimitOrder)order).LimitPrice;
}
unitPrice *= security.SymbolProperties.ContractMultiplier;
// apply fee factor, currently we do not model 30-day volume, so we use the first tier
return new OrderFee(new CashAmount(
unitPrice * order.AbsoluteQuantity * fee,
security.QuoteCurrency.Symbol));
}
}
}

View File

@@ -32,52 +32,52 @@ namespace QuantConnect.Orders.OptionExercise
/// <param name="order">Order to update</param>
public IEnumerable<OrderEvent> OptionExercise(Option option, OptionExerciseOrder order)
{
var underlying = option.Underlying;
var isShort = order.Quantity < 0;
var utcTime = option.LocalTime.ConvertToUtc(option.Exchange.TimeZone);
var optionQuantity = order.Quantity;
var assignment = order.Quantity < 0;
var underlying = option.Underlying;
var exercisePrice = order.Price;
var fillQuantity = option.GetExerciseQuantity(order.Quantity);
var exerciseQuantity =
option.Symbol.ID.OptionRight == OptionRight.Call ? fillQuantity : -fillQuantity;
var exerciseDirection = assignment?
(option.Symbol.ID.OptionRight == OptionRight.Call ? OrderDirection.Sell : OrderDirection.Buy):
(option.Symbol.ID.OptionRight == OptionRight.Call ? OrderDirection.Buy : OrderDirection.Sell);
var inTheMoney = option.IsAutoExercised(underlying.Close);
var addUnderlyingEvent = new OrderEvent(order.Id,
underlying.Symbol,
utcTime,
OrderStatus.Filled,
exerciseDirection,
exercisePrice,
exerciseQuantity,
OrderFee.Zero,
"Option Exercise/Assignment");
// we're assigned only if we get exercised against, meaning we wrote the option and it was exercised by the buyer
var isAssignment = inTheMoney && isShort;
var optionRemoveEvent = new OrderEvent(order.Id,
option.Symbol,
utcTime,
OrderStatus.Filled,
assignment ? OrderDirection.Buy : OrderDirection.Sell,
0.0m,
-optionQuantity,
OrderFee.Zero,
"Adjusting(or removing) the exercised/assigned option");
if (optionRemoveEvent.FillQuantity > 0)
yield return new OrderEvent(
order.Id,
option.Symbol,
utcTime,
OrderStatus.Filled,
isShort ? OrderDirection.Buy : OrderDirection.Sell,
0.0m,
-order.Quantity,
OrderFee.Zero,
// note whether or not we expired OTM/ITM and whether we were assigned or exercised
inTheMoney ? isAssignment ? "Automatic Assignment" : "Automatic Exercise" : "OTM"
)
{
optionRemoveEvent.IsAssignment = true;
}
IsAssignment = isAssignment
};
if (option.ExerciseSettlement == SettlementType.PhysicalDelivery &&
option.IsAutoExercised(underlying.Close))
if (inTheMoney && option.ExerciseSettlement == SettlementType.PhysicalDelivery)
{
return new[] { optionRemoveEvent, addUnderlyingEvent };
}
var right = option.Symbol.ID.OptionRight;
return new[] { optionRemoveEvent };
// TODO : Why doesn't this method take into account the directionality of the quantity like other quantity properties/methods
var fillQuantity = option.GetExerciseQuantity(order.Quantity);
var exerciseQuantity = right == OptionRight.Call ? fillQuantity : -fillQuantity;
yield return new OrderEvent(
order.Id,
underlying.Symbol,
utcTime,
OrderStatus.Filled,
right.GetExerciseDirection(isShort),
order.Price,
exerciseQuantity,
OrderFee.Zero,
isAssignment ? "Option Assignment" : "Option Exercise"
);
}
}
}
}

View File

@@ -88,8 +88,32 @@ namespace QuantConnect.Orders
// populate common order properties
order.Id = jObject["Id"].Value<int>();
order.Status = (OrderStatus) jObject["Status"].Value<int>();
order.Time = jObject["Time"].Value<DateTime>();
var jsonStatus = jObject["Status"];
var jsonTime = jObject["Time"];
if (jsonStatus.Type == JTokenType.Integer)
{
order.Status = (OrderStatus) jsonStatus.Value<int>();
}
else if (jsonStatus.Type == JTokenType.Null)
{
order.Status = OrderStatus.Canceled;
}
else
{
// The `Status` tag can sometimes appear as a string of the enum value in the LiveResultPacket.
order.Status = (OrderStatus) Enum.Parse(typeof(OrderStatus), jsonStatus.Value<string>(), true);
}
if (jsonTime != null && jsonTime.Type != JTokenType.Null)
{
order.Time = jsonTime.Value<DateTime>();
}
else
{
// `Time` can potentially be null in some LiveResultPacket instances, but
// `CreatedTime` will always be there if `Time` is absent.
order.Time = jObject["CreatedTime"].Value<DateTime>();
}
var orderSubmissionData = jObject["OrderSubmissionData"];
if (orderSubmissionData != null && orderSubmissionData.Type != JTokenType.Null)
@@ -127,8 +151,16 @@ namespace QuantConnect.Orders
}
order.Quantity = jObject["Quantity"].Value<decimal>();
var orderPrice = jObject["Price"];
if (orderPrice != null && orderPrice.Type != JTokenType.Null)
{
order.Price = orderPrice.Value<decimal>();
}
else
{
order.Price = default(decimal);
}
order.Price = jObject["Price"].Value<decimal>();
var priceCurrency = jObject["PriceCurrency"];
if (priceCurrency != null && priceCurrency.Type != JTokenType.Null)
{

View File

@@ -255,6 +255,7 @@
<Compile Include="Benchmarks\SecurityBenchmark.cs" />
<Compile Include="Brokerages\AlpacaBrokerageModel.cs" />
<Compile Include="Brokerages\AlphaStreamsBrokerageModel.cs" />
<Compile Include="Brokerages\BinanceBrokerageModel.cs" />
<Compile Include="Brokerages\BrokerageFactoryAttribute.cs" />
<Compile Include="Brokerages\BrokerageName.cs" />
<Compile Include="Brokerages\DefaultBrokerageMessageHandler.cs" />
@@ -363,6 +364,8 @@
<Compile Include="Interfaces\ISubscriptionDataConfigService.cs" />
<Compile Include="Interfaces\ITimeKeeper.cs" />
<Compile Include="Interfaces\ObjectStoreErrorRaisedEventArgs.cs" />
<Compile Include="Orders\BinanceOrderProperties.cs" />
<Compile Include="Orders\Fees\BinanceFeeModel.cs" />
<Compile Include="IsolatorLimitResult.cs" />
<Compile Include="IsolatorLimitResultProvider.cs" />
<Compile Include="ITimeProvider.cs" />

View File

@@ -226,13 +226,28 @@ namespace QuantConnect.Securities
markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]);
}
var forexCurrencyPairs = GetAvailableSymbols(SecurityType.Forex, marketMap, markets);
var cfdCurrencyPairs = GetAvailableSymbols(SecurityType.Cfd, marketMap, markets);
var cryptoCurrencySymbols = GetAvailableSymbols(SecurityType.Crypto, marketMap, markets);
var forexEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Forex, marketMap, markets);
var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets);
var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets);
var potentials = forexCurrencyPairs
.Concat(cfdCurrencyPairs)
.Concat(cryptoCurrencySymbols);
var potentialEntries = forexEntries
.Concat(cfdEntries)
.Concat(cryptoEntries)
.ToList();
if (!potentialEntries.Any(x =>
Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) ||
Symbol == x.Value.QuoteCurrency))
{
// currency not found in any tradeable pair
Log.Error($"No tradeable pair was found for currency {Symbol}, conversion rate to account currency ({accountCurrency}) will be set to zero.");
ConversionRateSecurity = null;
ConversionRate = 0m;
return null;
}
var potentials = potentialEntries
.Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, x.Key.SecurityType, x.Key.Market));
var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(defaultResolution).Min();
@@ -285,7 +300,7 @@ namespace QuantConnect.Securities
return Invariant($"{Symbol}: {CurrencySymbol}{Amount,15:0.00} @ {rate,10:0.00####} = ${Math.Round(ValueInAccountCurrency, 2)}");
}
private static IEnumerable<Symbol> GetAvailableSymbols(
private static IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> GetAvailableSymbolPropertiesDatabaseEntries(
SecurityType securityType,
IReadOnlyDictionary<SecurityType, string> marketMap,
IReadOnlyDictionary<SecurityType, string> markets
@@ -303,10 +318,9 @@ namespace QuantConnect.Securities
marketJoin.Add(market);
}
}
return marketJoin.SelectMany(market => SymbolPropertiesDatabase.FromDataFolder()
.GetSymbolPropertiesList(market, securityType)
.Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, securityType, market)));
.GetSymbolPropertiesList(market, securityType));
}
private void OnUpdate()

View File

@@ -16,7 +16,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using static QuantConnect.StringExtensions;
namespace QuantConnect.Securities.Future
@@ -36,8 +35,7 @@ namespace QuantConnect.Securities.Future
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDays(DateTime time, int n, bool useEquityHolidays = true, IEnumerable<DateTime> holidayList = null)
{
var holidays = holidayList?.ToList() ?? new List<DateTime>();
var holidays = GetDatesFromDateTimeList(holidayList);
if (useEquityHolidays)
{
holidays.AddRange(USHoliday.Dates);
@@ -45,12 +43,12 @@ namespace QuantConnect.Securities.Future
if (n < 0)
{
var businessDays = (-1) * n;
var businessDays = -n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(-totalDays);
if (!holidays.Contains(previousDay) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
@@ -67,7 +65,7 @@ namespace QuantConnect.Securities.Future
do
{
var previousDay = time.AddDays(totalDays);
if (!holidays.Contains(previousDay) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
@@ -90,7 +88,7 @@ namespace QuantConnect.Securities.Future
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var lastDayOfMonth = new DateTime(time.Year, time.Month, daysInMonth);
var holidays = (holidayList ?? new List<DateTime>()).ToList();
var holidays = GetDatesFromDateTimeList(holidayList);
if(n > daysInMonth)
{
@@ -119,9 +117,9 @@ namespace QuantConnect.Securities.Future
/// </summary>
/// <param name="time">Month to calculate business day for</param>
/// <param name="nthBusinessDay">n^th business day to get</param>
/// <param name="additionalHolidays">Additional user provided holidays to not count as business days</param>
/// <param name="holidayList">Additional user provided holidays to not count as business days</param>
/// <returns>Nth business day of the month</returns>
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> additionalHolidays = null)
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList = null)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
if (nthBusinessDay > daysInMonth)
@@ -137,30 +135,30 @@ namespace QuantConnect.Securities.Future
));
}
time = new DateTime(time.Year, time.Month, 1);
var calculatedTime = new DateTime(time.Year, time.Month, 1);
var daysCounted = time.IsCommonBusinessDay() ? 1 : 0;
var daysCounted = calculatedTime.IsCommonBusinessDay() ? 1 : 0;
var i = 0;
var holidays = additionalHolidays ?? new List<DateTime>();
var holidays = GetDatesFromDateTimeList(holidayList);
// Check for holiday up here in case we want the first business day and it is a holiday so that we don't skip over it.
// We also want to make sure that we don't stop on a weekend.
while (daysCounted < nthBusinessDay || holidays.Contains(time) || USHoliday.Dates.Contains(time) || !time.IsCommonBusinessDay())
while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || USHoliday.Dates.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
{
// The asset continues trading on days contained within `USHoliday.Dates`, but
// the last trade date is affected by those holidays. We check for
// both MHDB entries and holidays to get accurate business days
if (holidays.Contains(time) || USHoliday.Dates.Contains(time))
if (holidays.Contains(calculatedTime) || USHoliday.Dates.Contains(calculatedTime))
{
// Catches edge case where first day is on a friday
if (i == 0 && time.DayOfWeek == DayOfWeek.Friday)
if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
{
daysCounted = 0;
}
time = time.AddDays(1);
calculatedTime = calculatedTime.AddDays(1);
if (i != 0 && time.IsCommonBusinessDay())
if (i != 0 && calculatedTime.IsCommonBusinessDay())
{
daysCounted++;
}
@@ -168,16 +166,16 @@ namespace QuantConnect.Securities.Future
continue;
}
time = time.AddDays(1);
calculatedTime = calculatedTime.AddDays(1);
if (!holidays.Contains(time) && NotHoliday(time))
if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime))
{
daysCounted++;
}
i++;
}
return time;
return calculatedTime;
}
/// <summary>
@@ -214,18 +212,18 @@ namespace QuantConnect.Securities.Future
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="n">The order of the Weekday in the period</param>
/// <param name="dayofWeek">The day of the week</param>
/// <param name="dayOfWeek">The day of the week</param>
/// <returns>Nth Weekday of given month</returns>
public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayofWeek)
public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayOfWeek)
{
if (n < 1 || n > 5)
{
throw new ArgumentOutOfRangeException($"'n' lower than 1 or greater than 5");
throw new ArgumentOutOfRangeException(nameof(n), "'n' lower than 1 or greater than 5");
}
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth)
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayofWeek
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).ElementAt(n - 1);
}
@@ -234,14 +232,14 @@ namespace QuantConnect.Securities.Future
/// Method to retrieve the last weekday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="dayofWeek">the last weekday to be found</param>
/// <param name="dayOfWeek">the last weekday to be found</param>
/// <returns>Last day of the we</returns>
public static DateTime LastWeekday(DateTime time, DayOfWeek dayofWeek)
public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth).Reverse()
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayofWeek
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).First();
}
@@ -249,7 +247,6 @@ namespace QuantConnect.Securities.Future
/// Method to retrieve the last weekday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="dayofWeek">the last weekday to be found</param>
/// <returns>Last day of the we</returns>
public static DateTime LastThursday(DateTime time) => LastWeekday(time, DayOfWeek.Thursday);
@@ -260,7 +257,7 @@ namespace QuantConnect.Securities.Future
/// <returns>True if the time is not a holidays, otherwise returns false</returns>
public static bool NotHoliday(DateTime time)
{
return time.IsCommonBusinessDay() && !USHoliday.Dates.Contains(time);
return time.IsCommonBusinessDay() && !USHoliday.Dates.Contains(time.Date);
}
/// <summary>
@@ -319,7 +316,7 @@ namespace QuantConnect.Securities.Future
// The USDA price announcements are erratic in their publication date. You can view the calendar the USDA announces prices here: https://www.ers.usda.gov/calendar/
// More specifically, the report you should be looking for has the name "National Dairy Products Sales Report".
// To get the report dates found in FutuesExpiryFunctions.DairyReportDates, visit this website: https://mpr.datamart.ams.usda.gov/menu.do?path=Products\Dairy\All%20Dairy\(DY_CL102)%20National%20Dairy%20Products%20Prices%20-%20Monthly
// To get the report dates found in FuturesExpiryFunctions.DairyReportDates, visit this website: https://mpr.datamart.ams.usda.gov/menu.do?path=Products\Dairy\All%20Dairy\(DY_CL102)%20National%20Dairy%20Products%20Prices%20-%20Monthly
return publicationDate.Add(lastTradeTs);
}
@@ -335,6 +332,14 @@ namespace QuantConnect.Securities.Future
return ExpiriesPriorMonth.TryGetValue(underlying, out value) ? value : 0;
}
/// <summary>
/// Extracts Date portion of list of DateTimes for
/// </summary>
/// <param name="dateTimeList">List of DateTimes (with optional time portion) to filter</param>
/// <returns>List of DateTimes with default time (00:00:00)</returns>
public static List<DateTime> GetDatesFromDateTimeList(IEnumerable<DateTime> dateTimeList)
=> dateTimeList?.Select(x => x.Date).ToList() ?? new List<DateTime>();
private static readonly Dictionary<string, int> ExpiriesPriorMonth = new Dictionary<string, int>
{
{ Futures.Energies.ArgusLLSvsWTIArgusTradeMonth, 1 },

View File

@@ -128,7 +128,8 @@ namespace QuantConnect.Securities
.ToList();
var barCount = _window.Size + 1;
var extendedMarketHours = configurations.IsExtendedMarketHours();
// hour resolution does no have extended market hours data
var extendedMarketHours = _periodSpan != Time.OneHour && configurations.IsExtendedMarketHours();
var configuration = configurations.First();
var localStartTime = Time.GetStartTimeForTradeBars(

View File

@@ -120,7 +120,8 @@ namespace QuantConnect.Securities
var configuration = configurations.First();
var barCount = _window.Size + 1;
var extendedMarketHours = configurations.IsExtendedMarketHours();
// hour resolution does no have extended market hours data
var extendedMarketHours = _periodSpan != Time.OneHour && configurations.IsExtendedMarketHours();
var localStartTime = Time.GetStartTimeForTradeBars(
security.Exchange.Hours,
utcTime.ConvertFromUtc(security.Exchange.TimeZone),

View File

@@ -35531,6 +35531,80 @@
}
],
"holidays": []
},
"Crypto-binance-[*]": {
"dataTimeZone": "UTC",
"exchangeTimeZone": "UTC",
"sunday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"monday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
},
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"tuesday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
},
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"wednesday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
},
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"thursday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
},
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"friday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"saturday": [
{
"start": "00:00:00",
"end": "1.00:00:00",
"state": "market"
}
],
"holidays": []
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1046,22 +1046,10 @@ namespace QuantConnect.Lean.Engine
if (security.Type == SecurityType.Option)
{
var option = (Option)security;
if (security.Holdings.Quantity > 0)
{
request = new SubmitOrderRequest(OrderType.OptionExercise, security.Type, security.Symbol,
security.Holdings.Quantity, 0, 0, algorithm.UtcTime, "Automatic option exercise on expiration");
}
else
{
var message = option.GetPayOff(option.Underlying.Price) > 0
? "Automatic option assignment on expiration"
: "Option expiration";
request = new SubmitOrderRequest(OrderType.OptionExercise, security.Type, security.Symbol,
security.Holdings.Quantity, 0, 0, algorithm.UtcTime, message);
}
// notify tx handler of the expiration, this will be handled via the configured exercise model to see if
// we auto-exercise or get assigned at expiration. don't be presumptuous from here and guess if exercised/assigned
request = new SubmitOrderRequest(OrderType.OptionExercise, security.Type, security.Symbol,
security.Holdings.Quantity, 0, 0, algorithm.UtcTime, "Option Expired");
}
else
{

View File

@@ -15,7 +15,6 @@
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -27,8 +26,6 @@ using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Statistics;
using QuantConnect.Util;
using System.IO;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.Alphas;
namespace QuantConnect.Lean.Engine.Results
@@ -38,9 +35,6 @@ namespace QuantConnect.Lean.Engine.Results
/// </summary>
public class BacktestingResultHandler : BaseResultsHandler, IResultHandler
{
// used for resetting out/error upon completion
private static readonly TextWriter StandardOut = Console.Out;
private static readonly TextWriter StandardError = Console.Error;
private const double Samples = 4000;
private const double MinimumSamplePeriod = 4;
@@ -136,10 +130,6 @@ namespace QuantConnect.Lean.Engine.Results
}
Log.Trace("BacktestingResultHandler.Run(): Ending Thread...");
// reset standard out/error
Console.SetOut(StandardOut);
Console.SetError(StandardError);
} // End Run();
/// <summary>
@@ -639,7 +629,7 @@ namespace QuantConnect.Lean.Engine.Results
/// <summary>
/// Terminate the result thread and apply any required exit procedures like sending final results.
/// </summary>
public virtual void Exit()
public override void Exit()
{
// Only process the logs once
if (!ExitTriggered)
@@ -661,6 +651,8 @@ namespace QuantConnect.Lean.Engine.Results
StopUpdateRunner();
SendFinalResult();
base.Exit();
}
}

View File

@@ -38,6 +38,10 @@ namespace QuantConnect.Lean.Engine.Results
/// </summary>
public abstract class BaseResultsHandler
{
// used for resetting out/error upon completion
private static readonly TextWriter StandardOut = Console.Out;
private static readonly TextWriter StandardError = Console.Error;
/// <summary>
/// The main loop update interval
/// </summary>
@@ -212,6 +216,16 @@ namespace QuantConnect.Lean.Engine.Results
{
}
/// <summary>
/// Terminate the result thread and apply any required exit procedures like sending final results
/// </summary>
public virtual void Exit()
{
// reset standard out/error
Console.SetOut(StandardOut);
Console.SetError(StandardError);
}
/// <summary>
/// Gets the current Server statistics
/// </summary>

View File

@@ -944,7 +944,7 @@ namespace QuantConnect.Lean.Engine.Results
/// <summary>
/// Terminate the result thread and apply any required exit procedures like sending final results
/// </summary>
public void Exit()
public override void Exit()
{
if (!ExitTriggered)
{
@@ -968,6 +968,8 @@ namespace QuantConnect.Lean.Engine.Results
StopUpdateRunner();
SendFinalResult();
base.Exit();
}
}

View File

@@ -115,6 +115,10 @@
"bitfinex-api-secret": "",
"bitfinex-api-key": "",
// binance configuration
"binance-api-secret": "",
"binance-api-key": "",
// Required to access data from Quandl
// To get your access token go to https://www.quandl.com/account/api
"quandl-auth-token": "",
@@ -286,6 +290,20 @@
"history-provider": "BrokerageHistoryProvider"
},
"live-binance": {
"live-mode": true,
// real brokerage implementations require the BrokerageTransactionHandler
"live-mode-brokerage": "BinanceBrokerage",
"data-queue-handler": "BinanceBrokerage",
"setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
"result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
"data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
"history-provider": "BrokerageHistoryProvider"
},
// defines the 'live-alpaca' environment
"live-alpaca": {
"live-mode": true,

View File

@@ -91,7 +91,7 @@
<HintPath>..\packages\MathNet.Numerics.3.19.0\lib\net40\MathNet.Numerics.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath>
<HintPath>..\packages\NLog.4.4.13\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />

View File

@@ -7,7 +7,7 @@
<package id="Microsoft.CodeQuality.Analyzers" version="2.9.3" targetFramework="net452" />
<package id="Microsoft.NetCore.Analyzers" version="2.9.3" targetFramework="net452" />
<package id="Microsoft.NetFramework.Analyzers" version="2.9.3" targetFramework="net452" />
<package id="NLog" version="4.4.11" targetFramework="net452" />
<package id="NLog.Config" version="4.4.11" targetFramework="net452" />
<package id="NLog.Schema" version="4.4.11" targetFramework="net452" />
<package id="NLog" version="4.4.13" targetFramework="net452" />
<package id="NLog.Config" version="4.4.13" targetFramework="net452" />
<package id="NLog.Schema" version="4.4.13" targetFramework="net452" />
</packages>

View File

@@ -12,7 +12,6 @@
# limitations under the License.
from quantconnect.api import Api
from quantconnect.LeanReportCreator import LeanReportCreator
from time import sleep
import unittest
import os
@@ -165,10 +164,6 @@ class TestApi(unittest.TestCase):
attempts += 1
return attempts, result
def test_LeanReportCreator(self):
lrc = LeanReportCreator(f'--backtest=../json/sample.json --output=../outputs_test/Report.html --user=user_data.json')
lrc.create()
def get_content(file):
with open('../../Algorithm.Python/' + file, 'r') as f:
content = f.read()

View File

@@ -39,7 +39,8 @@ namespace QuantConnect.Report
{CrisisEvent.EurozoneOctober2014, new Crisis("European Debt Crisis Oct. 2014", new DateTime(2014, 10, 1), new DateTime(2014, 10, 29))},
{CrisisEvent.MarketSellOff2015, new Crisis("Market Sell-Off 2015", new DateTime(2015, 8, 10), new DateTime(2015, 10, 10))},
{CrisisEvent.Recovery, new Crisis("Recovery", new DateTime(2010, 1, 1), new DateTime(2012, 10, 1))},
{CrisisEvent.NewNormal, new Crisis("New Normal", new DateTime(2014, 1, 1), new DateTime(2019, 1, 1))}
{CrisisEvent.NewNormal, new Crisis("New Normal", new DateTime(2014, 1, 1), new DateTime(2019, 1, 1))},
{CrisisEvent.COVID19, new Crisis("COVID-19 Pandemic", new DateTime(2020, 2, 10), new DateTime(2020, 9, 20))},
};
/// <summary>
@@ -97,12 +98,7 @@ namespace QuantConnect.Report
/// <returns></returns>
public string ToString(DateTime start, DateTime end)
{
if (Name.ToLowerInvariant().Contains("crisis"))
{
return $"{Name} {start:MMM yyyy} - {end:MMM yyyy}";
}
return $"Crisis {Name} {start:MMM yyyy} - {end:MMM yyyy}";
return $"{Name} {start:MMM yyyy} - {end:MMM yyyy}";
}
}
}

View File

@@ -79,6 +79,11 @@ namespace QuantConnect.Report
/// <summary>
/// 2014 - 2019 market performance
/// </summary>
NewNormal
NewNormal,
/// <summary>
/// COVID-19 pandemic market crash
/// </summary>
COVID19
}
}

View File

@@ -46,6 +46,10 @@ namespace QuantConnect.Report
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.ReadFrom(reader);
if (token.Type == JTokenType.Null)
{
return null;
}
foreach (JProperty property in token["Charts"].Children())
{

View File

@@ -30,6 +30,7 @@ using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Lean.Engine.HistoricalData;
namespace QuantConnect.Report
{
@@ -39,7 +40,7 @@ namespace QuantConnect.Report
/// the holdings and other miscellaneous metrics at a point in time by reprocessing the orders
/// as they were filled.
/// </summary>
public class PortfolioLooper
public class PortfolioLooper : IDisposable
{
/// <summary>
/// Default resolution to read. This will affect the granularity of the results generated for FX and Crypto
@@ -48,6 +49,8 @@ namespace QuantConnect.Report
private SecurityService _securityService;
private DataManager _dataManager;
private IResultHandler _resultHandler;
private IDataCacheProvider _cacheProvider;
private IEnumerable<Slice> _conversionSlices = new List<Slice>();
/// <summary>
@@ -67,11 +70,11 @@ namespace QuantConnect.Report
// Initialize the providers that the HistoryProvider requires
var factorFileProvider = Composer.Instance.GetExportedValueByTypeName<IFactorFileProvider>("LocalDiskFactorFileProvider");
var mapFileProvider = Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>("LocalDiskMapFileProvider");
var dataCacheProvider = new ZipDataCacheProvider(new DefaultDataProvider(), false);
var historyProvider = Composer.Instance.GetExportedValueByTypeName<IHistoryProvider>("SubscriptionDataReaderHistoryProvider");
_cacheProvider = new ZipDataCacheProvider(new DefaultDataProvider(), false);
var historyProvider = new SubscriptionDataReaderHistoryProvider();
var dataPermissionManager = new DataPermissionManager();
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, dataCacheProvider, mapFileProvider, factorFileProvider, (_) => { }, false, dataPermissionManager));
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, _cacheProvider, mapFileProvider, factorFileProvider, (_) => { }, false, dataPermissionManager));
Algorithm = new PortfolioLooperAlgorithm((decimal)startingCash, orders);
Algorithm.SetHistoryProvider(historyProvider);
@@ -108,7 +111,7 @@ namespace QuantConnect.Report
new SecurityCacheProvider(Algorithm.Portfolio));
var transactions = new BacktestingTransactionHandler();
var results = new BacktestingResultHandler();
_resultHandler = new BacktestingResultHandler();
// Initialize security services and other properties so that we
// don't get null reference exceptions during our re-calculation
@@ -123,10 +126,10 @@ namespace QuantConnect.Report
Algorithm.PostInitialize();
// More initialization, this time with Algorithm and other misc. classes
results.Initialize(job, new Messaging.Messaging(), new Api.Api(), transactions);
results.SetAlgorithm(Algorithm, Algorithm.Portfolio.TotalPortfolioValue);
transactions.Initialize(Algorithm, new BacktestingBrokerage(Algorithm), results);
feed.Initialize(Algorithm, job, results, null, null, null, _dataManager, null, null);
_resultHandler.Initialize(job, new Messaging.Messaging(), new Api.Api(), transactions);
_resultHandler.SetAlgorithm(Algorithm, Algorithm.Portfolio.TotalPortfolioValue);
transactions.Initialize(Algorithm, new BacktestingBrokerage(Algorithm), _resultHandler);
feed.Initialize(Algorithm, job, _resultHandler, null, null, null, _dataManager, null, null);
// Begin setting up the currency conversion feed if needed
var coreSecurities = Algorithm.Securities.Values.ToList();
@@ -146,46 +149,13 @@ namespace QuantConnect.Report
}
/// <summary>
/// Utility method to get historical data for a list of securities
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="securities">List of securities you want historical data for</param>
/// <param name="start">Start date of the history request</param>
/// <param name="end">End date of the history request</param>
/// <param name="resolution">Resolution of the history request</param>
/// <returns>Enumerable of slices</returns>
public static IEnumerable<Slice> GetHistory(List<Security> securities, DateTime start, DateTime end, Resolution resolution)
public void Dispose()
{
var looper = new PortfolioLooper(0, new List<Order>(), resolution);
looper.Algorithm.SetStartDate(start);
looper.Algorithm.SetEndDate(end);
return GetHistory(looper.Algorithm, securities, resolution);
}
/// <summary>
/// Gets the history for the given symbols from the <paramref name="start"/> to the <paramref name="end"/>
/// </summary>
/// <param name="symbols">Symbols to request history for</param>
/// <param name="start">Start date of history request</param>
/// <param name="end">End date of history request</param>
/// <param name="resolution">Resolution of history request</param>
/// <returns>Enumerable of slices</returns>
public static IEnumerable<Slice> GetHistory(List<Symbol> symbols, DateTime start, DateTime end, Resolution resolution)
{
// Handles the conversion of Symbol to Security for us.
var looper = new PortfolioLooper(0, new List<Order>(), resolution);
var securities = new List<Security>();
looper.Algorithm.SetStartDate(start);
looper.Algorithm.SetEndDate(end);
foreach (var symbol in symbols)
{
var configs = looper.Algorithm.SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, false, false);
securities.Add(looper.Algorithm.Securities.CreateSecurity(symbol, configs));
}
return GetHistory(looper.Algorithm, securities, resolution);
_dataManager.RemoveAllSubscriptions();
_cacheProvider.DisposeSafely();
_resultHandler.Exit();
}
/// <summary>
@@ -272,6 +242,7 @@ namespace QuantConnect.Report
previousOrderId = order.Id;
}
PortfolioLooper looper = null;
PointInTimePortfolio prev = null;
foreach (var deploymentOrders in portfolioDeployments)
{
@@ -296,7 +267,7 @@ namespace QuantConnect.Report
}
// For every deployment, we want to start fresh.
var looper = new PortfolioLooper(deployment.LastValue(), deploymentOrders);
looper = new PortfolioLooper(deployment.LastValue(), deploymentOrders);
foreach (var portfolio in looper.ProcessOrders(deploymentOrders))
{
@@ -309,6 +280,8 @@ namespace QuantConnect.Report
{
yield return new PointInTimePortfolio(prev, equityCurve.LastKey());
}
looper.DisposeSafely();
}
/// <summary>
@@ -324,13 +297,24 @@ namespace QuantConnect.Report
Algorithm.SetDateTime(order.Time);
var orderSecurity = Algorithm.Securities[order.Symbol];
if (order.LastFillTime == null)
DateTime lastFillTime;
if ((order.Type == OrderType.MarketOnOpen || order.Type == OrderType.MarketOnClose) &&
(order.Status == OrderStatus.Filled || order.Status == OrderStatus.PartiallyFilled) && order.LastFillTime == null)
{
lastFillTime = order.Time;
}
else if (order.LastFillTime == null)
{
Log.Trace($"Order with ID: {order.Id} has been skipped because of null LastFillTime");
continue;
}
else
{
lastFillTime = order.LastFillTime.Value;
}
var tick = new Tick { Quantity = order.Quantity, AskPrice = order.Price, BidPrice = order.Price, Value = order.Price, EndTime = order.LastFillTime.Value };
var tick = new Tick { Quantity = order.Quantity, AskPrice = order.Price, BidPrice = order.Price, Value = order.Price, EndTime = lastFillTime };
// Set the market price of the security
orderSecurity.SetMarketPrice(tick);

View File

@@ -53,9 +53,10 @@ namespace QuantConnect.Report
Converters = new List<JsonConverter> { new NullResultValueTypeJsonConverter<BacktestResult>() },
FloatParseHandling = FloatParseHandling.Decimal
};
var backtest = JsonConvert.DeserializeObject<BacktestResult>(File.ReadAllText(backtestDataFile), backtestSettings);
var backtest = JsonConvert.DeserializeObject<BacktestResult>(File.ReadAllText(backtestDataFile), backtestSettings);
LiveResult live = null;
if (liveDataFile != string.Empty)
{
var settings = new JsonSerializerSettings

View File

@@ -125,6 +125,7 @@
<Compile Include="NullResultValueTypeJsonConverter.cs" />
<Compile Include="PortfolioLooper\MockDataFeed.cs" />
<Compile Include="PortfolioLooper\PortfolioLooperAlgorithm.cs" />
<Compile Include="ReportElements\DaysLiveReportElement.cs" />
<Compile Include="ResultsUtil.cs" />
<Compile Include="Metrics.cs" />
<Compile Include="Rolling.cs" />

View File

@@ -51,16 +51,17 @@ namespace QuantConnect.Report
Log.Trace($"QuantConnect.Report.Report(): Processing live orders");
var livePortfolioInTime = PortfolioLooper.FromOrders(liveCurve, liveOrders, liveSeries: true).ToList();
_elements = new List<ReportElement>
_elements = new List<IReportElement>
{
//Basics
new TextReportElement("strategy name", ReportKey.StrategyName, name),
new TextReportElement("description", ReportKey.StrategyDescription, description),
new TextReportElement("version", ReportKey.StrategyVersion, version),
new TextReportElement("stylesheet", ReportKey.Stylesheet, File.ReadAllText("css/report.css")),
new TextReportElement("live marker key", ReportKey.LiveMarker, live == null ? string.Empty : "Live "),
//KPI's Backtest:
new EstimatedCapacityReportElement("estimated capacity kpi", ReportKey.EstimatedCapacity, backtest, live),
new DaysLiveReportElement("days live kpi", ReportKey.DaysLive, live),
new CAGRReportElement("cagr kpi", ReportKey.CAGR, backtest, live),
new TurnoverReportElement("turnover kpi", ReportKey.Turnover, backtest, live),
new MaxDrawdownReportElement("max drawdown kpi", ReportKey.MaxDrawdown, backtest, live),
@@ -88,6 +89,7 @@ namespace QuantConnect.Report
new CrisisReportElement("crisis page", ReportKey.CrisisPageStyle, backtest, live),
new CrisisReportElement("crisis plots", ReportKey.CrisisPlots, backtest, live)
};
}
/// <summary>
@@ -108,4 +110,4 @@ namespace QuantConnect.Report
return html;
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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 System;
using System.Linq;
using QuantConnect.Packets;
namespace QuantConnect.Report.ReportElements
{
internal class DaysLiveReportElement : ReportElement
{
private LiveResult _live;
/// <summary>
/// Create a new metric describing the number of days an algorithm has been live.
/// </summary>
/// <param name="name">Name of the widget</param>
/// <param name="key">Location of injection</param>
/// <param name="backtest">Backtest result object</param>
/// <param name="live">Live result object</param>
public DaysLiveReportElement(string name, string key, LiveResult live)
{
_live = live;
Name = name;
Key = key;
}
/// <summary>
/// The generated output string to be injected
/// </summary>
public override string Render()
{
if (_live == null)
{
return "-";
}
var equityPoints = ResultsUtil.EquityPoints(_live);
return (DateTime.UtcNow - equityPoints.First().Key).Days.ToStringInvariant();
}
}
}

View File

@@ -13,6 +13,9 @@
* limitations under the License.
*/
using System;
using System.Linq;
using Deedle;
using QuantConnect.Packets;
namespace QuantConnect.Report.ReportElements
@@ -42,7 +45,24 @@ namespace QuantConnect.Report.ReportElements
/// </summary>
public override string Render()
{
return _backtest?.TotalPerformance?.PortfolioStatistics?.Drawdown.ToString("P1") ?? "-";
if (_live == null)
{
return _backtest?.TotalPerformance?.PortfolioStatistics?.Drawdown.ToString("P1") ?? "-";
}
var backtestEquityPoints = new Series<DateTime, double>(ResultsUtil.EquityPoints(_backtest));
var liveEquityPoints = new Series<DateTime, double>(ResultsUtil.EquityPoints(_live));
var backtestDrawdownGroups = new DrawdownCollection(backtestEquityPoints, 1);
var liveDrawdownGroups = new DrawdownCollection(liveEquityPoints, 1);
var separateResultsMaxDrawdown = backtestDrawdownGroups.Drawdowns
.Concat(liveDrawdownGroups.Drawdowns)
.Select(x => x.PeakToTrough)
.OrderByDescending(x => x)
.FirstOrDefault();
return $"{separateResultsMaxDrawdown:P1}";
}
}
}
}

View File

@@ -24,13 +24,14 @@ namespace QuantConnect.Report
public const string StrategyName = @"{{$TEXT-STRATEGY-NAME}}";
public const string StrategyDescription = @"{{$TEXT-STRATEGY-DESCRIPTION}}";
public const string StrategyVersion = @"{{$TEXT-STRATEGY-VERSION}}";
public const string LiveMarker = @"{{$LIVE-MARKER}}";
public const string CAGR = @"{{$KPI-CAGR}}";
public const string Turnover = @"{{$KPI-TURNOVER}}";
public const string MaxDrawdown = @"{{$KPI-DRAWDOWN}}";
public const string KellyEstimate = @"{{$KPI-KELLY-ESTIMATE}}";
public const string SharpeRatio = @"{{$KPI-SHARPE}}";
public const string EstimatedCapacity = @"{{$KPI-CAPACITY}}";
public const string DaysLive = @"{{$KPI-DAYS-LIVE}}";
public const string InformationRatio = @"{{$KPI-INFORMATION-RATIO}}";
public const string TradesPerDay = @"{{$KPI-TRADES-PER-DAY}}";
public const string Markets = @"{{$KPI-MARKETS}}";

View File

@@ -2,7 +2,7 @@
<table class="table compact">
<thead>
<tr>
<th>{{$TEXT-CRISIS-TITLE}}</th>
<th style="display: block; height: 75px;">{{$TEXT-CRISIS-TITLE}}</th>
</tr>
</thead>
<tbody>
@@ -13,4 +13,4 @@
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -61,7 +61,7 @@
</thead>
<tbody>
<tr>
<td class="title">Estimated Capacity</td><td>{{$KPI-CAPACITY}}</td><td class="kpi-live">{{$KPI-LIVE-CAPACITY}}</td>
<td class="title">Days Live</td><td>{{$KPI-DAYS-LIVE}}</td><td class="kpi-live">{{$KPI-DAYS-LIVE}}</td>
<td class="title">CAGR</td><td>{{$KPI-CAGR}}</td><td class="kpi-live">{{$KPI-LIVE-CAGR}}</td>
</tr>
<tr>
@@ -70,7 +70,7 @@
</tr>
<tr>
<td class="title">Kelly Estimate</td><td>{{$KPI-KELLY-ESTIMATE}}</td><td class="kpi-live">{{$KPI-LIVE-KELLY-ESTIMATE}}</td>
<td class="title">Sharpe Ratio</td><td>{{$KPI-SHARPE}}</td><td class="kpi-live">{{$KPI-LIVE-SHARPE}}</td>
<td class="title">{{$LIVE-MARKER}}Sharpe Ratio</td><td>{{$KPI-SHARPE}}</td><td class="kpi-live">{{$KPI-LIVE-SHARPE}}</td>
</tr>
<tr>
<td class="title">Probabilistic SR</td><td>{{$KPI-PSR}}</td><td class="kpi-live">{{$KPI-LIVE-PSR}}</td>
@@ -302,4 +302,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@@ -0,0 +1,43 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Logging;
using QuantConnect.ToolBox.BinanceDownloader;
using System;
using System.Linq;
namespace QuantConnect.Tests.Brokerages.Binance
{
[TestFixture]
public class BinanceBrokerageExchangeInfoTests
{
[Test]
public void GetsExchangeInfo()
{
var downloader = new BinanceExchangeInfoDownloader();
var tickers = downloader.Get().ToList();
Assert.IsTrue(tickers.Any());
foreach (var t in tickers)
{
Assert.IsTrue(t.StartsWith(Market.Binance, StringComparison.OrdinalIgnoreCase));
}
Log.Trace("Tickers retrieved: " + tickers.Count);
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Brokerages.Binance;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.HistoricalData;
using QuantConnect.Logging;
using QuantConnect.Securities;
using System;
using System.Linq;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Brokerages.Binance
{
[TestFixture]
public partial class BinanceBrokerageTests
{
[Test]
[TestCaseSource(nameof(ValidHistory))]
[TestCaseSource(nameof(InvalidHistory))]
public void GetsHistory(Symbol symbol, Resolution resolution, TimeSpan period, bool throwsException)
{
TestDelegate test = () =>
{
var brokerage = (BinanceBrokerage)Brokerage;
var historyProvider = new BrokerageHistoryProvider();
historyProvider.SetBrokerage(brokerage);
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, new DataPermissionManager()));
var now = DateTime.UtcNow;
var requests = new[]
{
new HistoryRequest(now.Add(-period),
now,
typeof(TradeBar),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
DateTimeZone.Utc,
Resolution.Minute,
false,
false,
DataNormalizationMode.Adjusted,
TickType.Quote)
};
var history = historyProvider.GetHistory(requests, TimeZones.Utc);
foreach (var slice in history)
{
if (resolution == Resolution.Tick)
{
foreach (var tick in slice.Ticks[symbol])
{
Log.Trace("{0}: {1} - {2} / {3}", tick.Time.ToStringInvariant("yyyy-MM-dd HH:mm:ss.fff"), tick.Symbol, tick.BidPrice, tick.AskPrice);
}
}
else
{
var bar = slice.Bars[symbol];
Log.Trace("{0}: {1} - O={2}, H={3}, L={4}, C={5}", bar.Time, bar.Symbol, bar.Open, bar.High, bar.Low, bar.Close);
}
}
Log.Trace("Data points retrieved: " + historyProvider.DataPointCount);
};
if (throwsException)
{
Assert.Throws<ArgumentException>(test);
}
else
{
Assert.DoesNotThrow(test);
}
}
[Test]
[TestCaseSource(nameof(NoHistory))]
public void GetEmptyHistory(Symbol symbol, Resolution resolution, TimeSpan period)
{
TestDelegate test = () =>
{
var brokerage = (BinanceBrokerage)Brokerage;
var historyProvider = new BrokerageHistoryProvider();
historyProvider.SetBrokerage(brokerage);
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null,false, new DataPermissionManager()));
var now = DateTime.UtcNow;
var requests = new[]
{
new HistoryRequest(now.Add(-period),
now,
typeof(TradeBar),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
DateTimeZone.Utc,
Resolution.Minute,
false,
false,
DataNormalizationMode.Adjusted,
TickType.Quote)
};
var history = historyProvider.GetHistory(requests, TimeZones.Utc).ToList();
Log.Trace("Data points retrieved: " + historyProvider.DataPointCount);
Assert.AreEqual(0, historyProvider.DataPointCount);
};
Assert.DoesNotThrow(test);
}
private static TestCaseData[] ValidHistory
{
get
{
return new[]
{
// valid
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), Resolution.Minute, Time.OneHour, false),
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), Resolution.Hour, Time.OneDay, false),
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), Resolution.Daily, TimeSpan.FromDays(15), false),
};
}
}
private static TestCaseData[] NoHistory
{
get
{
return new[]
{
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), Resolution.Tick, TimeSpan.FromSeconds(15)),
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), Resolution.Second, Time.OneMinute),
};
}
}
private static TestCaseData[] InvalidHistory
{
get
{
return new[]
{
// invalid period, no error, empty result
new TestCaseData(Symbols.EURUSD, Resolution.Daily, TimeSpan.FromDays(-15), true),
// invalid symbol, throws "System.ArgumentException : Unknown symbol: XYZ"
new TestCaseData(Symbol.Create("XYZ", SecurityType.Crypto, Market.Binance), Resolution.Daily, TimeSpan.FromDays(15), true),
// invalid security type, throws "System.ArgumentException : Invalid security type: Equity"
new TestCaseData(Symbols.AAPL, Resolution.Daily, TimeSpan.FromDays(15), true),
};
}
}
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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 System;
using System.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using NUnit.Framework;
using QuantConnect.Brokerages.Binance;
using QuantConnect.Configuration;
using Moq;
using QuantConnect.Brokerages;
using QuantConnect.Tests.Common.Securities;
using QuantConnect.Orders;
using QuantConnect.Logging;
using System.Threading;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Brokerages.Binance
{
[TestFixture, Explicit("This test requires a configured and testable Binance practice account")]
public partial class BinanceBrokerageTests : BrokerageTests
{
private BinanceRestApiClient _binanceApi;
/// <summary>
/// Creates the brokerage under test and connects it
/// </summary>
/// <param name="orderProvider"></param>
/// <param name="securityProvider"></param>
/// <returns></returns>
protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider)
{
var securities = new SecurityManager(new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork))
{
{ Symbol, CreateSecurity(Symbol) }
};
var transactions = new SecurityTransactionManager(null, securities);
transactions.SetOrderProcessor(new FakeOrderProcessor());
var algorithm = new Mock<IAlgorithm>();
algorithm.Setup(a => a.Transactions).Returns(transactions);
algorithm.Setup(a => a.BrokerageModel).Returns(new BinanceBrokerageModel());
algorithm.Setup(a => a.Portfolio).Returns(new SecurityPortfolioManager(securities, transactions));
var apiKey = Config.Get("binance-api-key");
var apiSecret = Config.Get("binance-api-secret");
_binanceApi = new BinanceRestApiClient(
new BinanceSymbolMapper(),
algorithm.Object?.Portfolio,
apiKey,
apiSecret);
return new BinanceBrokerage(
apiKey,
apiSecret,
algorithm.Object,
new AggregationManager()
);
}
/// <summary>
/// Gets Binance symbol mapper
/// </summary>
protected ISymbolMapper SymbolMapper => new BinanceSymbolMapper();
/// <summary>
/// Gets the symbol to be traded, must be shortable
/// </summary>
protected override Symbol Symbol => StaticSymbol;
private static Symbol StaticSymbol => Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Binance);
/// <summary>
/// Gets the security type associated with the <see cref="BrokerageTests.Symbol" />
/// </summary>
protected override SecurityType SecurityType => SecurityType.Crypto;
public static TestCaseData[] OrderParameters => new[]
{
new TestCaseData(new MarketOrderTestParameters(StaticSymbol)).SetName("MarketOrder"),
new TestCaseData(new LimitOrderTestParameters(StaticSymbol, HighPrice, LowPrice)).SetName("LimitOrder"),
new TestCaseData(new StopLimitOrderTestParameters(StaticSymbol, HighPrice, LowPrice)).SetName("StopLimitOrder"),
};
/// <summary>
/// Gets a high price for the specified symbol so a limit sell won't fill
/// </summary>
private const decimal HighPrice = 0.04m;
/// <summary>
/// Gets a low price for the specified symbol so a limit buy won't fill
/// </summary>
private const decimal LowPrice = 0.01m;
/// <summary>
/// Gets the current market price of the specified security
/// </summary>
protected override decimal GetAskPrice(Symbol symbol)
{
var brokerageSymbol = SymbolMapper.GetBrokerageSymbol(symbol);
var prices = _binanceApi.GetTickers();
return prices
.FirstOrDefault(t => t.Symbol == brokerageSymbol)
.Price;
}
/// <summary>
/// Returns wether or not the brokers order methods implementation are async
/// </summary>
protected override bool IsAsync() => false;
/// <summary>
/// Gets the default order quantity. Min order 10USD.
/// </summary>
protected override decimal GetDefaultQuantity() => 0.01m;
[Test, TestCaseSource(nameof(OrderParameters))]
public override void CancelOrders(OrderTestParameters parameters)
{
base.CancelOrders(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void LongFromZero(OrderTestParameters parameters)
{
base.LongFromZero(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void CloseFromLong(OrderTestParameters parameters)
{
base.CloseFromLong(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void ShortFromZero(OrderTestParameters parameters)
{
base.ShortFromZero(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void CloseFromShort(OrderTestParameters parameters)
{
base.CloseFromShort(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void ShortFromLong(OrderTestParameters parameters)
{
base.ShortFromLong(parameters);
}
[Test, TestCaseSource(nameof(OrderParameters))]
public override void LongFromShort(OrderTestParameters parameters)
{
base.LongFromShort(parameters);
}
[Test, Ignore("Holdings are now set to 0 swaps at the start of each launch. Not meaningful.")]
public override void GetAccountHoldings()
{
Log.Trace("");
Log.Trace("GET ACCOUNT HOLDINGS");
Log.Trace("");
var before = Brokerage.GetAccountHoldings();
Assert.AreEqual(0, before.Count());
PlaceOrderWaitForStatus(new MarketOrder(Symbol, GetDefaultQuantity(), DateTime.Now));
Thread.Sleep(3000);
var after = Brokerage.GetAccountHoldings();
Assert.AreEqual(0, after.Count());
}
protected override void ModifyOrderUntilFilled(Order order, OrderTestParameters parameters, double secondsTimeout = 90)
{
Assert.Pass("Order update not supported. Please cancel and re-create.");
}
}
}

View File

@@ -0,0 +1,234 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Brokerages;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using System;
namespace QuantConnect.Tests.Brokerages.Binance
{
[TestFixture]
public class BinanceFeeModelTests
{
[Test]
public static void GetFeeModelTest()
{
var model = new BinanceBrokerageModel();
Assert.IsInstanceOf<BinanceFeeModel>(model.GetFeeModel(Security));
}
[Test]
[TestCaseSource(nameof(MakerOrders))]
public void ReturnShortOrderMakerFees(OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel();
var order = parameters.CreateShortOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : LowPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
BinanceFeeModel.MakerTier1Fee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(TakerOrders))]
public void ReturnShortOrderTakerFees(OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel();
var order = parameters.CreateShortOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : LowPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
BinanceFeeModel.TakerTier1Fee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(MakerOrders))]
public void ReturnLongOrderMakerFees(OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel();
var order = parameters.CreateLongOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : HighPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
BinanceFeeModel.MakerTier1Fee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(TakerOrders))]
public void ReturnLongOrderTakerFees(OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel();
var order = parameters.CreateLongOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : HighPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
BinanceFeeModel.TakerTier1Fee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(CustomMakerOrders))]
public void ReturnShortOrderCustomMakerFees(decimal mFee, decimal tFee, OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel(mFee, tFee);
var order = parameters.CreateShortOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : LowPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
mFee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(CustomTakerOrders))]
public void ReturnShortOrderCustomTakerFees(decimal mFee, decimal tFee, OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel(mFee, tFee);
var order = parameters.CreateShortOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : LowPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
tFee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(CustomMakerOrders))]
public void ReturnLongOrderCustomMakerFees(decimal mFee, decimal tFee, OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel(mFee, tFee);
var order = parameters.CreateLongOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : HighPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
mFee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
[Test]
[TestCaseSource(nameof(CustomTakerOrders))]
public void ReturnLongOrderCustomTakerFees(decimal mFee, decimal tFee, OrderTestParameters parameters)
{
IFeeModel feeModel = new BinanceFeeModel(mFee, tFee);
var order = parameters.CreateLongOrder(Quantity);
var price = order.Type == OrderType.Limit ? ((LimitOrder)order).LimitPrice : HighPrice;
var fee = feeModel.GetOrderFee(new OrderFeeParameters(Security, order));
Assert.AreEqual(
tFee * price * Math.Abs(Quantity),
fee.Value.Amount);
Assert.AreEqual("USDT", fee.Value.Currency);
}
private static Symbol Symbol => Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance);
private static Security Security
{
get
{
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(
typeof(TradeBar),
Symbol,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false
),
new Cash("USDT", 0, 1m),
SymbolProperties.GetDefault("USDT"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
security.SetMarketPrice(new Tick(DateTime.UtcNow, Symbol, LowPrice, HighPrice));
return security;
}
}
private static OrderSubmissionData OrderSubmissionData => new OrderSubmissionData(Security.BidPrice, Security.AskPrice, (Security.BidPrice + Security.AskPrice) / 2);
private static decimal HighPrice => 1000m;
private static decimal LowPrice => 100m;
private static decimal Quantity => 1m;
private static TestCaseData[] MakerOrders => new[]
{
new TestCaseData(new LimitOrderTestParameters(Symbol, HighPrice, LowPrice)),
new TestCaseData(new LimitOrderTestParameters(Symbol, HighPrice, LowPrice) { OrderSubmissionData = OrderSubmissionData}),
new TestCaseData(new LimitOrderTestParameters(Symbol, HighPrice, LowPrice, new BinanceOrderProperties())),
new TestCaseData(new LimitOrderTestParameters(Symbol, LowPrice, HighPrice, new BinanceOrderProperties() { PostOnly = true }){ OrderSubmissionData = OrderSubmissionData}),
new TestCaseData(new LimitOrderTestParameters(Symbol, HighPrice, LowPrice, new BinanceOrderProperties() { PostOnly = true }))
};
private static TestCaseData[] TakerOrders => new[]
{
new TestCaseData(new MarketOrderTestParameters(Symbol)),
new TestCaseData(new MarketOrderTestParameters(Symbol, new BinanceOrderProperties() { PostOnly = true })),
new TestCaseData(new LimitOrderTestParameters(Symbol, LowPrice, HighPrice) { OrderSubmissionData = OrderSubmissionData})
};
private static TestCaseData[] CustomMakerOrders => new[]
{
new TestCaseData(0.001m, 0.001m, new LimitOrderTestParameters(Symbol, HighPrice, LowPrice)),
new TestCaseData(0.0009m, 0.001m, new LimitOrderTestParameters(Symbol, HighPrice, LowPrice) { OrderSubmissionData = OrderSubmissionData}),
new TestCaseData(0.0008m, 0.001m, new LimitOrderTestParameters(Symbol, HighPrice, LowPrice, new BinanceOrderProperties())),
new TestCaseData(0.0007m, 0.0009m, new LimitOrderTestParameters(Symbol, LowPrice, HighPrice, new BinanceOrderProperties() { PostOnly = true }){ OrderSubmissionData = OrderSubmissionData}),
new TestCaseData(0.0006m, 0.0008m, new LimitOrderTestParameters(Symbol, HighPrice, LowPrice, new BinanceOrderProperties() { PostOnly = true }))
};
private static TestCaseData[] CustomTakerOrders => new[]
{
new TestCaseData(0.0007m, 0.0009m, new MarketOrderTestParameters(Symbol)),
new TestCaseData(0.0006m, 0.0008m, new MarketOrderTestParameters(Symbol, new BinanceOrderProperties { PostOnly = true })),
new TestCaseData(0.0005m, 0.0006m, new LimitOrderTestParameters(Symbol, LowPrice, HighPrice) { OrderSubmissionData = OrderSubmissionData})
};
}
}

View File

@@ -0,0 +1,171 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Brokerages.Binance;
using System;
namespace QuantConnect.Tests.Brokerages.Binance
{
[TestFixture]
public class BinanceSymbolMapperTests
{
private BinanceSymbolMapper _mapper;
[SetUp]
public void Setup()
{
_mapper = new BinanceSymbolMapper();
}
[Test]
[TestCaseSource(nameof(CryptoPairs))]
public void ReturnsCryptoSecurityType(string pair)
{
Assert.AreEqual(SecurityType.Crypto, _mapper.GetBrokerageSecurityType(pair));
var symbol = _mapper.GetLeanSymbol(pair);
Assert.AreEqual(SecurityType.Crypto, _mapper.GetLeanSecurityType(symbol.Value));
}
[Test]
[TestCaseSource(nameof(CryptoPairs))]
public void ReturnsCorrectLeanSymbol(string pair)
{
var symbol = _mapper.GetLeanSymbol(pair);
Assert.AreEqual(pair.LazyToUpper(), symbol.Value);
Assert.AreEqual(SecurityType.Crypto, symbol.ID.SecurityType);
Assert.AreEqual(Market.Binance, symbol.ID.Market);
}
[Test]
[TestCaseSource(nameof(RawCryptoSymbols))]
public void ReturnsCorrectLeanSymbol(string pair, SecurityType type, string market)
{
var symbol = _mapper.GetLeanSymbol(pair);
Assert.AreEqual(pair.LazyToUpper(), symbol.Value);
Assert.AreEqual(SecurityType.Crypto, symbol.ID.SecurityType);
Assert.AreEqual(Market.Binance, symbol.ID.Market);
}
[Test]
[TestCaseSource(nameof(CryptoSymbols))]
public void ReturnsCorrectBrokerageSymbol(Symbol symbol)
{
Assert.AreEqual(symbol.Value.LazyToUpper(), _mapper.GetBrokerageSymbol(symbol));
}
[Test]
[TestCaseSource(nameof(CurrencyPairs))]
public void ThrowsOnCurrencyPairs(string pair)
{
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSecurityType(pair));
}
[Test]
public void ThrowsOnNullOrEmptySymbols()
{
string ticker = null;
Assert.IsFalse(_mapper.IsKnownBrokerageSymbol(ticker));
Assert.Throws<ArgumentException>(() => _mapper.GetLeanSymbol(ticker, SecurityType.Crypto, Market.Binance));
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSecurityType(ticker));
ticker = "";
Assert.IsFalse(_mapper.IsKnownBrokerageSymbol(ticker));
Assert.Throws<ArgumentException>(() => _mapper.GetLeanSymbol(ticker, SecurityType.Crypto, Market.Binance));
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSecurityType(ticker));
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSymbol(Symbol.Create(ticker, SecurityType.Crypto, Market.Binance)));
}
[Test]
[TestCaseSource(nameof(UnknownSymbols))]
public void ThrowsOnUnknownSymbols(string pair, SecurityType type, string market)
{
Assert.IsFalse(_mapper.IsKnownBrokerageSymbol(pair));
Assert.Throws<ArgumentException>(() => _mapper.GetLeanSymbol(pair, type, market));
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSymbol(Symbol.Create(pair, type, market)));
}
[Test]
[TestCaseSource(nameof(UnknownSecurityType))]
public void ThrowsOnUnknownSecurityType(string pair, SecurityType type, string market)
{
Assert.IsTrue(_mapper.IsKnownBrokerageSymbol(pair));
Assert.Throws<ArgumentException>(() => _mapper.GetLeanSymbol(pair, type, market));
Assert.Throws<ArgumentException>(() => _mapper.GetBrokerageSymbol(Symbol.Create(pair, type, market)));
}
[Test]
[TestCaseSource(nameof(UnknownMarket))]
public void ThrowsOnUnknownMarket(string pair, SecurityType type, string market)
{
Assert.IsTrue(_mapper.IsKnownBrokerageSymbol(pair));
Assert.Throws<ArgumentException>(() => _mapper.GetLeanSymbol(pair, type, market));
Assert.AreEqual(pair.LazyToUpper(), _mapper.GetBrokerageSymbol(Symbol.Create(pair, type, market)));
}
#region Data
private static TestCaseData[] CryptoPairs => new[]
{
new TestCaseData("ETHUSDT"),
new TestCaseData("BTCUSDT"),
new TestCaseData("ETHBTC")
};
private static TestCaseData[] RawCryptoSymbols => new[]
{
new TestCaseData("ETHUSDT", SecurityType.Crypto, Market.Binance),
new TestCaseData("ETHBTC", SecurityType.Crypto, Market.Binance),
new TestCaseData("BTCUSDT", SecurityType.Crypto, Market.Binance),
};
private static TestCaseData[] CryptoSymbols => new[]
{
new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance)),
new TestCaseData(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance)),
new TestCaseData(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Binance))
};
private static TestCaseData[] CurrencyPairs => new[]
{
new TestCaseData(""),
new TestCaseData("EURUSD"),
new TestCaseData("GBPUSD"),
new TestCaseData("USDJPY")
};
private static TestCaseData[] UnknownSymbols => new[]
{
new TestCaseData("eth-usd", SecurityType.Crypto, Market.Binance),
new TestCaseData("BTC/USD", SecurityType.Crypto, Market.Binance),
new TestCaseData("eurusd", SecurityType.Crypto, Market.GDAX),
new TestCaseData("gbpusd", SecurityType.Forex, Market.Binance),
new TestCaseData("usdjpy", SecurityType.Forex, Market.FXCM),
new TestCaseData("btceth", SecurityType.Crypto, Market.Binance)
};
private static TestCaseData[] UnknownSecurityType => new[]
{
new TestCaseData("BTCUSDT", SecurityType.Forex, Market.Binance),
};
private static TestCaseData[] UnknownMarket => new[]
{
new TestCaseData("ETHUSDT", SecurityType.Crypto, Market.GDAX)
};
#endregion
}
}

View File

@@ -263,6 +263,123 @@ namespace QuantConnect.Tests.Common.Orders
TestOrderType(actual2);
}
[Test]
public void DeserializesStringStatusAndNullTime()
{
const string stringStatusJson = @"{
'Type': 4,
'Id': 1,
'ContingentId': 0,
'BrokerId': [
'1'
],
'Symbol': {
'Value': 'SPY',
'ID': 'SPY R735QTJ8XC9X',
'Permtick': 'SPY'
},
'Price': 321.66,
'PriceCurrency': 'USD',
'Time': '2019-12-24T14:31:00Z',
'CreatedTime': '2019-12-24T14:31:00Z',
'LastUpdateTime': '2019-12-25T14:31:00Z',
'LastFillTime': '2019-12-26T14:31:00Z',
'Quantity': 1.0,
'Status': 'filled',
'TimeInForce': {},
'Tag': '',
'Properties': {
'TimeInForce': {}
},
'SecurityType': 1,
'Direction': 0,
'AbsoluteQuantity': 1.0,
'Value': 321.66,
'OrderSubmissionData': {
'BidPrice': 321.4700,
'AskPrice': 321.4700,
'LastPrice': 321.4700
},
'IsMarketable': false
}";
const string nullTimeJson = @"{
'Type': 4,
'Id': 1,
'ContingentId': 0,
'BrokerId': [
'1'
],
'Symbol': {
'Value': 'SPY',
'ID': 'SPY R735QTJ8XC9X',
'Permtick': 'SPY'
},
'Price': 321.66,
'PriceCurrency': 'USD',
'Time': null,
'CreatedTime': '2019-12-24T14:31:00Z',
'LastUpdateTime': '2019-12-25T14:31:00Z',
'LastFillTime': '2019-12-26T14:31:00Z',
'Quantity': 1.0,
'Status': 3,
'TimeInForce': {},
'Tag': '',
'Properties': {
'TimeInForce': {}
},
'SecurityType': 1,
'Direction': 0,
'AbsoluteQuantity': 1.0,
'Value': 321.66,
'OrderSubmissionData': {
'BidPrice': 321.4700,
'AskPrice': 321.4700,
'LastPrice': 321.4700
},
'IsMarketable': false
}";
var time = DateTime.SpecifyKind(new DateTime(2019, 12, 24, 14, 31, 0), DateTimeKind.Utc);
var fillTime = DateTime.SpecifyKind(new DateTime(2019, 12, 26, 14, 31, 0), DateTimeKind.Utc);
var updateTime = DateTime.SpecifyKind(new DateTime(2019, 12, 25, 14, 31, 0), DateTimeKind.Utc);
var expected1 = new MarketOnOpenOrder(Symbol.Create("SPY", SecurityType.Equity, Market.USA), 1m, time)
{
Id = 1,
ContingentId = 0,
BrokerId = new List<string> { "1" },
Price = 321.66m,
PriceCurrency = "USD",
LastFillTime = fillTime,
LastUpdateTime = updateTime,
Status = OrderStatus.Filled,
OrderSubmissionData = new OrderSubmissionData(321.47m, 321.47m, 321.47m),
};
var expected2 = new MarketOnOpenOrder(Symbol.Create("SPY", SecurityType.Equity, Market.USA), 1m, time)
{
Id = 1,
ContingentId = 0,
BrokerId = new List<string> { "1" },
Price = 321.66m,
PriceCurrency = "USD",
LastFillTime = fillTime,
LastUpdateTime = updateTime,
Status = OrderStatus.Filled,
OrderSubmissionData = new OrderSubmissionData(321.47m, 321.47m, 321.47m),
};
var actual1 = (MarketOnOpenOrder)DeserializeOrder<MarketOnOpenOrder>(stringStatusJson);
var actual2 = (MarketOnOpenOrder)DeserializeOrder<MarketOnOpenOrder>(nullTimeJson);
TestOrderType(expected1);
TestOrderType(expected2);
TestOrderType(actual1);
TestOrderType(actual2);
}
[TestCase("Day")]
[TestCase("GoodTilCanceled")]
[TestCase("GoodTilDate")]

View File

@@ -585,23 +585,20 @@ namespace QuantConnect.Tests.Common.Securities
}
[Test]
public void EnsureCurrencyDataFeedThrowsWithUnsupportedCurrency()
public void EnsureCurrencyDataFeedDoesNothingWithUnsupportedCurrency()
{
Assert.Throws<ArgumentException>(() =>
var book = new CashBook
{
var book = new CashBook
{
{Currencies.USD, new Cash(Currencies.USD, 100, 1) },
{"ILS", new Cash("ILS", 0, 0.3m) }
};
var subscriptions = new SubscriptionManager();
var dataManager = new DataManagerStub(TimeKeeper);
subscriptions.SetDataManager(dataManager);
var securities = new SecurityManager(TimeKeeper);
{Currencies.USD, new Cash(Currencies.USD, 100, 1) },
{"ILS", new Cash("ILS", 0, 0.3m) }
};
var subscriptions = new SubscriptionManager();
var dataManager = new DataManagerStub(TimeKeeper);
subscriptions.SetDataManager(dataManager);
var securities = new SecurityManager(TimeKeeper);
// System.ArgumentException: In order to maintain cash in ILS you are required to add a subscription for Forex pair ILSUSD or USDILS
book.EnsureCurrencyDataFeeds(securities, subscriptions, MarketMap, SecurityChanges.None, dataManager.SecurityService);
});
var added = book.EnsureCurrencyDataFeeds(securities, subscriptions, MarketMap, SecurityChanges.None, dataManager.SecurityService);
Assert.IsEmpty(added);
}
private static TimeKeeper TimeKeeper

View File

@@ -16,6 +16,7 @@ namespace QuantConnect.Tests.Common.Securities.Cryptos
[Test]
[TestCase("BTCUSD", "USD")]
[TestCase("BTCEUR", "EUR")]
[TestCase("ETHBTC", "BTC")]
[TestCase("ETHUSDT", "USDT")]
public void ConstructorParseBaseCurrencyBySymbolProps(string ticker, string quote)

View File

@@ -23,14 +23,14 @@ namespace QuantConnect.Tests.Common.Securities.Futures
[TestFixture, Parallelizable(ParallelScope.All)]
public class FuturesExpiryUtilityFunctionsTests
{
[TestCase("08/05/2017", 4, "12/05/2017")]
[TestCase("10/05/2017", 5, "17/05/2017")]
[TestCase("24/12/2017", 3, "28/12/2017")]
public void AddBusinessDays_WithPositiveInput_ShouldReturnNthSuccedingBusinessDay(string time, int n, string actual)
[TestCase("08/05/2017 00:00:01", 4, "12/05/2017 00:00:01")]
[TestCase("10/05/2017 00:00:01", 5, "17/05/2017 00:00:01")]
[TestCase("24/12/2017 00:00:01", 3, "28/12/2017 00:00:01")]
public void AddBusinessDays_WithPositiveInput_ShouldReturnNthSucceedingBusinessDay(string time, int n, string actual)
{
//Arrange
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedDate = FuturesExpiryUtilityFunctions.AddBusinessDays(inputTime, n);
@@ -39,14 +39,14 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(actualDate, calculatedDate);
}
[TestCase("11/05/2017", -2, "09/05/2017")]
[TestCase("15/05/2017", -3, "10/05/2017")]
[TestCase("26/12/2017", -5, "18/12/2017")]
public void AddBusinessDays_WithNegativeInput_ShouldReturnNthprecedingBusinessDay(string time, int n, string actual)
[TestCase("11/05/2017 00:00:01", -2, "09/05/2017 00:00:01")]
[TestCase("15/05/2017 00:00:01", -3, "10/05/2017 00:00:01")]
[TestCase("26/12/2017 00:00:01", -5, "18/12/2017 00:00:01")]
public void AddBusinessDays_WithNegativeInput_ShouldReturnNthPrecedingBusinessDay(string time, int n, string actual)
{
//Arrange
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedDate = FuturesExpiryUtilityFunctions.AddBusinessDays(inputTime, n);
@@ -55,54 +55,54 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(actualDate, calculatedDate);
}
[TestCase("08/05/2017", 4, "15/05/2017", "12/05/2017")]
[TestCase("08/05/2017", 5, "22/05/2017", "15/05/2017", "16/05/2017", "17/05/2017", "18/05/2017", "19/05/2017")]
[TestCase("24/12/2017", 3, "27/12/2017")]
public void AddBusinessDays_WithPositiveInput_ShouldReturnNthSuccedingBusinessDay_ExcludingCustomHolidays(string time, int n, string actual, params string[] holidays)
[TestCase("08/05/2017 00:00:01", 4, "15/05/2017 00:00:01", "12/05/2017 00:00:01")]
[TestCase("08/05/2017 00:00:01", 5, "22/05/2017 00:00:01", "15/05/2017 00:00:01", "16/05/2017 00:00:01", "17/05/2017 00:00:01", "18/05/2017 00:00:01", "19/05/2017 00:00:01")]
[TestCase("24/12/2017 00:00:01", 3, "27/12/2017 00:00:01")]
public void AddBusinessDays_WithPositiveInput_ShouldReturnNthSucceedingBusinessDay_ExcludingCustomHolidays(string time, int n, string actual, params string[] holidays)
{
//Arrange
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedDate = FuturesExpiryUtilityFunctions.AddBusinessDays(
inputTime,
n,
useEquityHolidays: false,
holidayList: holidays.Select(x => Parse.DateTimeExact(x, "dd/MM/yyyy")));
holidayList: holidays.Select(x => Parse.DateTimeExact(x, "dd/MM/yyyy HH:mm:ss")));
//Assert
Assert.AreEqual(actualDate, calculatedDate);
}
[TestCase("11/05/2017", -1, "09/05/2017", "10/05/2017")]
[TestCase("15/05/2017", -1, "10/05/2017", "11/05/2017", "12/05/2017")]
[TestCase("26/12/2017", -1, "18/12/2017", "25/12/2017", "22/12/2017", "21/12/2017", "20/12/2017", "19/12/2017")]
public void AddBusinessDays_WithNegativeInput_ShouldReturnNthprecedingBusinessDay_ExcludingCustomHolidays(string time, int n, string actual, params string[] holidays)
[TestCase("11/05/2017 00:00:01", -1, "09/05/2017 00:00:01", "10/05/2017 00:00:01")]
[TestCase("15/05/2017 00:00:01", -1, "10/05/2017 00:00:01", "11/05/2017 00:00:01", "12/05/2017 00:00:01")]
[TestCase("26/12/2017 00:00:01", -1, "18/12/2017 00:00:01", "25/12/2017 00:00:01", "22/12/2017 00:00:01", "21/12/2017 00:00:01", "20/12/2017 00:00:01", "19/12/2017 00:00:01")]
public void AddBusinessDays_WithNegativeInput_ShouldReturnNthPrecedingBusinessDay_ExcludingCustomHolidays(string time, int n, string actual, params string[] holidays)
{
//Arrange
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var inputTime = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedDate = FuturesExpiryUtilityFunctions.AddBusinessDays(
inputTime,
n,
useEquityHolidays: false,
holidayList: holidays.Select(x => Parse.DateTimeExact(x, "dd/MM/yyyy")));
holidayList: holidays.Select(x => Parse.DateTimeExact(x, "dd/MM/yyyy HH:mm:ss")));
//Assert
Assert.AreEqual(actualDate, calculatedDate);
}
[TestCase("01/03/2016", 5, "24/03/2016")]
[TestCase("01/02/2014", 3, "26/02/2014")]
[TestCase("05/07/2017", 7, "21/07/2017")]
[TestCase("01/03/2016 00:00:01", 5, "24/03/2016 00:00:00")]
[TestCase("01/02/2014 00:00:01", 3, "26/02/2014 00:00:00")]
[TestCase("05/07/2017 00:00:01", 7, "21/07/2017 00:00:00")]
public void NthLastBusinessDay_WithInputsLessThanDaysInMonth_ShouldReturnNthLastBusinessDay(string time, int numberOfDays, string actual)
{
//Arrange
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
var actualDate = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedDate = FuturesExpiryUtilityFunctions.NthLastBusinessDay(inputDate, numberOfDays);
@@ -111,12 +111,12 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(actualDate, calculatedDate);
}
[TestCase("01/03/2016", 45)]
[TestCase("05/02/2017", 30)]
[TestCase("01/03/2016 00:00:00", 45)]
[TestCase("05/02/2017 00:00:00", 30)]
public void NthLastBusinessDay_WithInputsMoreThanDaysInMonth_ShouldThrowException(string time, int numberOfDays)
{
//Arrange
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
//Act
Assert.Throws<ArgumentOutOfRangeException>(() =>
@@ -125,43 +125,43 @@ namespace QuantConnect.Tests.Common.Securities.Futures
});
}
[TestCase("01/01/2016", 1, "01/04/2016")]
[TestCase("02/01/2019", 1, "02/01/2019")]
[TestCase("01/01/2019", 1, "01/02/2019")]
[TestCase("06/01/2019", 1, "06/03/2019")]
[TestCase("07/01/2019", 5, "07/08/2019")]
[TestCase("01/01/2016 00:00:01", 1, "01/04/2016 00:00:00")]
[TestCase("02/01/2019 00:00:01", 1, "02/01/2019 00:00:00")]
[TestCase("01/01/2019 00:00:01", 1, "01/02/2019 00:00:00")]
[TestCase("06/01/2019 00:00:01", 1, "06/03/2019 00:00:00")]
[TestCase("07/01/2019 00:00:01", 5, "07/08/2019 00:00:00")]
public void NthBusinessDay_ShouldReturnAccurateBusinessDay(string testDate, int nthBusinessDay, string actualDate)
{
var inputDate = Parse.DateTimeExact(testDate, "MM/dd/yyyy");
var expectedResult = Parse.DateTimeExact(actualDate, "MM/dd/yyyy");
var inputDate = Parse.DateTimeExact(testDate, "MM/dd/yyyy HH:mm:ss");
var expectedResult = Parse.DateTimeExact(actualDate, "MM/dd/yyyy HH:mm:ss");
var actual = FuturesExpiryUtilityFunctions.NthBusinessDay(inputDate, nthBusinessDay);
Assert.AreEqual(expectedResult, actual);
}
[TestCase("01/01/2016", 1, "01/05/2016", "01/04/2016")]
[TestCase("02/01/2019", 12, "02/20/2019", "02/19/2019")]
[TestCase("01/01/2019", 1, "01/07/2019", "01/02/2019", "01/03/2019", "01/04/2019")]
[TestCase("01/01/2016 00:00:01", 1, "01/05/2016 00:00:00", "01/04/2016 00:00:01")]
[TestCase("02/01/2019 00:00:01", 12, "02/20/2019 00:00:00", "02/19/2019 00:00:01")]
[TestCase("01/01/2019 00:00:01", 1, "01/07/2019 00:00:00", "01/02/2019 00:00:01", "01/03/2019 00:00:01", "01/04/2019 00:00:01")]
public void NthBusinessDay_ShouldReturnAccurateBusinessDay_WithHolidays(string testDate, int nthBusinessDay, string actualDate, params string[] holidayDates)
{
var inputDate = Parse.DateTimeExact(testDate, "MM/dd/yyyy");
var expectedResult = Parse.DateTimeExact(actualDate, "MM/dd/yyyy");
var holidays = holidayDates.Select(x => Parse.DateTimeExact(x, "MM/dd/yyyy"));
var inputDate = Parse.DateTimeExact(testDate, "MM/dd/yyyy HH:mm:ss");
var expectedResult = Parse.DateTimeExact(actualDate, "MM/dd/yyyy HH:mm:ss");
var holidays = holidayDates.Select(x => Parse.DateTimeExact(x, "MM/dd/yyyy HH:mm:ss"));
var actual = FuturesExpiryUtilityFunctions.NthBusinessDay(inputDate, nthBusinessDay, holidays);
Assert.AreEqual(expectedResult, actual);
}
[TestCase("01/2015","16/01/2015")]
[TestCase("06/2016", "17/06/2016")]
[TestCase("12/2018", "21/12/2018")]
public void ThirdFriday_WithNormalMonth_ShouldReturnThridFriday(string time, string actual)
[TestCase("01/2015", "16/01/2015 00:00:00")]
[TestCase("06/2016", "17/06/2016 00:00:00")]
[TestCase("12/2018", "21/12/2018 00:00:00")]
public void ThirdFriday_WithNormalMonth_ShouldReturnThirdFriday(string time, string actual)
{
//Arrange
var inputMonth = Parse.DateTimeExact(time, "MM/yyyy");
var actualFriday = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var actualFriday = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedFriday = FuturesExpiryUtilityFunctions.ThirdFriday(inputMonth);
@@ -170,14 +170,14 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(calculatedFriday, actualFriday);
}
[TestCase("04/2017", "19/04/2017")]
[TestCase("02/2015", "18/02/2015")]
[TestCase("01/2003", "15/01/2003")]
public void ThirdWednesday_WithNormalMonth_ShouldReturnThridWednesday(string time, string actual)
[TestCase("04/2017", "19/04/2017 00:00:00")]
[TestCase("02/2015", "18/02/2015 00:00:00")]
[TestCase("01/2003", "15/01/2003 00:00:00")]
public void ThirdWednesday_WithNormalMonth_ShouldReturnThirdWednesday(string time, string actual)
{
//Arrange
var inputMonth = Parse.DateTimeExact(time, "MM/yyyy");
var actualFriday = Parse.DateTimeExact(actual, "dd/MM/yyyy");
var actualFriday = Parse.DateTimeExact(actual, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedFriday = FuturesExpiryUtilityFunctions.ThirdWednesday(inputMonth);
@@ -186,13 +186,13 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(calculatedFriday, actualFriday);
}
[TestCase("07/05/2017")]
[TestCase("01/01/1998")]
[TestCase("25/03/2005")]
[TestCase("07/05/2017 00:00:01")]
[TestCase("01/01/1998 00:00:01")]
[TestCase("25/03/2005 00:00:01")]
public void NotHoliday_ForAHoliday_ShouldReturnFalse(string time)
{
//Arrange
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedValue = FuturesExpiryUtilityFunctions.NotHoliday(inputDate);
@@ -201,13 +201,13 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(calculatedValue, false);
}
[TestCase("08/05/2017")]
[TestCase("05/04/2007")]
[TestCase("27/05/2003")]
[TestCase("08/05/2017 00:00:01")]
[TestCase("05/04/2007 00:00:01")]
[TestCase("27/05/2003 00:00:01")]
public void NotHoliday_ForABusinessDay_ShouldReturnTrue(string time)
{
//Arrange
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(time, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedValue = FuturesExpiryUtilityFunctions.NotHoliday(inputDate);
@@ -216,13 +216,13 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(calculatedValue, true);
}
[TestCase("09/04/2017")]
[TestCase("02/04/2003")]
[TestCase("02/03/2002")]
public void NotPrecededByHoliday_WithNonThrusdayWeekday_ShouldThrowException(string day)
[TestCase("09/04/2017 00:00:01")]
[TestCase("02/04/2003 00:00:01")]
[TestCase("02/03/2002 00:00:01")]
public void NotPrecededByHoliday_WithNonThursdayWeekday_ShouldThrowException(string day)
{
//Arrange
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy HH:mm:ss");
//Act
Assert.Throws<ArgumentException>(() =>
@@ -231,12 +231,12 @@ namespace QuantConnect.Tests.Common.Securities.Futures
});
}
[TestCase("13/04/2017")]
[TestCase("14/02/2002")]
[TestCase("13/04/2017 00:00:01")]
[TestCase("14/02/2002 00:00:01")]
public void NotPrecededByHoliday_ForThursdayWithNoHolidayInFourPrecedingDays_ShouldReturnTrue(string day)
{
//Arrange
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedOutput = FuturesExpiryUtilityFunctions.NotPrecededByHoliday(inputDate);
@@ -245,12 +245,12 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(calculatedOutput, true);
}
[TestCase("31/03/2016")]
[TestCase("30/05/2002")]
[TestCase("31/03/2016 00:00:01")]
[TestCase("30/05/2002 00:00:01")]
public void NotPrecededByHoliday_ForThursdayWithHolidayInFourPrecedingDays_ShouldReturnFalse(string day)
{
//Arrange
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(day, "dd/MM/yyyy HH:mm:ss");
//Act
var calculatedOutput = FuturesExpiryUtilityFunctions.NotPrecededByHoliday(inputDate);
@@ -274,24 +274,24 @@ namespace QuantConnect.Tests.Common.Securities.Futures
Assert.AreEqual(expected, actual);
}
[TestCase("17/06/2020", DayOfWeek.Friday, 1, "05/06/2020")]
[TestCase("30/08/2017", DayOfWeek.Monday, 2, "14/08/2017")]
[TestCase("17/06/2020 00:00:01", DayOfWeek.Friday, 1, "05/06/2020 00:00:00")]
[TestCase("30/08/2017 00:00:01", DayOfWeek.Monday, 2, "14/08/2017 00:00:00")]
public void Nth_WeekDay_ShouldReturnCorrectDate(string contractDate, DayOfWeek dayOfWeek, int n, string expectedOutput)
{
// Arrange
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy HH:mm:ss");
var calculated = FuturesExpiryUtilityFunctions.NthWeekday(inputDate, n, dayOfWeek);
var expected = Parse.DateTimeExact(expectedOutput, "dd/MM/yyyy");
var expected = Parse.DateTimeExact(expectedOutput, "dd/MM/yyyy HH:mm:ss");
Assert.AreEqual(expected, calculated);
}
[TestCase("17/06/2020", DayOfWeek.Friday, -2)]
[TestCase("30/08/2017", DayOfWeek.Monday, 7)]
[TestCase("17/06/2020 00:00:01", DayOfWeek.Friday, -2)]
[TestCase("30/08/2017 00:00:01", DayOfWeek.Monday, 7)]
public void Nth_WeekDay_ShouldHandShouldThrowException(string contractDate, DayOfWeek dayOfWeek, int n)
{
// Arrange
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy HH:mm:ss");
//Act
Assert.Throws<ArgumentOutOfRangeException>(() =>
@@ -300,14 +300,14 @@ namespace QuantConnect.Tests.Common.Securities.Futures
});
}
[TestCase("06/01/2015", DayOfWeek.Friday, "30/01/2015")]
[TestCase("06/05/2016", DayOfWeek.Wednesday, "25/05/2016")]
[TestCase("06/01/2015 00:00:01", DayOfWeek.Friday, "30/01/2015 00:00:00")]
[TestCase("06/05/2016 00:00:01", DayOfWeek.Wednesday, "25/05/2016 00:00:00")]
public void Last_WeekDay_ShouldReturnCorrectDate(string contractDate, DayOfWeek dayOfWeek, string expectedOutput)
{
// Arrange
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy");
var inputDate = Parse.DateTimeExact(contractDate, "dd/MM/yyyy HH:mm:ss");
var calculated = FuturesExpiryUtilityFunctions.LastWeekday(inputDate, dayOfWeek);
var expected = Parse.DateTimeExact(expectedOutput, "dd/MM/yyyy");
var expected = Parse.DateTimeExact(expectedOutput, "dd/MM/yyyy HH:mm:ss");
Assert.AreEqual(expected, calculated);
}

View File

@@ -1255,7 +1255,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
Assert.AreEqual("Option Exercise", fills[1].Message);
foreach (var fill in fills)
{
@@ -1318,7 +1323,10 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.AreEqual("OTM", fills[0].Message);
foreach (var fill in fills)
{
@@ -1381,7 +1389,10 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.AreEqual("OTM", fills[0].Message);
foreach (var fill in fills)
{
@@ -1442,7 +1453,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
Assert.AreEqual("Option Exercise", fills[1].Message);
foreach (var fill in fills)
{
@@ -1507,7 +1523,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
Assert.AreEqual("Option Exercise", fills[1].Message);
foreach (var fill in fills)
{
@@ -1569,7 +1590,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
Assert.AreEqual("Option Assignment", fills[1].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -1638,7 +1664,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
Assert.AreEqual("Option Assignment", fills[1].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -1704,7 +1735,12 @@ namespace QuantConnect.Tests.Common.Securities
var order = (OptionExerciseOrder)transactions.GetOrders(x => true).First();
option.Underlying = securities[Symbols.SPY];
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(2, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
Assert.AreEqual("Option Assignment", fills[1].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -1772,7 +1808,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
foreach (var fill in fills)
{
@@ -1834,7 +1874,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("OTM", fills[0].Message);
foreach (var fill in fills)
{
@@ -1895,7 +1939,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
foreach (var fill in fills)
{
@@ -2082,7 +2130,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("Automatic Exercise", fills[0].Message);
foreach (var fill in fills)
{
@@ -2145,7 +2197,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -2210,7 +2266,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsFalse(fills[0].IsAssignment);
Assert.AreEqual("OTM", fills[0].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -2277,7 +2337,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;
@@ -2344,7 +2408,11 @@ namespace QuantConnect.Tests.Common.Securities
option.Underlying = securities[Symbols.SPY];
option.ExerciseSettlement = SettlementType.Cash;
var fills = option.OptionExerciseModel.OptionExercise(option, order);
var fills = option.OptionExerciseModel.OptionExercise(option, order).ToList();
Assert.AreEqual(1, fills.Count);
Assert.IsTrue(fills[0].IsAssignment);
Assert.AreEqual("Automatic Assignment", fills[0].Message);
// we are simulating assignment by calling a method for this
var portfolioModel = (OptionPortfolioModel)option.PortfolioModel;

View File

@@ -20,10 +20,15 @@ using Newtonsoft.Json;
using NodaTime;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.HistoricalData;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Packets;
@@ -1149,6 +1154,92 @@ actualDictionary.update({'IBM': 5})
Assert.AreEqual(Time.EndOfTime, func(second));
}
[Test]
[TestCase(OptionRight.Call, true, OrderDirection.Sell)]
[TestCase(OptionRight.Call, false, OrderDirection.Buy)]
[TestCase(OptionRight.Put, true, OrderDirection.Buy)]
[TestCase(OptionRight.Put, false, OrderDirection.Sell)]
public void GetsExerciseDirection(OptionRight right, bool isShort, OrderDirection expected)
{
var actual = right.GetExerciseDirection(isShort);
Assert.AreEqual(expected, actual);
}
[Test]
public void AppliesScalingToEquityTickQuotes()
{
// This test ensures that all Ticks with TickType == TickType.Quote have adjusted BidPrice and AskPrice.
// Relevant issue: https://github.com/QuantConnect/Lean/issues/4788
var algo = new QCAlgorithm();
var dataFeed = new NullDataFeed();
algo.SubscriptionManager = new SubscriptionManager();
algo.SubscriptionManager.SetDataManager(new DataManager(
dataFeed,
new UniverseSelection(
algo,
new SecurityService(
new CashBook(),
MarketHoursDatabase.FromDataFolder(),
SymbolPropertiesDatabase.FromDataFolder(),
algo,
null,
null
),
new DataPermissionManager(),
new DefaultDataProvider()
),
algo,
new TimeKeeper(DateTime.UtcNow),
MarketHoursDatabase.FromDataFolder(),
false,
null,
new DataPermissionManager()
));
using (var zipDataCacheProvider = new ZipDataCacheProvider(new DefaultDataProvider()))
{
algo.HistoryProvider = new SubscriptionDataReaderHistoryProvider();
algo.HistoryProvider.Initialize(
new HistoryProviderInitializeParameters(
null,
null,
null,
zipDataCacheProvider,
new LocalDiskMapFileProvider(),
new LocalDiskFactorFileProvider(),
(_) => {},
false,
new DataPermissionManager()));
algo.SetStartDate(DateTime.UtcNow.AddDays(-1));
var history = algo.History(new[] { Symbols.IBM }, new DateTime(2013, 10, 7), new DateTime(2013, 10, 8), Resolution.Tick).ToList();
Assert.AreEqual(57401, history.Count);
foreach (var slice in history)
{
if (!slice.Ticks.ContainsKey(Symbols.IBM))
{
continue;
}
foreach (var tick in slice.Ticks[Symbols.IBM])
{
if (tick.BidPrice != 0)
{
Assert.LessOrEqual(Math.Abs(tick.Value - tick.BidPrice), 0.05);
}
if (tick.AskPrice != 0)
{
Assert.LessOrEqual(Math.Abs(tick.Value - tick.AskPrice), 0.05);
}
}
}
}
}
private PyObject ConvertToPyObject(object value)
{
using (Py.GIL())

View File

@@ -37,7 +37,7 @@ namespace QuantConnect.Tests.Python
//Filter function that returns a list of symbols
var module = PythonEngine.ModuleFromString(Guid.NewGuid().ToString(),
"def filter(universe):\n" +
" universe = universe.WeeklysOnly().Expiration(0, 10)\n" +
" universe = universe.WeeklysOnly().Expiration(0, 10)\n" +
" return [symbol for symbol in universe\n"+
" if symbol.ID.OptionRight != OptionRight.Put\n" +
" and universe.Underlying.Price - symbol.ID.StrikePrice < 10]\n"
@@ -96,7 +96,7 @@ namespace QuantConnect.Tests.Python
{"Tracking Error", "0"},
{"Treynor Ratio", "0"},
{"Total Fees", "$1.00"},
{"OrderListHash", "-379511851"}
{"OrderListHash", "-1843155373"}
},
Language.Python,
AlgorithmStatus.Completed);
@@ -152,7 +152,7 @@ namespace QuantConnect.Tests.Python
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "1935621950"}
{"OrderListHash", "-1438496252"}
},
Language.Python,
AlgorithmStatus.Completed);

View File

@@ -363,6 +363,11 @@
<Compile Include="API\NodeTests.cs" />
<Compile Include="Brokerages\BrokerageFactoryTests.cs" />
<Compile Include="Brokerages\Bitfinex\BitfinexBrokerageAdditionalTests.cs" />
<Compile Include="Brokerages\Binance\BinanceBrokerageHistoryProviderTests.cs" />
<Compile Include="Brokerages\Binance\BinanceBrokerageTests.cs" />
<Compile Include="Brokerages\Binance\BinanceFeeModelTests.cs" />
<Compile Include="Brokerages\Binance\BinanceSymbolMapperTests.cs" />
<Compile Include="Brokerages\Binance\BinanceBrokerageExchangeInfoTests.cs" />
<Compile Include="Brokerages\Bitfinex\BitfinexFeeModelTests.cs" />
<Compile Include="Brokerages\Bitfinex\BitfinexSymbolMapperTests.cs" />
<Compile Include="Brokerages\Bitfinex\BitfinexBrokerageHistoryProviderTests.cs" />
@@ -733,6 +738,7 @@
<Compile Include="Indicators\StochasticTests.cs" />
<Compile Include="Research\QuantBookFundamentalTests.cs" />
<Compile Include="Python\PythonOptionTests.cs" />
<Compile Include="Report\DrawdownCollectionTests.cs" />
<Compile Include="Research\QuantBookIndicatorsTests.cs" />
<Compile Include="Research\QuantBookHistoryTests.cs" />
<Compile Include="Messaging\StreamingMessageHandlerTests.cs" />

View File

@@ -0,0 +1,45 @@
/*
* 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 System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Report;
namespace QuantConnect.Tests.Report
{
[TestFixture]
public class DrawdownCollectionTests
{
[Test]
public void MaxDrawdown()
{
var series = new Deedle.Series<DateTime, double>(new []
{
new KeyValuePair<DateTime, double>(new DateTime(2020, 1, 1), 100000),
new KeyValuePair<DateTime, double>(new DateTime(2020, 1, 2), 90000),
new KeyValuePair<DateTime, double>(new DateTime(2020, 1, 3), 100000),
new KeyValuePair<DateTime, double>(new DateTime(2020, 1, 4), 100000),
new KeyValuePair<DateTime, double>(new DateTime(2020, 1, 5), 80000)
});
var collection = DrawdownCollection.GetDrawdownPeriods(series, 1).ToList();
Assert.AreEqual(1, collection.Count);
Assert.AreEqual(0.2, collection.First().Drawdown, 0.0001);
}
}
}

View File

@@ -47,5 +47,71 @@ namespace QuantConnect.Tests.Report
Assert.DoesNotThrow(() => PortfolioLooper.FromOrders(series, orders).ToList());
}
[TestCase(OrderType.Market, 0, 0)]
[TestCase(OrderType.Limit, 0, 80000)]
[TestCase(OrderType.StopLimit, 80000, 80000)]
[TestCase(OrderType.StopMarket, 80000, 0)]
[TestCase(OrderType.MarketOnOpen, 0, 0, true)]
[TestCase(OrderType.MarketOnClose, 0, 0, true)]
public void OrderProcessedInLooper(OrderType orderType, double stopPrice, double limitPrice, bool hasNullLastFillTime = false)
{
var equityPoints = new SortedList<DateTime, double>
{
{ new DateTime(2019, 1, 3, 5, 0, 5), 100000 },
{ new DateTime(2019, 1, 4, 5, 0, 5), 90000 },
};
var series = new Series<DateTime, double>(equityPoints);
var entryOrder = Order.CreateOrder(new SubmitOrderRequest(
orderType,
SecurityType.Equity,
Symbols.SPY,
1,
(decimal)stopPrice,
(decimal)limitPrice,
new DateTime(2019, 1, 3, 5, 0, 5),
string.Empty
));
var exitOrder = Order.CreateOrder(new SubmitOrderRequest(
orderType,
SecurityType.Equity,
Symbols.SPY,
-1,
(decimal)stopPrice,
(decimal)limitPrice,
new DateTime(2019, 1, 4, 5, 0, 5),
string.Empty
));
if (!hasNullLastFillTime)
{
entryOrder.LastFillTime = new DateTime(2019, 1, 3, 5, 0, 5);
exitOrder.LastFillTime = new DateTime(2019, 1, 4, 5, 0, 5);
}
entryOrder.GetType().GetProperty("Id").SetValue(entryOrder, 1);
entryOrder.GetType().GetProperty("Price").SetValue(entryOrder, 100000m);
Order marketOnFillOrder = null;
if (hasNullLastFillTime)
{
marketOnFillOrder = entryOrder.Clone();
marketOnFillOrder.GetType().GetProperty("Status").SetValue(marketOnFillOrder, OrderStatus.Filled);
marketOnFillOrder.GetType().GetProperty("Time").SetValue(marketOnFillOrder, new DateTime(2019, 1, 3, 6, 0 ,5));
}
exitOrder.GetType().GetProperty("Id").SetValue(exitOrder, 2);
exitOrder.GetType().GetProperty("Price").SetValue(exitOrder, 80000m);
exitOrder.GetType().GetProperty("Status").SetValue(exitOrder, OrderStatus.Filled);
var orders = new[] { entryOrder, marketOnFillOrder, exitOrder }.Where(x => x != null);
var looper = PortfolioLooper.FromOrders(series, orders);
var pointInTimePortfolio = looper.ToList();
Assert.AreEqual(3, pointInTimePortfolio.Count);
Assert.AreEqual(100000, pointInTimePortfolio[0].TotalPortfolioValue);
Assert.AreEqual(80000, pointInTimePortfolio[1].TotalPortfolioValue);
Assert.AreEqual(80000, pointInTimePortfolio[2].TotalPortfolioValue);
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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 NodaTime;
using QuantConnect.Brokerages.Binance;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Util;
namespace QuantConnect.ToolBox.BinanceDownloader
{
/// <summary>
/// Binance Downloader class
/// </summary>
public class BinanceDataDownloader : IDataDownloader, IDisposable
{
private readonly BinanceBrokerage _brokerage;
private readonly BinanceSymbolMapper _symbolMapper = new BinanceSymbolMapper();
/// <summary>
/// Initializes a new instance of the <see cref="BinanceDataDownloader"/> class
/// </summary>
public BinanceDataDownloader()
{
_brokerage = new BinanceBrokerage(null, null,null, null);
}
/// <summary>
/// Get historical data enumerable for a single symbol, type and resolution given this start and end time (in UTC).
/// </summary>
/// <param name="symbol">Symbol for the data we're looking for.</param>
/// <param name="resolution">Resolution of the data request</param>
/// <param name="startUtc">Start time of the data in UTC</param>
/// <param name="endUtc">End time of the data in UTC</param>
/// <returns>Enumerable of base data for this symbol</returns>
public IEnumerable<BaseData> Get(Symbol symbol, Resolution resolution, DateTime startUtc, DateTime endUtc)
{
if (resolution == Resolution.Tick || resolution == Resolution.Second)
{
throw new ArgumentException($"Resolution not available: {resolution}");
}
if (!_symbolMapper.IsKnownLeanSymbol(symbol))
{
throw new ArgumentException($"The ticker {symbol.Value} is not available.");
}
if (endUtc < startUtc)
{
throw new ArgumentException("The end date must be greater or equal than the start date.");
}
var historyRequest = new HistoryRequest(
startUtc,
endUtc,
typeof(TradeBar),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
DateTimeZone.Utc,
resolution,
false,
false,
DataNormalizationMode.Adjusted,
TickType.Trade);
return _brokerage.GetHistory(historyRequest);
}
/// <summary>
/// Creates Lean Symbol
/// </summary>
/// <param name="ticker"></param>
/// <returns></returns>
internal Symbol GetSymbol(string ticker)
{
return _symbolMapper.GetLeanSymbol(ticker);
}
/// <summary>
/// Aggregates a list of minute bars at the requested resolution
/// </summary>
/// <param name="symbol"></param>
/// <param name="bars"></param>
/// <param name="resolution"></param>
/// <returns></returns>
internal IEnumerable<TradeBar> AggregateBars(Symbol symbol, IEnumerable<TradeBar> bars, TimeSpan resolution)
{
return
(from b in bars
group b by b.Time.RoundDown(resolution)
into g
select new TradeBar
{
Symbol = symbol,
Time = g.Key,
Open = g.First().Open,
High = g.Max(b => b.High),
Low = g.Min(b => b.Low),
Close = g.Last().Close,
Volume = g.Sum(b => b.Volume),
Value = g.Last().Close,
DataType = MarketDataType.TradeBar,
Period = resolution,
EndTime = g.Key.AddMilliseconds(resolution.TotalMilliseconds)
});
}
public void Dispose()
{
_brokerage.Disconnect();
_brokerage.DisposeSafely();
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.Configuration;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.ToolBox.BinanceDownloader
{
public static class BinanceDownloaderProgram
{
/// <summary>
/// Primary entry point to the program.
/// </summary>
public static void DataDownloader(IList<string> tickers, string resolution, DateTime fromDate, DateTime toDate)
{
if (resolution.IsNullOrEmpty() || tickers.IsNullOrEmpty())
{
Console.WriteLine("BinanceDownloader ERROR: '--tickers=' or '--resolution=' parameter is missing");
Console.WriteLine("--tickers=eg BTCUSD");
Console.WriteLine("--resolution=Minute/Hour/Daily/All");
Environment.Exit(1);
}
try
{
var allResolutions = resolution.Equals("all", StringComparison.OrdinalIgnoreCase);
var castResolution = allResolutions ? Resolution.Minute : (Resolution)Enum.Parse(typeof(Resolution), resolution);
// Load settings from config.json
var dataDirectory = Config.Get("data-folder", "../../../Data");
using (var downloader = new BinanceDataDownloader())
{
foreach (var ticker in tickers)
{
// Download the data
var startDate = fromDate;
var symbol = downloader.GetSymbol(ticker);
var data = downloader.Get(symbol, castResolution, fromDate, toDate);
var bars = data.Cast<TradeBar>().ToList();
// Save the data (single resolution)
var writer = new LeanDataWriter(castResolution, symbol, dataDirectory);
writer.Write(bars);
if (allResolutions)
{
// Save the data (other resolutions)
foreach (var res in new[] { Resolution.Hour, Resolution.Daily })
{
var resData = downloader.AggregateBars(symbol, bars, res.ToTimeSpan());
writer = new LeanDataWriter(res, symbol, dataDirectory);
writer.Write(resData);
}
}
}
}
}
catch (Exception err)
{
Log.Error(err);
}
}
/// <summary>
/// Endpoint for downloading exchange info
/// </summary>
public static void ExchangeInfoDownloader()
{
new ExchangeInfoUpdater(new BinanceExchangeInfoDownloader())
.Run();
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.ToolBox.BinanceDownloader.Models;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace QuantConnect.ToolBox.BinanceDownloader
{
/// <summary>
/// Binance implementation of <see cref="IExchangeInfoDownloader"/>
/// </summary>
public class BinanceExchangeInfoDownloader : IExchangeInfoDownloader
{
/// <summary>
/// Market name
/// </summary>
public string Market => QuantConnect.Market.Binance;
/// <summary>
/// Pulling data from a remote source
/// </summary>
/// <returns>Enumerable of exchange info</returns>
public IEnumerable<string> Get()
{
var request = (HttpWebRequest)WebRequest.Create("https://api.binance.com/api/v3/exchangeInfo");
using (var response = (HttpWebResponse)request.GetResponse())
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
var exchangeInfo = JsonConvert.DeserializeObject<ExchangeInfo>(reader.ReadToEnd());
foreach (var symbol in exchangeInfo.Symbols.OrderBy(x => x.Name))
{
if (!symbol.IsSpotTradingAllowed && !symbol.IsMarginTradingAllowed)
{
// exclude derivatives
continue;
}
var priceFilter = symbol.Filters
.First(f => f.GetValue("filterType").ToString() == "PRICE_FILTER")
.GetValue("tickSize");
var lotFilter = symbol.Filters
.First(f => f.GetValue("filterType").ToString() == "LOT_SIZE")
.GetValue("stepSize");
yield return $"binance,{symbol.Name},crypto,{symbol.Name},{symbol.QuoteAsset},1,{priceFilter},{lotFilter}";
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.
*
*/
namespace QuantConnect.ToolBox.BinanceDownloader.Models
{
/// <summary>
/// Represents Binance exchange info response
/// https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#exchange-information
/// </summary>
public class ExchangeInfo
{
public Symbol[] Symbols { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace QuantConnect.ToolBox.BinanceDownloader.Models
{
/// <summary>
/// Represents Binance exchange info for symbol
/// </summary>
public class Symbol
{
[JsonProperty(PropertyName = "symbol")]
public string Name { get; set; }
public string BaseAsset { get; set; }
public string QuoteAsset { get; set; }
public bool IsSpotTradingAllowed { get; set; }
public bool IsMarginTradingAllowed { get; set; }
/// <summary>
/// Exchange info filter defines trading rules on a symbol or an exchange
/// https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#filters
/// </summary>
public JObject[] Filters { get; set; }
public string[] Permissions { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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 System;
using System.IO;
namespace QuantConnect.ToolBox
{
/// <summary>
/// Base tool for pulling data from a remote source and updating existing csv file.
/// </summary>
public class ExchangeInfoUpdater
{
private readonly IExchangeInfoDownloader _eidl;
public ExchangeInfoUpdater(IExchangeInfoDownloader eidl)
{
_eidl = eidl;
}
/// <summary>
/// Update existing symbol properties database
/// </summary>
public void Run()
{
var directory = Path.Combine(Globals.DataFolder, "symbol-properties");
var file = Path.Combine(directory, "symbol-properties-database.csv");
var tmp = Path.Combine(directory, "symbol-properties-database.tmp.csv");
if (!File.Exists(file))
{
throw new FileNotFoundException("Unable to locate symbol properties file: " + file);
}
using (var writer = new StreamWriter(tmp))
{
var fetch = false;
// skip the first header line, also skip #'s as these are comment lines
foreach (var line in File.ReadLines(file))
{
if (!line.StartsWithInvariant(_eidl.Market, true))
{
writer.WriteLine(line);
}
else if (!fetch)
{
foreach (var upd in _eidl.Get())
{
writer.WriteLine(upd);
}
fetch = true;
}
}
if (!fetch)
{
writer.WriteLine(Environment.NewLine);
foreach (var upd in _eidl.Get())
{
writer.WriteLine(upd);
}
}
}
File.Delete(file); // Delete the existing file if exists
File.Move(tmp, file);
}
}
}

View File

@@ -1,11 +1,11 @@
/*
* 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");
*
* 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.

View File

@@ -0,0 +1,36 @@
/*
* 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 System.Collections.Generic;
namespace QuantConnect.ToolBox
{
/// <summary>
/// Exchange Info Downloader Interface for pulling data from a remote source.
/// </summary>
public interface IExchangeInfoDownloader
{
/// <summary>
/// Market
/// </summary>
string Market { get; }
/// <summary>
/// Get exchange info coma-separated data
/// </summary>
/// <returns>Enumerable of exchange info for this market</returns>
IEnumerable<string> Get();
}
}

View File

@@ -21,6 +21,7 @@ using QuantConnect.Configuration;
using QuantConnect.ToolBox.AlgoSeekFuturesConverter;
using QuantConnect.ToolBox.AlgoSeekOptionsConverter;
using QuantConnect.ToolBox.Benzinga;
using QuantConnect.ToolBox.BinanceDownloader;
using QuantConnect.ToolBox.BitfinexDownloader;
using QuantConnect.ToolBox.CoarseUniverseGenerator;
using QuantConnect.ToolBox.CoinApiDataConverter;
@@ -126,6 +127,10 @@ namespace QuantConnect.ToolBox
case "bitfinexdownloader":
BitfinexDownloaderProgram.BitfinexDownloader(tickers, resolution, fromDate, toDate);
break;
case "mbxdl":
case "binancedownloader":
BinanceDownloaderProgram.DataDownloader(tickers, resolution, fromDate, toDate);
break;
case "secdl":
case "secdownloader":
SECDataDownloaderProgram.SECDataDownloader(
@@ -187,6 +192,19 @@ namespace QuantConnect.ToolBox
break;
}
}
else if (targetApp.Contains("updater") || targetApp.EndsWith("spu"))
{
switch (targetApp)
{
case "mbxspu":
case "binancesymbolpropertiesupdater":
BinanceDownloaderProgram.ExchangeInfoDownloader();
break;
default:
PrintMessageAndExit(1, "ERROR: Unrecognized --app value");
break;
}
}
else
{
switch (targetApp)

View File

@@ -317,6 +317,11 @@
<Compile Include="Benzinga\BenzingaNewsDataConverter.cs" />
<Compile Include="Benzinga\BenzingaProgram.cs" />
<Compile Include="Benzinga\BenzingaNewsDataDownloader.cs" />
<Compile Include="BinanceDownloader\Models\ExchangeInfo.cs" />
<Compile Include="BinanceDownloader\Models\Symbol.cs" />
<Compile Include="BinanceDownloader\BinanceDownloaderProgram.cs" />
<Compile Include="BinanceDownloader\BinanceDataDownloader.cs" />
<Compile Include="BinanceDownloader\BinanceExchangeInfoDownloader.cs" />
<Compile Include="BitfinexDownloader\BitfinexDataDownloader.cs" />
<Compile Include="BitfinexDownloader\BitfinexDownloaderProgram.cs" />
<Compile Include="CoarseUniverseGenerator\SecurityIdentifierContext.cs" />
@@ -344,6 +349,8 @@
<Compile Include="GzipStreamProvider.cs" />
<Compile Include="IBDownloader\IBDataDownloader.cs" />
<Compile Include="IBDownloader\IBDownloaderProgram.cs" />
<Compile Include="IExchangeInfoDownloader.cs" />
<Compile Include="ExchangeInfoUpdater.cs" />
<Compile Include="IEX\IEXDataDownloader.cs" />
<Compile Include="IEX\IEXDownloaderProgram.cs" />
<Compile Include="IQFeedDownloader\IQFeedDataDownloader.cs" />

View File

@@ -34,6 +34,7 @@ Example: --app=YahooDownloader --tickers=SPY,AAPL --resolution=Daily --from-date
- YahooDownloader or YDL
- IEXDownloader or IEXDL
- BitfinexDownloader or BFXDL
- BinanceDownloader or MBXDL
- **'--from-date=yyyyMMdd-HH:mm:ss'** required
- **'--tickers=SPY,AAPL,etc'** required, except for QuandlBitfinexDownloader (QBDL)
- **'--resolution=Tick/Second/Minute/Hour/Daily/All'** required, except for QuandlBitfinexDownloader (QBDL), CryptoiqDownloader (CDL). **Case sensitive. Not all downloaders support all resolutions**, send empty for more information.

View File

@@ -113,7 +113,7 @@ namespace QuantConnect.ToolBox.YahooDownloader
parsed.Add(new Dividend
{
Time = Parse.DateTimeExact(values[0].Replace("-", string.Empty), DateFormat.EightCharacter),
Value = Parse.Decimal(values[1])
Value = Parse.Decimal(values[1], System.Globalization.NumberStyles.Float)
});
}
}

View File

@@ -12,15 +12,20 @@
Lean Engine is an open-source algorithmic trading engine built for easy strategy research, backtesting and live trading. We integrate with common data providers and brokerages so you can quickly deploy algorithmic trading strategies.
The core of the LEAN Engine is written in C#; but it operates seamlessly on Linux, Mac and Windows operating systems. It supports algorithms written in Python 3.6, C# or F#. Lean drives the web based algorithmic trading platform [QuantConnect][4].
The core of the LEAN Engine is written in C#; but it operates seamlessly on Linux, Mac and Windows operating systems. It supports algorithms written in Python 3.6, C# or F#. Lean drives the web-based algorithmic trading platform [QuantConnect][4].
## Proudly Sponsored By ##
Want your company logo here? [Sponsor LEAN](https://github.com/sponsors/QuantConnect) to be part of radically open algorithmic-trading innovation.
## QuantConnect is Hiring! ##
Join the team and solve some of the most difficult challenges in quantitative finance. If you are passionate about algorithmic trading we'd like to hear from you. The below roles are open in our Seattle, WA office. When applying, make sure to mention you came through GitHub:
- [**Senior UX Developer**](mailto:jared@quantconnect.com): Collaborate with QuantConnect to develop a world leading online experience for a community of developers from all over the world.
- [**Senior UX Developer**](mailto:jared@quantconnect.com): Collaborate with QuantConnect to develop a world-leading online experience for a community of developers from all over the world.
- [**Technical Writers**](mailto:jared@quantconnect.com): Help us improve the QuantConnect and LEAN documentation with hands on tutorials with how to use all the adaptors LEAN offers, and how to setup trading locally.
- [**Technical Writers**](mailto:jared@quantconnect.com): Help us improve the QuantConnect and LEAN documentation with hands-on tutorials with how to use all the adaptors LEAN offers, and how to set up trading locally.
- [**Quantitative Development Intern**](mailto:jared@quantconnect.com): If you are a recent or current graduate with a knack for quantitative finance, consider applying for an internship!
@@ -28,7 +33,7 @@ Join the team and solve some of the most difficult challenges in quantitative fi
![alt tag](Documentation/2-Overview-Detailed-New.png)
The Engine is broken into many modular pieces which can be extended without touching other files. The modules are configured in config.json as set "environments". Through these environments you can control LEAN to operate in the mode required.
The Engine is broken into many modular pieces which can be extended without touching other files. The modules are configured in config.json as set "environments". Through these environments, you can control LEAN to operate in the mode required.
The most important plugins are:
@@ -36,13 +41,13 @@ The most important plugins are:
> Handle all messages from the algorithmic trading engine. Decide what should be sent, and where the messages should go. The result processing system can send messages to a local GUI, or the web interface.
- **Datafeed Sourcing** (IDataFeed)
> Connect and download data required for the algorithmic trading engine. For backtesting this sources files from the disk, for live trading it connects to a stream and generates the data objects.
> Connect and download the data required for the algorithmic trading engine. For backtesting this sources files from the disk, for live trading, it connects to a stream and generates the data objects.
- **Transaction Processing** (ITransactionHandler)
> Process new order requests; either using the fill models provided by the algorithm, or with an actual brokerage. Send the processed orders back to the algorithm's portfolio to be filled.
> Process new order requests; either using the fill models provided by the algorithm or with an actual brokerage. Send the processed orders back to the algorithm's portfolio to be filled.
- **Realtime Event Management** (IRealtimeHandler)
> Generate real time events - such as end of day events. Trigger callbacks to real time event handlers. For backtesting this is mocked-up an works on simulated time.
> Generate real-time events - such as the end of day events. Trigger callbacks to real-time event handlers. For backtesting, this is mocked-up a works on simulated time.
- **Algorithm State Setup** (ISetupHandler)
> Configure the algorithm cash, portfolio and data requested. Initialize all state parameters required.
@@ -117,6 +122,8 @@ If you get: "Error initializing task Fsc: Not registered task Fsc." -> `sudo apt
If you get: "XX not found" -> Make sure Nuget ran successfully, and re-run if neccessary.
If you get: "Confirm ... '.../QuantConnect.XX.csproj.*.props' is correct, and that the file exists on disk." -> Ensure that your installation path is free of [reserved characters](https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
If you get other errors that lead to the failure of your building, please refer to the commands in "DockerfileLeanFoundation" file for help.
- Run the compiled `exe` file:
@@ -157,13 +164,13 @@ The mailing list for the project can be found on [LEAN Forum][6]. Please use thi
## Contributors and Pull Requests ##
Contributions are warmly very welcomed but we ask you read the existing code to see how it is formatted, commented and ensure contributions match the existing style. All code submissions must include accompanying tests. Please see the [contributor guide lines][7].
Contributions are warmly very welcomed but we ask you to read the existing code to see how it is formatted, commented and ensure contributions match the existing style. All code submissions must include accompanying tests. Please see the [contributor guide lines][7].
All accepted pull requests will get a 2mo free Prime subscription on QuantConnect. Once your pull-request has been merged write to us at support@quantconnect.com with a link to your PR to claim your free live trading. QC <3 Open Source.
## Acknowledgements ##
The open sourcing of QuantConnect would not have been possible without the support of the Pioneers. The Pioneers formed the core 100 early adopters of QuantConnect who subscribed and allowed us to launch the project into open source.
The open-sourcing of QuantConnect would not have been possible without the support of the Pioneers. The Pioneers formed the core 100 early adopters of QuantConnect who subscribed and allowed us to launch the project into open source.
Ryan H, Pravin B, Jimmie B, Nick C, Sam C, Mattias S, Michael H, Mark M, Madhan, Paul R, Nik M, Scott Y, BinaryExecutor.com, Tadas T, Matt B, Binumon P, Zyron, Mike O, TC, Luigi, Lester Z, Andreas H, Eugene K, Hugo P, Robert N, Christofer O, Ramesh L, Nicholas S, Jonathan E, Marc R, Raghav N, Marcus, Hakan D, Sergey M, Peter McE, Jim M, INTJCapital.com, Richard E, Dominik, John L, H. Orlandella, Stephen L, Risto K, E.Subasi, Peter W, Hui Z, Ross F, Archibald112, MooMooForex.com, Jae S, Eric S, Marco D, Jerome B, James B. Crocker, David Lypka, Edward T, Charlie Guse, Thomas D, Jordan I, Mark S, Bengt K, Marc D, Al C, Jan W, Ero C, Eranmn, Mitchell S, Helmuth V, Michael M, Jeremy P, PVS78, Ross D, Sergey K, John Grover, Fahiz Y, George L.Z., Craig E, Sean S, Brad G, Dennis H, Camila C, Egor U, David T, Cameron W, Napoleon Hernandez, Keeshen A, Daniel E, Daniel H, M.Patterson, Asen K, Virgil J, Balazs Trader, Stan L, Con L, Will D, Scott K, Barry K, Pawel D, S Ray, Richard C, Peter L, Thomas L., Wang H, Oliver Lee, Christian L.