diff --git a/Binance.TradeRobot.API/Startup.cs b/Binance.TradeRobot.API/Startup.cs index 46d507d..1238fa8 100644 --- a/Binance.TradeRobot.API/Startup.cs +++ b/Binance.TradeRobot.API/Startup.cs @@ -95,7 +95,7 @@ namespace Binance.TradeRobot.API } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RobotBusiness robotBusiness, GlobalContext globalContext) { app.UseSwagger(c => c.SerializeAsV2 = true) .UseSwaggerUI(c => @@ -117,7 +117,12 @@ namespace Binance.TradeRobot.API endpoints.MapControllers(); }); - //websocket + var robotList = robotBusiness.GetRobotList(robotState: Enums.RobotState.Runing); + foreach (var robot in robotList) + { + globalContext.SubscribeKLine(robot); + globalContext.SubscribeOrderPublish(robot); + } } } } diff --git a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj index 2050c2c..2f34003 100644 --- a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj +++ b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj @@ -8,6 +8,7 @@ + @@ -20,6 +21,7 @@ + diff --git a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml index e83cd9e..2daae90 100644 --- a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml +++ b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml @@ -40,6 +40,16 @@ + + + 查询机器人基本信息 + + 如果robotId有值,将忽略其他条件 + + + + + 获取动2.1策略机器人列表 @@ -81,5 +91,29 @@ 比例乘100 + + + 订阅K线 + + + + + + 订阅订单推送 + + + + + + 取消订阅K线 + + + + + + 取消订阅订单推送 + + + diff --git a/Binance.TradeRobot.Business/Business/RobotBusiness.cs b/Binance.TradeRobot.Business/Business/RobotBusiness.cs index 9602550..186baa2 100644 --- a/Binance.TradeRobot.Business/Business/RobotBusiness.cs +++ b/Binance.TradeRobot.Business/Business/RobotBusiness.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using Yitter.IdGenerator; namespace Binance.TradeRobot.Business @@ -16,9 +17,11 @@ namespace Binance.TradeRobot.Business [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)] public class RobotBusiness : BaseBusiness { - public RobotBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache) - { + private GlobalContext globalContext; + public RobotBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache, GlobalContext globalContext) : base(fsql, logManager, idGenerator, memoryCache) + { + this.globalContext = globalContext; } /// @@ -49,15 +52,23 @@ namespace Binance.TradeRobot.Business public void StartRobot(long robotId) { fsql.Update(robotId).Set(r => r.State, Enums.RobotState.Runing).ExecuteAffrows(); - //监听K线和订单 + var robot = GetRobotList(robotId).FirstOrDefault(); - } + //监听K线和订单 + globalContext.SubscribeKLine(robot); + } public void StopRobot(long robotId) { + var robot = GetRobotList(robotId).FirstOrDefault(); fsql.Update(robotId).Set(r => r.State, Enums.RobotState.Stop).ExecuteAffrows(); - //取消监听K线和订单 + var sameRunningCount = GetRobotList(symbol: robot.Symbol, robotState: Enums.RobotState.Runing, exchange: robot.ExchangeId).Count(); + if (sameRunningCount == 0) + { + //取消监听K线和订单 + globalContext.UnSubscribeKLine(robot); + } } /// @@ -135,6 +146,44 @@ namespace Binance.TradeRobot.Business }); } + /// + /// 查询机器人基本信息 + /// + /// 如果robotId有值,将忽略其他条件 + /// + /// + /// + /// + public IList GetRobotList(long? robotId = null, + string symbol = "", + Enums.RobotState? robotState = null, + Enums.Exchange? exchange = null) + { + var select = fsql.Select().InnerJoin((r, e) => r.Id == e.RobotId); + if (robotId != null) + select = select.Where((r, e) => r.Id == robotId); + else + select = select.WhereIf(!string.IsNullOrEmpty(symbol), (r, e) => r.Symbol == symbol) + .WhereIf(robotState != null, (r, e) => r.State == robotState) + .WhereIf(exchange != null, (r, e) => r.ExchangeId == exchange); + + return select.ToList((r, e) => new Robot() + { + Id = r.Id, + BusinessType = r.BusinessType, + ExchangeId = r.ExchangeId, + Symbol = r.Symbol, + State = r.State, + RunningTime = r.RunningTime, + CreateTime = r.CreateTime, + TradePolicy = r.TradePolicy, + + ExchangeAccountId = e.AccountId, + ExchangeAPIKey = e.APIKey, + ExchangeSecretKey = e.SecretKey + }).Map>(); + } + /// /// 获取动2.1策略机器人列表 /// diff --git a/Binance.TradeRobot.Business/GlobalContext.cs b/Binance.TradeRobot.Business/GlobalContext.cs index 5c66ea7..fbcec25 100644 --- a/Binance.TradeRobot.Business/GlobalContext.cs +++ b/Binance.TradeRobot.Business/GlobalContext.cs @@ -1,14 +1,66 @@ using Binance.TradeRobot.Common.DI; +using Binance.TradeRobot.Model.Dto; using Microsoft.Extensions.DependencyInjection; +using SDKAdapter.WebSockets.Market; +using System.Collections.Generic; namespace Binance.TradeRobot.Business { [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)] public class GlobalContext { - public GlobalContext() - { - + private NLogManager logManager; + private IDictionary spotMarketWebSocketClientDictionary; + + public GlobalContext(NLogManager logManager) + { + this.logManager = logManager; + spotMarketWebSocketClientDictionary = new Dictionary(); + } + + /// + /// 订阅K线 + /// + /// + public void SubscribeKLine(RobotResponse robot) + { + if (!spotMarketWebSocketClientDictionary.TryGetValue(robot.KLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) + { + var loggerName = $"SpotKLine-{robot.ExchangeId}-{robot.Symbol}"; + spotMarketWebSocketClient = SpotMarketWebSocketClient.Create(robot.ExchangeId, robot.Symbol, logManager.GetLogger(loggerName)); + spotMarketWebSocketClientDictionary.TryAdd(robot.KLineKey, spotMarketWebSocketClient); + } + if (!spotMarketWebSocketClient.IsConnected) + spotMarketWebSocketClient.Start(); + } + + /// + /// 订阅订单推送 + /// + /// + public void SubscribeOrderPublish(RobotResponse robot) + { + + } + + /// + /// 取消订阅K线 + /// + /// + public void UnSubscribeKLine(RobotResponse robot) + { + //停止订阅k线 + if (spotMarketWebSocketClientDictionary.TryGetValue(robot.KLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) + spotMarketWebSocketClient.Stop(); + } + + /// + /// 取消订阅订单推送 + /// + /// + public void UnSubscribeOrderPublish(RobotResponse robot) + { + } } } diff --git a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs index 29d80ad..96618d0 100644 --- a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs +++ b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs @@ -22,6 +22,10 @@ namespace Binance.TradeRobot.Model.Dto public Enums.Exchange ExchangeId { get; set; } + public string ExecuteLogKey { get { return $"Execute-{ExchangeId}-{TradePolicy}-{Symbol}"; } } + + public string KLineKey { get { return $"KLine-{ExchangeId}-{Symbol}"; }} + /// /// 机器人账户对象 /// diff --git a/SDKAdapter/SDKAdapter.csproj b/SDKAdapter/SDKAdapter.csproj index ca327b0..8f07c50 100644 --- a/SDKAdapter/SDKAdapter.csproj +++ b/SDKAdapter/SDKAdapter.csproj @@ -10,7 +10,12 @@ + + + + + diff --git a/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs b/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs new file mode 100644 index 0000000..687c518 --- /dev/null +++ b/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs @@ -0,0 +1,36 @@ +using Binance.Net.Clients; +using Binance.Net.Enums; +using System.Threading; + +namespace SDKAdapter.WebSockets.Market +{ + public class BinanceSpotMarketWebSocketClient : SpotMarketWebSocketClient + { + private BinanceSocketClient client; + private CancellationTokenSource cancellationTokenSource; + + public BinanceSpotMarketWebSocketClient(string symbol, NLog.ILogger logger) : base(symbol, logger) + { + client = new BinanceSocketClient(); + + } + + public override void Start() + { + cancellationTokenSource = new CancellationTokenSource(); + client.SpotStreams.SubscribeToKlineUpdatesAsync(Symbol, KlineInterval.OneMinute, (e) => + { + base.OnReceived(e.Data.Data.ClosePrice); + }, cancellationTokenSource.Token); + base.Start(); + } + + public override void Stop() + { + cancellationTokenSource.Cancel(); + client.SpotStreams.Dispose(); + base.Stop(); + cancellationTokenSource = null; + } + } +} diff --git a/SDKAdapter/WebSockets/Market/GateIOSpotMarketWebSocketClient.cs b/SDKAdapter/WebSockets/Market/GateIOSpotMarketWebSocketClient.cs new file mode 100644 index 0000000..497bb14 --- /dev/null +++ b/SDKAdapter/WebSockets/Market/GateIOSpotMarketWebSocketClient.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SDKAdapter.WebSockets.Market +{ + public class GateIOSpotMarketWebSocketClient : SpotMarketWebSocketClient + { + public GateIOSpotMarketWebSocketClient(string symbol, NLog.ILogger logger) : base(symbol, logger) + { + + } + } +} diff --git a/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs b/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs index b33a71b..8c275d4 100644 --- a/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs +++ b/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Binance.TradeRobot.Model.Base; +using System; namespace SDKAdapter.WebSockets.Market { @@ -19,7 +18,7 @@ namespace SDKAdapter.WebSockets.Market /// /// 最新成交价 /// - public decimal NewestPrice { get; private set; } + public decimal NewestPrice { get; protected set; } /// r /// 上一次价格更新时间 @@ -28,6 +27,8 @@ namespace SDKAdapter.WebSockets.Market public NLog.ILogger logger { get; private set; } + public bool IsConnected { get; protected set; } + public SpotMarketWebSocketClient(string symbol, NLog.ILogger logger) { this.Symbol = symbol; @@ -36,12 +37,12 @@ namespace SDKAdapter.WebSockets.Market public virtual void Start() { - + IsConnected = true; } public virtual void Stop() { - + IsConnected = false; } protected virtual void OnReceived(decimal newestPrice) @@ -53,5 +54,12 @@ namespace SDKAdapter.WebSockets.Market LastUpdateTime = DateTime.Now; } } + + public static SpotMarketWebSocketClient Create(Enums.Exchange exchange, string symbol, NLog.ILogger logger) + { + if (exchange == Enums.Exchange.Binance) + return new BinanceSpotMarketWebSocketClient(symbol, logger); + return null; + } } }