币安量化交易
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
9.3 KiB

using Binance.Net.Clients;
using Binance.Net.Objects;
using Binance.TradeRobot.Model.Base;
using CryptoExchange.Net.Authentication;
using Newtonsoft.Json;
using SDKAdapter.Model;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace SDKAdapter.WebSockets.Order.Spot
{
public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient
{
private BinanceSocketClient binanceSocketClient;
private BinanceClient binanceClient;
private CancellationTokenSource cancellationTokenSource;
private string listenKey;
private IList<Binance.Net.Enums.OrderStatus> ignoreOrderStateList;
private System.Threading.Timer timer;
private readonly long extendListenKeyPeriod = 1000 * 60 * 30; //30分钟
private string isolateMarginSymbol = ""; //逐仓杠杆专用
public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType,
long accountId,
string apiKey,
string secret,
NLog.ILogger logger,
Action<SpotOrderPublishInfo> onOrderUpdated) : base(businessType,
accountId,
apiKey,
secret,
logger,
onOrderUpdated)
{
var spotClientOption = new BinanceApiClientOptions()
{
BaseAddress = "https://api.binance.com",
ApiCredentials = new ApiCredentials(apiKey, secret)
};
//var usdFuturesClientOption = new BinanceApiClientOptions()
//{
// BaseAddress = "https://fapi.binance.com",
// ApiCredentials = new ApiCredentials(apiKey, secret)
//};
binanceClient = new BinanceClient(new BinanceClientOptions()
{
//UsdFuturesApiOptions = usdFuturesClientOption,
SpotApiOptions = spotClientOption
});
binanceSocketClient = new BinanceSocketClient();
listenKey = string.Empty;
ignoreOrderStateList = new List<Binance.Net.Enums.OrderStatus>()
{
Binance.Net.Enums.OrderStatus.New,
Binance.Net.Enums.OrderStatus.PendingCancel,
//Binance.Net.Enums.OrderStatus.PartiallyFilled, //由于无法确定手续费时候存在局部,暂时忽略局部成交
Binance.Net.Enums.OrderStatus.Insurance,
Binance.Net.Enums.OrderStatus.Adl
};
timer = new Timer(new TimerCallback(ExtendListenKey), null, -1, extendListenKeyPeriod);
}
public override void Start(string symbol = "")
{
if (IsConnected)
return;
IsConnected = true;
isolateMarginSymbol = symbol;
cancellationTokenSource = new CancellationTokenSource();
var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(isolateMarginSymbol).Result;
if (!getListenKeyResponse.Success)
throw new Exception(getListenKeyResponse.Error?.Message ?? "");
listenKey = getListenKeyResponse.Data;
_ = binanceSocketClient.SpotStreams.SubscribeToUserDataUpdatesAsync(listenKey,
(e) =>
{
try
{
var originData = new StringBuilder();
var isStop = false;
if (ignoreOrderStateList.Contains(e.Data.Status))
{
originData.Append($"不支持的订单状态{e.Data.Status}");
isStop = true;
}
else if (string.IsNullOrEmpty(e.Data.ClientOrderId))
{
originData.Append($"缺少ClientOrderId");
isStop = true;
}
long? robotId = null;
Enums.TradePolicy? tradePolicy = null;
if (!isStop)
{
var match = Regex.Match(e.Data.ClientOrderId, @"^([a-z0-9]{5})_(\d{15,})_(\d{1,2})$");
if (!match.Success)
{
originData.Append($"非机器人交易的ClientOrderId");
isStop = true;
}
else
{
robotId = long.Parse(match.Groups[2].Value);
tradePolicy = (Enums.TradePolicy)int.Parse(match.Groups[3].Value);
}
}
originData.Append($" {JsonConvert.SerializeObject(e.Data)}");
logger.Info(originData);
if (isStop)
return;
var orderState = Enums.SpotOrderState.Unknow;
switch (e.Data.Status)
{
case Binance.Net.Enums.OrderStatus.PartiallyFilled:
case Binance.Net.Enums.OrderStatus.Filled:
case Binance.Net.Enums.OrderStatus.Canceled:
orderState = (Enums.SpotOrderState)(int)e.Data.Status;
break;
case Binance.Net.Enums.OrderStatus.Rejected:
orderState = Enums.SpotOrderState.Rejected;
break;
case Binance.Net.Enums.OrderStatus.Expired:
orderState = Enums.SpotOrderState.Expired;
break;
}
OnOrderUpdated?.Invoke(new SpotOrderPublishInfo()
{
OrderId = e.Data.Id,
RobotId = robotId.Value,
TradePolicy = tradePolicy.Value,
Symbol = e.Data.Symbol,
AccountId = this.AccountId,
OrderType = (Enums.OrderType)(int)e.Data.Type,
SpotOrderState = orderState,
TradeDirection = (Enums.TradeDirection)(int)e.Data.Side,
ClientOrderId = e.Data.ClientOrderId,
CummulativeTradeAmount = e.Data.QuoteQuantityFilled,
CummulativeTradeQuantity = e.Data.QuantityFilled,
Exchange = Enums.Exchange.Binance,
Fee = e.Data.Fee,
FeeUnit = e.Data.FeeAsset,
LastTradeAmount = e.Data.LastQuoteQuantity,
LastTradePrice = e.Data.LastPriceFilled,
LastTradeQuantity = e.Data.LastQuantityFilled,
LastTradeTime = e.Data.UpdateTime.AddHours(8),
CreateTime = e.Data.CreateTime,
LoggerName = logger.Name,
RejectedReason = e.Data.RejectReason.ToString()
});
}
catch (Exception ex)
{
logger.Error(ex);
}
},
(e) =>
{
},
(e) =>
{
},
(e) =>
{
},
cancellationTokenSource.Token);
try
{
timer.Change(extendListenKeyPeriod, extendListenKeyPeriod);
}
catch { }
}
public override void Stop(string symbol = "")
{
if (!IsConnected)
return;
IsConnected = false;
cancellationTokenSource.Cancel();
binanceSocketClient.SpotStreams.Dispose();
cancellationTokenSource = null;
_ = binanceClient.SpotApi.Account.CloseIsolatedMarginUserStreamAsync(symbol, listenKey).Result;
listenKey = string.Empty;
try
{
timer.Change(-1, extendListenKeyPeriod);
}
catch { }
}
private void ExtendListenKey(object? o)
{
if (string.IsNullOrEmpty(listenKey))
return;
if (BusinessType == Enums.BusinessType.IsolateMargin)
_ = binanceClient.SpotApi.Account.KeepAliveIsolatedMarginUserStreamAsync(isolateMarginSymbol, listenKey);
}
}
}