Files
quantconnect--lean/Messaging/Messaging.cs
T
Martin-Molinero c4a2d6eef4
Regression Tests / build (push) Has been cancelled
Build & Test Lean / build (push) Has been cancelled
Crypto base currency fees handled correctly (#6166)
* Binance fees deducted from fill quantity accordengly

- For Binance cash accounts while buying, if fees are from the base
  currency of leans virtual position, we need to deduct the fee from the
  fill quantity, else we can end with a position bigger that it actually
  is and not be able to liquidate

* Refactor solution

- Refactor solution into a more generic approach solving fees in base
  currency at the BrokerageTransactionHandler level, covering all
  brokerages that require it. Adding regression algorithm reproducing
  issue.
- Update Bitfinex and Binance fee models to correctly reflact reality

* Log fill quantity adjusment once
2022-01-28 13:02:58 -03:00

224 lines
8.3 KiB
C#

/*
* 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.Globalization;
using System.IO;
using System.Linq;
using QuantConnect.Configuration;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Notifications;
using QuantConnect.Orders.Serialization;
using QuantConnect.Packets;
using QuantConnect.Util;
namespace QuantConnect.Messaging
{
/// <summary>
/// Local/desktop implementation of messaging system for Lean Engine.
/// </summary>
public class Messaging : IMessagingHandler
{
private static readonly bool UpdateRegressionStatistics = Config.GetBool("regression-update-statistics", false);
private AlgorithmNodePacket _job;
private OrderEventJsonConverter _orderEventJsonConverter;
/// <summary>
/// This implementation ignores the <seealso cref="HasSubscribers"/> flag and
/// instead will always write to the log.
/// </summary>
public bool HasSubscribers
{
get;
set;
}
/// <summary>
/// Initialize the messaging system
/// </summary>
public void Initialize()
{
//
}
/// <summary>
/// Set the messaging channel
/// </summary>
public void SetAuthentication(AlgorithmNodePacket job)
{
_job = job;
_orderEventJsonConverter = new OrderEventJsonConverter(job.AlgorithmId);
}
/// <summary>
/// Send a generic base packet without processing
/// </summary>
public void Send(Packet packet)
{
switch (packet.Type)
{
case PacketType.Debug:
var debug = (DebugPacket) packet;
Log.Trace("Debug: " + debug.Message);
break;
case PacketType.SystemDebug:
var systemDebug = (SystemDebugPacket)packet;
Log.Trace("Debug: " + systemDebug.Message);
break;
case PacketType.Log:
var log = (LogPacket) packet;
Log.Trace("Log: " + log.Message);
break;
case PacketType.RuntimeError:
var runtime = (RuntimeErrorPacket) packet;
var rstack = (!string.IsNullOrEmpty(runtime.StackTrace) ? (Environment.NewLine + " " + runtime.StackTrace) : string.Empty);
Log.Error(runtime.Message + rstack);
break;
case PacketType.HandledError:
var handled = (HandledErrorPacket) packet;
var hstack = (!string.IsNullOrEmpty(handled.StackTrace) ? (Environment.NewLine + " " + handled.StackTrace) : string.Empty);
Log.Error(handled.Message + hstack);
break;
case PacketType.AlphaResult:
// spams the logs
//var insights = ((AlphaResultPacket) packet).Insights;
//foreach (var insight in insights)
//{
// Log.Trace("Insight: " + insight);
//}
break;
case PacketType.BacktestResult:
var result = (BacktestResultPacket) packet;
if (result.Progress == 1)
{
// inject alpha statistics into backtesting result statistics
// this is primarily so we can easily regression test these values
var alphaStatistics = result.Results.AlphaRuntimeStatistics?.ToDictionary() ?? Enumerable.Empty<KeyValuePair<string, string>>();
foreach (var kvp in alphaStatistics)
{
result.Results.Statistics.Add(kvp);
}
var orderHash = result.Results.Orders.GetHash();
result.Results.Statistics.Add("OrderListHash", orderHash);
if (UpdateRegressionStatistics && _job.Language == Language.CSharp)
{
UpdateRegressionStatisticsInSourceFile(result);
}
var statisticsStr = $"{Environment.NewLine}" +
$"{string.Join(Environment.NewLine,result.Results.Statistics.Select(x => $"STATISTICS:: {x.Key} {x.Value}"))}";
Log.Trace(statisticsStr);
}
break;
}
}
/// <summary>
/// Send any notification with a base type of Notification.
/// </summary>
public void SendNotification(Notification notification)
{
var type = notification.GetType();
if (type == typeof (NotificationEmail)
|| type == typeof (NotificationWeb)
|| type == typeof (NotificationSms)
|| type == typeof(NotificationTelegram))
{
Log.Error("Messaging.SendNotification(): Send not implemented for notification of type: " + type.Name);
return;
}
notification.Send();
}
private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result)
{
if (!result.Results.Statistics.Any())
{
Log.Error("Messaging.UpdateRegressionStatisticsInSourceFile(): No statistics generated. Skipping update.");
return;
}
var algorithmSource = Directory.EnumerateFiles("../../../Algorithm.CSharp", $"{_job.AlgorithmId}.cs", SearchOption.AllDirectories).SingleOrDefault();
if (algorithmSource == null)
{
algorithmSource = Directory.EnumerateFiles("../../../Algorithm.CSharp", $"*{_job.AlgorithmId}.cs", SearchOption.AllDirectories).Single();
}
var file = File.ReadAllLines(algorithmSource).ToList().GetEnumerator();
var lines = new List<string>();
while (file.MoveNext())
{
var line = file.Current;
if (line == null)
{
continue;
}
if (line.Contains("Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>")
|| line.Contains("Dictionary<string, string> ExpectedStatistics => new()"))
{
lines.Add(line);
lines.Add(" {");
foreach (var pair in result.Results.Statistics)
{
lines.Add($" {{\"{pair.Key}\", \"{pair.Value}\"}},");
}
// remove trailing comma
var lastLine = lines[lines.Count - 1];
lines[lines.Count - 1] = lastLine.Substring(0, lastLine.Length - 1);
// now we skip existing expected statistics in file
while (file.MoveNext())
{
line = file.Current;
if (line != null && line.Contains("};"))
{
lines.Add(line);
break;
}
}
}
else
{
lines.Add(line);
}
}
file.DisposeSafely();
File.WriteAllLines(algorithmSource, lines);
}
/// <summary>
/// Dispose of any resources
/// </summary>
public void Dispose()
{
}
}
}