14 changed files with 265 additions and 140 deletions
@ -0,0 +1,20 @@ |
|||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using SDKAdapter.Model; |
||||
|
using System; |
||||
|
using Yitter.IdGenerator; |
||||
|
|
||||
|
namespace Binance.TradeRobot.Business |
||||
|
{ |
||||
|
public class BaseSpotOrderPublishTradeBusiness : BaseBusiness |
||||
|
{ |
||||
|
public BaseSpotOrderPublishTradeBusiness(IFreeSql fsql, |
||||
|
NLogManager logManager, |
||||
|
IIdGenerator idGenerator, |
||||
|
IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache) { } |
||||
|
|
||||
|
public virtual void OnSpotOrderPublish(SpotOrderTradePublishInfo spotOrderTradePublishInfo) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Binance.TradeRobot.Business.Business.OrderPublishBusiness.Spot |
||||
|
{ |
||||
|
internal class D21OrderPublishTradeBusiness |
||||
|
{ |
||||
|
} |
||||
|
} |
@ -0,0 +1,183 @@ |
|||||
|
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; |
||||
|
|
||||
|
public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType, |
||||
|
long accountId, |
||||
|
string apiKey, |
||||
|
string secret, |
||||
|
NLog.ILogger logger, |
||||
|
Action<SpotOrderTradePublishInfo> 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.Insurance, |
||||
|
Binance.Net.Enums.OrderStatus.Adl |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public override void Start(string symbol = "") |
||||
|
{ |
||||
|
if (IsConnected) |
||||
|
return; |
||||
|
IsConnected = true; |
||||
|
cancellationTokenSource = new CancellationTokenSource(); |
||||
|
var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(symbol).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 SpotOrderTradePublishInfo() |
||||
|
{ |
||||
|
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, |
||||
|
CreateTime = e.Data.CreateTime |
||||
|
}); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
logger.Error(ex); |
||||
|
} |
||||
|
}, |
||||
|
(e) => |
||||
|
{ |
||||
|
|
||||
|
}, |
||||
|
(e) => |
||||
|
{ |
||||
|
|
||||
|
}, |
||||
|
(e) => |
||||
|
{ |
||||
|
|
||||
|
}, |
||||
|
cancellationTokenSource.Token); |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,112 +0,0 @@ |
|||||
using Binance.Net.Clients; |
|
||||
using Binance.Net.Objects; |
|
||||
using Binance.TradeRobot.Model.Base; |
|
||||
using CryptoExchange.Net.Authentication; |
|
||||
using Newtonsoft.Json; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading; |
|
||||
|
|
||||
namespace SDKAdapter.WebSockets.Order.SpotOrder |
|
||||
{ |
|
||||
public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient |
|
||||
{ |
|
||||
private BinanceSocketClient binanceSocketClient; |
|
||||
private BinanceClient binanceClient; |
|
||||
private CancellationTokenSource cancellationTokenSource; |
|
||||
private string listenKey; |
|
||||
private IList<Binance.Net.Enums.OrderStatus> unSupportStateList; |
|
||||
|
|
||||
public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger) |
|
||||
: base(businessType, accountId, apiKey, secret, logger) |
|
||||
{ |
|
||||
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; |
|
||||
unSupportStateList = new List<Binance.Net.Enums.OrderStatus>() |
|
||||
{ |
|
||||
Binance.Net.Enums.OrderStatus.PendingCancel, |
|
||||
Binance.Net.Enums.OrderStatus.Insurance, |
|
||||
Binance.Net.Enums.OrderStatus.Adl |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public override void Start(string symbol = "") |
|
||||
{ |
|
||||
if (IsConnected) |
|
||||
return; |
|
||||
IsConnected = true; |
|
||||
cancellationTokenSource = new CancellationTokenSource(); |
|
||||
var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(symbol).Result; |
|
||||
if (!getListenKeyResponse.Success) |
|
||||
throw new Exception(getListenKeyResponse.Error?.Message ?? ""); |
|
||||
listenKey = getListenKeyResponse.Data; |
|
||||
binanceSocketClient.SpotStreams.SubscribeToUserDataUpdatesAsync(listenKey, |
|
||||
(e) => |
|
||||
{ |
|
||||
logger.Info(JsonConvert.SerializeObject(e.Data)); |
|
||||
if (unSupportStateList.Contains(e.Data.Status)) |
|
||||
return; |
|
||||
OnOrderUpdated?.Invoke(new Model.SpotOrderTradePublishInfo() |
|
||||
{ |
|
||||
OrderId = e.Data.Id, |
|
||||
Symbol = e.Data.Symbol, |
|
||||
AccountId = this.AccountId, |
|
||||
OrderType = (Enums.OrderType)(int)e.Data.Type, |
|
||||
SpotOrderState = (Enums.SpotOrderState)(int)e.Data.Status, |
|
||||
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, |
|
||||
CreateTime = e.Data.CreateTime |
|
||||
}); |
|
||||
}, |
|
||||
(e) => |
|
||||
{ |
|
||||
|
|
||||
}, |
|
||||
(e) => |
|
||||
{ |
|
||||
|
|
||||
}, |
|
||||
(e) => |
|
||||
{ |
|
||||
|
|
||||
}, |
|
||||
cancellationTokenSource.Token); |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
Loading…
Reference in new issue