diff --git a/.gitignore b/.gitignore index 96374c4..dfcfd56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,350 @@ -# Windows image file caches -Thumbs.db -ehthumbs.db +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore -# Folder config file -Desktop.ini +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates -# Recycle Bin used on file shares -$RECYCLE.BIN/ +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs -# Windows Installer files -*.cab -*.msi -*.msm -*.msp +# Mono auto generated files +mono_crash.* -# Windows shortcuts -*.lnk +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ -# ========================= -# Operating System Files -# ========================= +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ -# OSX -# ========================= +# Visual Studio 2017 auto generated files +Generated\ Files/ -.DS_Store -.AppleDouble -.LSOverride +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* -# Thumbnails -._* +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml -# Files that might appear on external disk -.Spotlight-V100 -.Trashes +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/QuantBox.API.Provider/QuantBox.APIProvider.csproj b/QuantBox.API.Provider/QuantBox.APIProvider.csproj deleted file mode 100644 index 998a013..0000000 --- a/QuantBox.API.Provider/QuantBox.APIProvider.csproj +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Debug - AnyCPU - {E86E1057-B344-4805-A836-43606154677E} - Library - Properties - QuantBox.APIProvider - QuantBox.APIProvider - v4.5.1 - 512 - - - - true - full - false - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NLog.4.3.9\lib\net45\NLog.dll - True - - - False - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\SmartQuant.dll - False - - - - - - - - - - - - - - - - - - False - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\XAPI_CSharp.exe - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Form - - - ApiControlForm.cs - - - Form - - - ApiManagerForm.cs - - - - - - - - - - - - Designer - - - - - - - - ApiControlForm.cs - - - ApiManagerForm.cs - - - - - {d8d82e27-47f5-4579-92ad-416da86ad601} - QuantBox.Extensions - - - - - \ No newline at end of file diff --git a/QuantBox.API.Provider/QuantBox.APIProvider.csproj.user b/QuantBox.API.Provider/QuantBox.APIProvider.csproj.user deleted file mode 100644 index ee82c1f..0000000 --- a/QuantBox.API.Provider/QuantBox.APIProvider.csproj.user +++ /dev/null @@ -1,7 +0,0 @@ - - - - Program - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\OpenQuant.x86.exe - - \ No newline at end of file diff --git a/QuantBox.API.Provider/QuantBox.APIProvider_Linux.csproj b/QuantBox.API.Provider/QuantBox.APIProvider_Linux.csproj deleted file mode 100644 index 58a1aa7..0000000 --- a/QuantBox.API.Provider/QuantBox.APIProvider_Linux.csproj +++ /dev/null @@ -1,95 +0,0 @@ - - - - - Debug - AnyCPU - {E86E1057-B344-4805-A836-43606154677E} - Library - Properties - QuantBox.APIProvider - QuantBox.APIProvider - v4.5.1 - 512 - - - - true - full - false - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.6.0.5\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NLog.3.1.0.0\lib\net45\NLog.dll - - - ..\..\bin\SmartQuant.dll - - - ..\..\bin\QuantBox.XAPI.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {D8D82E27-47F5-4579-92AD-416DA86AD601} - QuantBox.Extensions_Linux - - - \ No newline at end of file diff --git a/QuantBox.API.Provider/Single/ApiItem.cs b/QuantBox.API.Provider/Single/ApiItem.cs deleted file mode 100644 index 5e75528..0000000 --- a/QuantBox.API.Provider/Single/ApiItem.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Newtonsoft.Json; -using QuantBox.APIProvider.UI; -using XAPI.Callback; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.Design; -using System.Drawing.Design; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using XAPI; - -namespace QuantBox.APIProvider.Single -{ - /// - /// 由用户选择Dll,然后加载,得到 - /// - /// - public class ApiItem : ICloneable - { - public const string CATEGORY_INFO = "Information"; - public const string CATEGORY_Type = "Type"; - - internal BindingList LinkedUserList; - internal BindingList LinkedServerList; - - private string _dllPath; - - [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))] - public string DllPath - { - get { - if (ProviderHost.autoMakeRelativePath) - { - return PathHelper.MakeRelativePath(_dllPath); - } - else - { - return PathHelper.MakeAbsolutePath(_dllPath); - } - } - set - { - string tmp_dllPath; - if (ProviderHost.autoMakeRelativePath) - { - tmp_dllPath = PathHelper.MakeRelativePath(value); - } - else - { - tmp_dllPath = PathHelper.MakeAbsolutePath(value); - } - - // 不一样 - bool diff = _dllPath != tmp_dllPath; - _dllPath = tmp_dllPath; - - - // 这个地方导致Json无法还原,所以先判断是否需要进行调用 - if (LinkedUserList != null) { - if (Api == null || diff) - { - Api = new XApi(_dllPath); - } - if (Api != null) { - try - { - Type = Api.GetApiTypes; - Name = Api.GetApiName; - Version = Api.GetApiVersion; - - // 取公共部分 - UseType = UseType & Type; - } - catch(Exception ex) - { - Api = null; - Type = ApiType.Nono; - Name = ex.Message; - Version = "请使用depends检查一下是否缺少依赖"; - UseType = ApiType.Nono; - } - } - } - } - } - - [Browsable(false)] - internal XApi Api { get; set; } - - [Category(CATEGORY_INFO)] - [ReadOnly(true)] - public string Name { get; set; } - - [Category(CATEGORY_INFO)] - [ReadOnly(true)] - public string Version { get; set; } - - [TypeConverter(typeof(UserItemConverter))] - public int User { get; set; } - [TypeConverter(typeof(ServerItemConverter))] - public int Server { get; set; } - - private BindingList userList = new BindingList(); - public BindingList UserList - { - get { return userList; } - set { userList = value; } - } - - public string LogPrefix { get; set; } - - - [Category(CATEGORY_Type)] - [ReadOnly(true)] - public ApiType Type { get; set; } - [Category(CATEGORY_Type)] - [Editor(typeof(ApiTypeSelectorEditor), typeof(UITypeEditor))] - public ApiType UseType { get; set; } - - - public override string ToString() - { - return string.Format("LogPrefix={0};UseType={1};Name={2}", LogPrefix, UseType, Name); - } - - public object Clone() - { - return this.MemberwiseClone(); - } - } -} diff --git a/QuantBox.API.Provider/Single/OrderMap.cs b/QuantBox.API.Provider/Single/OrderMap.cs deleted file mode 100644 index 434d470..0000000 --- a/QuantBox.API.Provider/Single/OrderMap.cs +++ /dev/null @@ -1,237 +0,0 @@ -using XAPI; -using SmartQuant; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using SQ = SmartQuant; - -namespace QuantBox.APIProvider.Single -{ - class OrderMap : BaseMap - { - private ConcurrentDictionary externalOrders; // 新单 - // LocalID到Order的映射 - private ConcurrentDictionary pendingOrders; // 新单 - // ID到Order - private Dictionary workingOrders; // 挂单 - - // Order.ID与LocalID或ID的映射,没有收到回报时是LocalID,收到后要更新为ID - private Dictionary orderIDs; // 撤单时映射 - private ConcurrentDictionary pendingCancels; // 撤单拒绝时使用 - private OrderRecord GetExternalOrder(ref TradeField field) - { - ExternalOrderRecord record; - if (externalOrders.TryGetValue(field.InstrumentID, out record)) { - if (field.OpenClose == OpenCloseType.Open) { - return field.Side == XAPI.OrderSide.Buy ? record.BuyOpen : record.SellOpen; - } - return field.Side == XAPI.OrderSide.Buy ? record.BuyClose : record.SellClose; - } - return null; - } - private void SetExternalOrder(Order order, ref OrderField field) - { - var orderRecord = new OrderRecord(order); - ExternalOrderRecord record = new ExternalOrderRecord(); - record = externalOrders.GetOrAdd(order.Instrument.Symbol, record); - if (field.OpenClose == OpenCloseType.Open) { - if (field.Side == XAPI.OrderSide.Buy) - { - record.BuyOpen = orderRecord; - } - else { - record.SellOpen = orderRecord; - } - } - else { - if (field.Side == XAPI.OrderSide.Buy) - { - record.BuyClose = orderRecord; - } - else { - record.SellClose = orderRecord; - } - } - } - public OrderMap(Framework framework, SingleProvider provider) - : base(framework, provider) - { - externalOrders = new ConcurrentDictionary(); - pendingOrders = new ConcurrentDictionary(); - workingOrders = new Dictionary(); - orderIDs = new Dictionary(); - pendingCancels = new ConcurrentDictionary(); - } - - public void Clear() - { - pendingOrders.Clear(); - workingOrders.Clear(); - orderIDs.Clear(); - pendingCancels.Clear(); - } - - public void DoOrderSend(ref OrderField[] ordersArray, Order order) - { - if (Convert.ToInt32(order.Qty) == int.MaxValue) { - SetExternalOrder(order, ref ordersArray[0]); - } - else { - DoOrderSend(ref ordersArray, new List() { order }); - } - } - - public void DoOrderSend(ref OrderField[] ordersArray, List ordersList) - { - // 这里其实返回的是LocalID - string outstr = provider._TdApi.SendOrder(ref ordersArray); - string[] OrderIds = outstr.Split(';'); - - int i = 0; - foreach (var orderId in OrderIds) - { - if (string.IsNullOrEmpty(orderId)) - { - // 直接将单子拒绝 - EmitExecutionReport(new OrderRecord(ordersList[i]), SQ.ExecType.ExecRejected, SQ.OrderStatus.Rejected, "ErrorCode:" + orderId); - } - else - { - this.pendingOrders.TryAdd(orderId, new OrderRecord(ordersList[i])); - // 记下了本地ID,用于立即撤单时供API来定位 - this.orderIDs.Add(ordersList[i].Id, orderId); - ordersList[i].Fields[9] = orderId; - } - ++i; - } - } - - public void DoOrderCancel(Order order) - { - DoOrderCancel(new List() { order }); - } - - public void DoOrderCancel(List ordersList) - { - OrderRecord[] recordList = new OrderRecord[ordersList.Count]; - string[] OrderIds = new string[ordersList.Count]; - - for (int i = 0; i < ordersList.Count; ++i) { - // 如果需要下单的过程中撤单,这里有可能返回LocalID或ID - if (orderIDs.TryGetValue(ordersList[i].Id, out OrderIds[i])) { - if (this.workingOrders.TryGetValue(OrderIds[i], out recordList[i])) { - pendingCancels[OrderIds[i]] = recordList[i]; - } - }else if (ordersList[i].Fields[9] != null) - { - OrderIds[i] = (string)ordersList[i].Fields[9]; - recordList[i] = new OrderRecord(ordersList[i]); - } - } - - string outstr = provider._TdApi.CancelOrder(OrderIds); - string[] errs = outstr.Split(';'); - - { - int i = 0; - foreach (var e in errs) - { - if (!string.IsNullOrEmpty(e) && e != "0") - { - EmitExecutionReport(recordList[i], SQ.ExecType.ExecCancelReject, recordList[i].Order.Status, "ErrorCode:" + e); - } - ++i; - } - } - } - - public void Process(ref OrderField order) - { - // 所有的成交信息都不处理,交给TradeField处理 - if (order.ExecType == XAPI.ExecType.Trade) - return; - - OrderRecord record; - - switch (order.ExecType) { - case XAPI.ExecType.New: - if (this.pendingOrders.TryRemove(order.LocalID, out record)) { - this.workingOrders.Add(order.ID, record); - // 将LocalID更新为ID - this.orderIDs[record.Order.Id] = order.ID; - EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status); - } - break; - case XAPI.ExecType.Rejected: - if (this.pendingOrders.TryRemove(order.LocalID, out record)) - { - orderIDs.Remove(record.Order.Id); - EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); - } - else if (this.workingOrders.TryGetValue(order.ID, out record)) { - // 比如说出现超出涨跌停时,先会到ProcessNew,所以得再多判断一次 - workingOrders.Remove(order.ID); - orderIDs.Remove(record.Order.Id); - EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); - } - break; - case XAPI.ExecType.Cancelled: - if (this.workingOrders.TryGetValue(order.ID, out record)) { - workingOrders.Remove(order.ID); - orderIDs.Remove(record.Order.Id); - EmitExecutionReport(record, SQ.ExecType.ExecCancelled, SQ.OrderStatus.Cancelled); - } - else if (this.pendingOrders.TryRemove(order.LocalID, out record)) - { - orderIDs.Remove(record.Order.Id); - EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); - } - break; - case XAPI.ExecType.PendingCancel: - if (this.workingOrders.TryGetValue(order.ID, out record)) { - EmitExecutionReport(record, SQ.ExecType.ExecPendingCancel, SQ.OrderStatus.PendingCancel); - } - break; - case XAPI.ExecType.CancelReject: - if (this.pendingCancels.TryRemove(order.ID, out record)) { - EmitExecutionReport(record, SQ.ExecType.ExecCancelReject, (SQ.OrderStatus)order.Status, order.Text()); - } - break; - } - } - - public void Process(ref TradeField trade) - { - OrderRecord record; - if (!workingOrders.TryGetValue(trade.ID, out record)) { - record = GetExternalOrder(ref trade); - } - if (record != null) { - record.AddFill(trade.Price, (int)trade.Qty); - SQ.ExecType execType = SQ.ExecType.ExecTrade; - SQ.OrderStatus orderStatus = (record.LeavesQty > 0) ? SQ.OrderStatus.PartiallyFilled : SQ.OrderStatus.Filled; - ExecutionReport report = CreateReport(record, execType, orderStatus); - report.LastPx = trade.Price; - report.LastQty = trade.Qty; - provider.EmitExecutionReport(report); - } - } - - public void ProcessNew(ref QuoteField quote, QuoteRecord record) - { - OrderRecord askRecord = new OrderRecord(record.AskOrder); - this.workingOrders.Add(quote.AskID, askRecord); - this.orderIDs.Add(askRecord.Order.Id, quote.AskID); - EmitExecutionReport(askRecord, (SQ.ExecType)quote.ExecType, (SQ.OrderStatus)quote.Status); - - OrderRecord bidRecord = new OrderRecord(record.BidOrder); - this.workingOrders.Add(quote.BidID, bidRecord); - this.orderIDs.Add(bidRecord.Order.Id, quote.BidID); - EmitExecutionReport(bidRecord, (SQ.ExecType)quote.ExecType, (SQ.OrderStatus)quote.Status); - } - } -} diff --git a/QuantBox.API.Provider/Single/ServerItem.cs b/QuantBox.API.Provider/Single/ServerItem.cs deleted file mode 100644 index ddc9a6d..0000000 --- a/QuantBox.API.Provider/Single/ServerItem.cs +++ /dev/null @@ -1,121 +0,0 @@ -using XAPI; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace QuantBox.APIProvider.Single - -{ - [DefaultProperty("Label")] - public class ServerItem : ICloneable - { - private const string OPEN_QUANT = "OpenQuant"; - [Category("标签")] - public string Label - { - get; - set; - } - /// - /// 订阅主题 - /// - [Category("行情 - Femas")] - [Description("Femas")] - public int TopicId { get; set; } - - /// - /// 流恢复 - /// - [Category("流重传方式")] - public ResumeType MarketDataTopicResumeType { get; set; } - [Category("流重传方式")] - public ResumeType PrivateTopicResumeType { get; set; } - [Category("流重传方式")] - public ResumeType PublicTopicResumeType { get; set; } - [Category("流重传方式")] - public ResumeType UserTopicResumeType { get; set; } - /// - /// 经纪公司代码 - /// - [Category("服务端信息")] - public string BrokerID { get; set; } - /// - /// 用户端产品信息 - /// - [Category("客户端认证 - CTP/LTS")] - public string UserProductInfo { get; set; } - /// - /// 认证码 - /// - [Category("客户端认证 - CTP/LTS")] - public string AuthCode { get; set; } - /// - /// 地址 - /// - [Category("服务端信息")] - public string Address { get; set; } - /// - /// 地址 - /// - [Category("扩展信息")] - public string ExtInfoChar128 { get; set; } - [Category("扩展信息")] - public string ConfigPath { get; set; } - /// - /// 端口号 - /// - [Category("服务端信息")] - public int Port { get; set; } - /// - /// UDP行情 - /// - [Category("行情")] - [Description("CTP/DFITC_Level2")] - public bool IsUsingUdp { get; set; } - /// - /// 多播行情 - /// - [Category("行情")] - [Description("CTP")] - public bool IsMulticast { get; set; } - - public ServerItem() - { - UserProductInfo = OPEN_QUANT; - } - - public ServerInfoField ToStruct() - { - ServerInfoField field; - field.IsUsingUdp = this.IsUsingUdp; - field.IsMulticast = this.IsMulticast; - field.TopicId = this.TopicId; - field.Port = this.Port; - field.MarketDataTopicResumeType = this.MarketDataTopicResumeType; - field.PrivateTopicResumeType = this.PrivateTopicResumeType; - field.PublicTopicResumeType = this.PublicTopicResumeType; - field.UserTopicResumeType = this.UserTopicResumeType; - field.BrokerID = this.BrokerID; - field.UserProductInfo = this.UserProductInfo; - field.AuthCode = this.AuthCode; - field.Address = this.Address; - field.ConfigPath = this.ConfigPath; - field.ExtInfoChar128 = this.ExtInfoChar128; - - return field; - } - - public override string ToString() - { - return string.Format("Label={0};BrokerID={1};Address={2}", this.Label, this.BrokerID, this.Address); - } - - public object Clone() - { - return this.MemberwiseClone(); - } - } -} diff --git a/QuantBox.API.Provider/Single/SessionTimeItem.cs b/QuantBox.API.Provider/Single/SessionTimeItem.cs deleted file mode 100644 index 521a066..0000000 --- a/QuantBox.API.Provider/Single/SessionTimeItem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using OrderedPropertyGrid; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace QuantBox.APIProvider.Single -{ - // 发现想让属性显示排序,但发现Json转换出问题了,所以还是决定不排序了 - //[TypeConverter(typeof(PropertySorter))] - public class SessionTimeItem - { - [PropertyOrder(1)] - public TimeSpan SessionStart { get; set; } - [PropertyOrder(2)] - public TimeSpan SessionEnd { get; set; } - //[PropertyOrder(3)] - //public bool Enable { get; set; } - - public override string ToString() - { - return string.Format("Start={0};End={1}", this.SessionStart, this.SessionEnd); - //if (Enable) - //{ - // return string.Format("[+]Start={0};End={1}", this.SessionStart, this.SessionEnd); - //} - //else - //{ - - // return string.Format("[-]Start={0};End={1}", this.SessionStart, this.SessionEnd); - //} - } - } -} diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.MarketData.cs b/QuantBox.API.Provider/Single/SingleProvider.API.MarketData.cs deleted file mode 100644 index 901ca92..0000000 --- a/QuantBox.API.Provider/Single/SingleProvider.API.MarketData.cs +++ /dev/null @@ -1,169 +0,0 @@ -using SmartQuant; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using XAPI.Callback; -using XAPI; -//using System.Threading.Tasks.Dataflow; -using QuantBox.Extensions; - -namespace QuantBox.APIProvider.Single -{ - public partial class SingleProvider - { - private DateTime _dateTime = DateTime.Now; - private DateTime _exchangeDateTime = DateTime.Now; - - private void OnRtnDepthMarketData_callback(object sender, ref DepthMarketDataNClass pDepthMarketData) - { - if (!_enableEmitData) - return; - - try - { - // 传过来的Symbol,有可能是不带点,有可能是带点的 - // 要同时存成完整版,因为下单时 - MarketDataRecord record; - if (!marketDataRecords.TryGetValue(pDepthMarketData.Symbol, out record)) - { - return; - } - - // 取出上次的行情记录 - DepthMarketDataNClass depthMarket = record.DepthMarket; - - //将更新字典的功能提前,因为如果一开始就OnTrade中下单,涨跌停没有更新 - record.DepthMarket = pDepthMarketData; - - _dateTime = DateTime.Now; - try - { - _exchangeDateTime = pDepthMarketData.ExchangeDateTime(); - } - catch - { - _exchangeDateTime = _dateTime; - (sender as XApi).GetLog().Error("{0} ExchangeDateTime有误,现使用LocalDateTime代替,请找API开发人员处理API中的时间兼容问题。", pDepthMarketData.ToFormattedStringExchangeDateTime()); - } - - - if (_emitBidAskFirst) - { - if (_emitBidAsk) - { - FireBid(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - FireAsk(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - } - FireTrade(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - } - else - { - FireTrade(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - if (_emitBidAsk) - { - FireBid(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - FireAsk(record.Instrument.Id, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); - } - } - } - catch (Exception ex) - { - (sender as XApi).GetLog().Error(ex); - } - } - - private void FireTrade(int InstrumentId,DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData,DepthMarketDataNClass DepthMarket) - { - //行情过来时是今天累计成交量,得转换成每个tick中成交量之差 - double volume = pDepthMarketData.Volume - DepthMarket.Volume; - // 以前第一条会导致集合竞价后的第一条没有成交量,这种方法就明确了上一笔是空数据 - if (0 == DepthMarket.TradingDay && 0 == DepthMarket.ActionDay) - { - //没有接收到最开始的一条,所以这计算每个Bar的数据时肯定超大,强行设置为0 - volume = 0; - } - else if (volume < 0) - { - //如果隔夜运行,会出现今早成交量0-昨收盘成交量,出现负数,所以当发现为负时要修改 - volume = pDepthMarketData.Volume; - } - - // 使用新的类,保存更多信息 - var trade = new TradeEx( - _dateTime, - _exchangeDateTime, - this.id, - InstrumentId, - pDepthMarketData.LastPrice, - (int) volume) {DepthMarketData = pDepthMarketData}; - - // 启用底层数据上传 - - EmitData(trade); - } - - private void FireBid(int InstrumentId, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) - { - do - { - if (pDepthMarketData.Bids == null || pDepthMarketData.Bids.Length == 0) - break; - - if (DepthMarket.Bids != null && DepthMarket.Bids.Length > 0) - { - if (DepthMarket.Bids[0].Size == pDepthMarketData.Bids[0].Size - && DepthMarket.Bids[0].Price == pDepthMarketData.Bids[0].Price) - { - // 由于与上次一样,不能动 - break; - } - } - - Bid bid = new Bid( - _dateTime, - _exchangeDateTime, - this.id, - InstrumentId, - pDepthMarketData.Bids[0].Price, - pDepthMarketData.Bids[0].Size); - - EmitData(bid); - - } while (false); - } - - private void FireAsk(int InstrumentId, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) - { - do - { - if (pDepthMarketData.Asks == null || pDepthMarketData.Asks.Length == 0) - break; - - if (DepthMarket.Asks != null && DepthMarket.Asks.Length > 0) - { - if (DepthMarket.Asks[0].Size == pDepthMarketData.Asks[0].Size - && DepthMarket.Asks[0].Price == pDepthMarketData.Asks[0].Price) - { - // 由于与上次一样,不能动 - break; - } - - } - - Ask ask = new Ask( - _dateTime, - _exchangeDateTime, - this.id, - InstrumentId, - pDepthMarketData.Asks[0].Price, - pDepthMarketData.Asks[0].Size); - - EmitData(ask); - - } while (false); - } - } -} diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.cs b/QuantBox.API.Provider/Single/SingleProvider.API.cs deleted file mode 100644 index 97330b1..0000000 --- a/QuantBox.API.Provider/Single/SingleProvider.API.cs +++ /dev/null @@ -1,204 +0,0 @@ -using SmartQuant; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using XAPI.Callback; -using XAPI; -using NLog; -using System.Reflection; -using QuantBox.Extensions; -using System.IO; - -namespace QuantBox.APIProvider.Single -{ - public partial class SingleProvider - { - static SingleProvider() { - NLog.LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(Path.Combine(PathHelper.RootPath.LocalPath, "NLog.config"), true); - } - //记录合约列表,从实盘合约名到对象的映射 - private readonly Dictionary _dictInstruments = new Dictionary(); - - public static int GetDate(DateTime dt) - { - return dt.Year * 10000 + dt.Month * 100 + dt.Day; - } - - public static int GetTime(DateTime dt) - { - return dt.Hour * 10000 + dt.Minute * 100 + dt.Second; - } - - public static DateTime GetDateTime(int yyyyMMdd, int hhmmss, int Millisecond) - { - int yyyy = yyyyMMdd / 10000; - int MM = yyyyMMdd % 10000 / 100; - int dd = yyyyMMdd % 100; - int hh = hhmmss / 10000; - int mm = hhmmss % 10000 / 100; - int ss = hhmmss % 100; - DateTime dt = new DateTime(yyyy, MM, dd, hh, mm, ss, Millisecond); - return dt; - } - - private void OnRspQryInstrument_callback(object sender, ref InstrumentField instrument, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryInstrument"); - return; - } - - _dictInstruments[instrument.Symbol] = instrument; - - if(bIsLast) - { - (sender as XApi).GetLog().Info("合约列表已经接收完成,共 {0} 条", _dictInstruments.Count); - } - } - - private void OnRspQryTradingAccount_callback(object sender, ref AccountField account, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryTradingAccount"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryTradingAccount:" + account.ToFormattedString()); - - if (!IsConnected) - return; - - string currency = "CNY"; - - AccountData ad = new AccountData(DateTime.Now, AccountDataType.AccountValue, - account.AccountID, this.id, this.id); - - Type type = typeof(AccountField); - FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - foreach (FieldInfo field in fields) - { - ad.Fields.Add(field.Name, currency, field.GetValue(account)); - } - // 将对像完全设置进去,等着取出 - ad.Fields.Add(AccountDataFieldEx.USER_DATA, currency, account); - ad.Fields.Add(AccountDataFieldEx.DATE, currency, GetDate(DateTime.Today)); - - - try - { - EmitAccountData(ad); - } - catch (Exception ex) - { - (sender as XApi).GetLog().Error(ex); - } - } - - private void OnRspQryInvestor_callback(object sender, ref InvestorField investor, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryInvestor"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryInvestor:{0}", investor.ToFormattedString()); - } - - private void OnRspQryInvestorPosition_callback(object sender, ref PositionField position, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryInvestorPosition"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryInvestorPosition:" + position.ToFormattedString()); - - if (!IsConnected) - return; - - PositionFieldEx item; - if (!positions.TryGetValue(position.Symbol, out item)) - { - item = new PositionFieldEx(); - positions[position.Symbol] = item; - } - item.AddPosition(position); - - AccountData ad = new AccountData(DateTime.Now, AccountDataType.Position, - position.AccountID, this.id, this.id); - - ad.Fields.Add(AccountDataField.SYMBOL, item.Symbol); - ad.Fields.Add(AccountDataField.EXCHANGE,item.Exchange); - ad.Fields.Add(AccountDataField.QTY, item.Qty); - ad.Fields.Add(AccountDataField.LONG_QTY, item.LongQty); - ad.Fields.Add(AccountDataField.SHORT_QTY, item.ShortQty); - - ad.Fields.Add(AccountDataFieldEx.USER_DATA, item); - ad.Fields.Add(AccountDataFieldEx.DATE, GetDate(DateTime.Today)); - - try - { - EmitAccountData(ad); - } - catch (Exception ex) - { - (sender as XApi).GetLog().Error(ex); - } - } - - private void OnRspQrySettlementInfo(object sender, ref SettlementInfoClass settlementInfo, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQrySettlementInfo"); - return; - } - - if (bIsLast) - { - (sender as XApi).GetLog().Info("OnRspQrySettlementInfo:" + Environment.NewLine + settlementInfo.Content); - } - } - - - private void OnRspQryQuote_callback(object sender, ref QuoteField quote, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryQuote"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryQuote:" + quote.ToFormattedString()); - } - - private void OnRspQryTrade_callback(object sender, ref TradeField trade, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryTrade"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryTrade:" + trade.ToFormattedString()); - } - - private void OnRspQryOrder_callback(object sender, ref OrderField order, int size1, bool bIsLast) - { - if (size1 <= 0) - { - (sender as XApi).GetLog().Info("OnRspQryOrder"); - return; - } - - (sender as XApi).GetLog().Info("OnRspQryOrder:" + order.ToFormattedString()); - } - } -} diff --git a/QuantBox.API.Provider/Single/SingleProvider.Other.cs b/QuantBox.API.Provider/Single/SingleProvider.Other.cs deleted file mode 100644 index 8603ffc..0000000 --- a/QuantBox.API.Provider/Single/SingleProvider.Other.cs +++ /dev/null @@ -1,37 +0,0 @@ -using XAPI; -using SmartQuant; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace QuantBox.APIProvider.Single -{ - public partial class SingleProvider - { - // 得到API中的合约名与交易所 - private void GetApi_Symbol_Exchange_TickSize(Instrument instrument, - out string altSymbol, out string altExchange, - out string apiSymbol, out string apiExchange, - out double apiTickSize) - { - // 取合约别名 - altSymbol = instrument.GetSymbol(this.id); - altExchange = instrument.GetExchange(this.id); - apiTickSize = instrument.TickSize; - - // 取合约在API中的名字 - apiSymbol = altSymbol; - apiExchange = altExchange; - - InstrumentField _Instrument; - if (_dictInstruments.TryGetValue(altSymbol, out _Instrument)) - { - apiSymbol = _Instrument.InstrumentID; - apiExchange = _Instrument.ExchangeID; - apiTickSize = _Instrument.PriceTick; - } - } - } -} diff --git a/QuantBox.API.Provider/Single/UserItem.cs b/QuantBox.API.Provider/Single/UserItem.cs deleted file mode 100644 index 1ea388b..0000000 --- a/QuantBox.API.Provider/Single/UserItem.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using XAPI; - -namespace QuantBox.APIProvider.Single -{ - [DefaultProperty("Label")] - public class UserItem:ICloneable - { - [Category("标签")] - public string Label - { - get; - set; - } - /// - /// 用户代码 - /// - [Category("账号")] - public string UserID { get; set; } - /// - /// 密码 - /// - [Category("账号")] - public string Password { get; set; } - /// - /// 扩展信息 - /// - [Category("账号")] - public string ExtInfoChar64 { get; set; } - [Category("账号")] - public int ExtInfoInt32 { get; set; } - - public UserInfoField ToStruct() - { - UserInfoField field; - - field.UserID = this.UserID; - field.Password = this.Password; - field.ExtInfoChar64 = this.ExtInfoChar64; - field.ExtInfoInt32 = this.ExtInfoInt32; - - return field; - } - - public override string ToString() - { - return string.Format("Label={0};UserID={1}", this.Label,this.UserID); - } - - public object Clone() - { - return this.MemberwiseClone(); - } - } -} diff --git a/QuantBox.API.Provider/app.config b/QuantBox.API.Provider/app.config deleted file mode 100644 index 99ddf3e..0000000 --- a/QuantBox.API.Provider/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/QuantBox.API.Provider/packages.config b/QuantBox.API.Provider/packages.config deleted file mode 100644 index 539b72e..0000000 --- a/QuantBox.API.Provider/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/QuantBox.APIProvider.sln b/QuantBox.APIProvider.sln new file mode 100644 index 0000000..ecede0b --- /dev/null +++ b/QuantBox.APIProvider.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29411.108 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuantBox.Extensions", "QuantBox.Extensions\QuantBox.Extensions.csproj", "{1A772F95-B670-40AC-8D29-2B82A4B95E12}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuantBox.APIProvider", "QuantBox.APIProvider\QuantBox.APIProvider.csproj", "{DD59FAE6-8CA6-4389-B934-06F26A08D289}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A772F95-B670-40AC-8D29-2B82A4B95E12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A772F95-B670-40AC-8D29-2B82A4B95E12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A772F95-B670-40AC-8D29-2B82A4B95E12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A772F95-B670-40AC-8D29-2B82A4B95E12}.Release|Any CPU.Build.0 = Release|Any CPU + {DD59FAE6-8CA6-4389-B934-06F26A08D289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD59FAE6-8CA6-4389-B934-06F26A08D289}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD59FAE6-8CA6-4389-B934-06F26A08D289}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD59FAE6-8CA6-4389-B934-06F26A08D289}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DE721877-AECD-4501-976D-2AA3C52F9CE0} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = SMACrossover\Realtime\Realtime.csproj + EndGlobalSection +EndGlobal diff --git a/QuantBox.APIProvider/CmdLine.cs b/QuantBox.APIProvider/CmdLine.cs new file mode 100644 index 0000000..f1aee41 --- /dev/null +++ b/QuantBox.APIProvider/CmdLine.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using CommandLine; + +#if NET48 +using System.Windows.Forms; +#endif + +namespace QuantBox.APIProvider +{ + public class Options + { + [Option('f', "file", Required = false, HelpText = "策略项目文件")] + public string file { get; set; } + + [Option('i', "id", Required = false, HelpText = "实例数字ID,用于收到剪贴板事件时过滤使用")] + public int id { get; set; } + + [Option('r', "run", Required = false, Default = false, HelpText = "运行策略")] + public bool run { get; set; } + + [Option('s', "stop", Required = false, Default = false, HelpText = "停止策略")] + public bool stop { get; set; } + + [Option('e', "exit", Required = false, Default = false, HelpText = "退出程序")] + public bool exit { get; set; } + } + + public class CmdLine + { + private int id; + + public void ParseForStart(ProviderHost host) + { + // 通过命令行启动策略 + //cd "C:\Program Files\SmartQuant Ltd\OpenQuant 2014" + //C: + //start OpenQuant.exe --file="D:\Users\Kan\Documents\OpenQuant 2014\Solutions\SMACrossover\SMACrossover.sln" --id=100 --run + + var args = Environment.GetCommandLineArgs(); + var text = Environment.CommandLine; + Console.WriteLine($"命令行: {text}"); + CommandLine.Parser.Default.ParseArguments(args) + .WithParsed(opts => RunOptions(opts, host)) + .WithNotParsed((errs) => HandleParseError(errs)); + } + + public void ParseForStop(ProviderHost host) + { +#if NET48 + //echo --id=100 --stop --exit | clip + IDataObject ido = Clipboard.GetDataObject(); + + if (!ido.GetDataPresent(DataFormats.Text)) + return; + + var text = ido.GetData(DataFormats.Text) as string; + Console.WriteLine($"剪贴板: {text}"); + CommandLine.Parser.Default.ParseArguments(text.Split(' ')) + .WithParsed(opts => ExitOptions(opts, host)) + .WithNotParsed((errs) => HandleParseError(errs)); +#endif + } + + void RunOptions(Options opts, ProviderHost host) + { + // 记下ID,退出时使用 + id = opts.id; + + if (string.IsNullOrEmpty(opts.file)) + return; + + if (!(new FileInfo(opts.file).Exists)) + return; + + host.Solution_Start_Thread(opts); + } + + void ExitOptions(Options opts, ProviderHost host) + { + if (id != opts.id) + return; + + host.Solution_Stop_Thread(opts); + } + + void HandleParseError(IEnumerable errs) + { + // Application.Exit(); + } + } +} diff --git a/QuantBox.API.Provider/ControlExtention.cs b/QuantBox.APIProvider/ControlExtention.cs similarity index 91% rename from QuantBox.API.Provider/ControlExtention.cs rename to QuantBox.APIProvider/ControlExtention.cs index abf4943..696e84e 100644 --- a/QuantBox.API.Provider/ControlExtention.cs +++ b/QuantBox.APIProvider/ControlExtention.cs @@ -3,10 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Windows.Forms; + namespace QuantBox.APIProvider { +#if NET48 + using System.Windows.Forms; + public static class ControlExtention { public delegate void InvokeHandler(); @@ -23,4 +26,5 @@ public static void SafeInvoke(this Control control, InvokeHandler handler) } } } +#endif } diff --git a/QuantBox.API.Provider/Host/ProviderHost.cs b/QuantBox.APIProvider/Host/ProviderHost.cs similarity index 76% rename from QuantBox.API.Provider/Host/ProviderHost.cs rename to QuantBox.APIProvider/Host/ProviderHost.cs index c29d2cc..836ab8d 100644 --- a/QuantBox.API.Provider/Host/ProviderHost.cs +++ b/QuantBox.APIProvider/Host/ProviderHost.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + using SmartQuant; using System.IO; @@ -10,7 +8,10 @@ using System.ComponentModel; using QuantBox.APIProvider.Single; using System.Reflection; -using System.Windows.Forms; +using CommandLine; +using ClipboardMonitor; +using System.Threading; + namespace QuantBox.APIProvider { @@ -18,7 +19,7 @@ namespace QuantBox.APIProvider /// Provder宿主 /// 由它进行其它Provder的初始创建,以及订单的路由 /// - public class ProviderHost : Provider, IExecutionProvider, IDataProvider + public partial class ProviderHost : Provider, IExecutionProvider, IDataProvider { #region Provider [Description("创建Provder的记录文件路径")] @@ -48,8 +49,6 @@ public string Version get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } - - public ProviderHost(Framework framework) : base(framework) { @@ -67,6 +66,22 @@ public ProviderHost(Framework framework) Create(ProviderList); ProviderList.ListChanged += ProviderList_ListChanged; + +#if NET48 + cmdLine = new CmdLine(); + try + { + cmdLine.ParseForStart(this); + } + catch + { + + } + + + new ClipboardNotifications(); + ClipboardNotifications.ClipboardUpdate += ClipboardNotifications_ClipboardUpdate; +#endif } ~ProviderHost() @@ -79,53 +94,19 @@ void ProviderList_ListChanged(object sender, ListChangedEventArgs e) Save(ConfigPath); } - //private static bool _bMenuAdded; - public void Init(byte id, string name) { base.id = id; base.name = name; base.description = "QuantBox API Provider Host"; base.url = "www.quantbox.cn"; - - //if (!_bMenuAdded) - //{ - // try - // { - // // DOS窗口时没有问题,非DOS下异常,所以可以利用一下 - // Console.Clear(); - // } - // catch - // { - // Console.WriteLine("要创建自己的菜单"); - // // 只有DOS窗口时要注意 - // System.Threading.ThreadPool.QueueUserWorkItem(delegate - // { - // while (Application.OpenForms.Count == 0 || Application.OpenForms[0].Name != "MainForm") - // { - // System.Threading.Thread.Sleep(1000); - // } - // Form mainForm = Application.OpenForms[0]; - - // try - // { - // mainForm.SafeInvoke(() => { AddToolStripMenuItem(mainForm); }); - // } - // catch (Exception e) - // { - // // 奇怪,调试的时候总是会出错 - // Console.WriteLine(e); - // } - // }); - // } - // _bMenuAdded = true; - //} } /// /// 指定保存格式 /// - private static readonly JsonSerializerSettings JSetting = new JsonSerializerSettings() { + private static readonly JsonSerializerSettings JSetting = new JsonSerializerSettings() + { //NullValueHandling = NullValueHandling.Ignore, //DefaultValueHandling = DefaultValueHandling.Ignore, Formatting = Formatting.Indented, @@ -134,17 +115,19 @@ public void Init(byte id, string name) // 读取信息 public void Load(string path) { - try { + try + { object ret; - using (TextReader reader = new StreamReader(path)) { + using (TextReader reader = new StreamReader(path)) + { ret = JsonConvert.DeserializeObject(reader.ReadToEnd(), ProviderList.GetType()); reader.Close(); } ProviderList = ret as BindingList; } - catch + catch(Exception ex) { - // ignored + Console.WriteLine(ex); } } @@ -153,7 +136,8 @@ public void Save(string path) if (ProviderList == null) return; - using (TextWriter writer = new StreamWriter(path)) { + using (TextWriter writer = new StreamWriter(path)) + { writer.Write("{0}", JsonConvert.SerializeObject(ProviderList, ProviderList.GetType(), JSetting)); writer.Close(); } @@ -174,9 +158,11 @@ public override void Disconnect() public void Create(IList list) { - foreach (var l in list) { + foreach (var l in list) + { IProvider pvd = framework.ProviderManager.GetProvider(l.Id); - if (pvd == null) { + if (pvd == null) + { SingleProvider provider = new SingleProvider(framework); provider.Init(l.Id, l.Name); framework.ProviderManager.AddProvider(provider); @@ -202,7 +188,7 @@ public IExecutionProvider ExecutionProvider } public override void Send(ExecutionCommand command) { - if(command.RouteId == id) + if (command.RouteId == id) return; IExecutionProvider provider; diff --git a/QuantBox.APIProvider/Host/ProviderHost_UI.cs b/QuantBox.APIProvider/Host/ProviderHost_UI.cs new file mode 100644 index 0000000..b715a89 --- /dev/null +++ b/QuantBox.APIProvider/Host/ProviderHost_UI.cs @@ -0,0 +1,161 @@ +using System; + +using SmartQuant; +using System.IO; +using System.Reflection; + +using CommandLine; + +using System.Threading; + +#if NET48 +using ClipboardMonitor; +using System.Windows.Forms; +#endif + +namespace QuantBox.APIProvider +{ + /// + /// Provder宿主 + /// 由它进行其它Provder的初始创建,以及订单的路由 + /// + public partial class ProviderHost + { + private CmdLine cmdLine = null; + + private void ClipboardNotifications_ClipboardUpdate(object sender, EventArgs e) + { + try + { + cmdLine.ParseForStop(this); + } + catch + { + + } + } + + private object GetSolutionManager() + { + // OpenQuant.Global.SolutionManager是静态属性,可以通过Get方式获得 + var g = Assembly.GetEntryAssembly().GetType("OpenQuant.Global"); + var sm = g.GetProperty("SolutionManager"); + return sm.GetGetMethod().Invoke(null, null); + } + + private void LoadSolution(object solutionManager, string filename) + { + var type = solutionManager.GetType(); + var m = type.GetMethod("LoadSolution", BindingFlags.NonPublic | BindingFlags.Instance); + m.Invoke(solutionManager, new object[] { new FileInfo(filename) }); + } + + public void Solution_Start_Thread(Options opts) + { +#if NET48 + System.Threading.ThreadPool.QueueUserWorkItem(delegate + { + DateTime dt = DateTime.Now; + // 检查界面是否正常启动 + var mainForm = GetMainForm(); + while (mainForm == null) + { + Thread.Sleep(1000); + mainForm = GetMainForm(); + + // 如果1分钟找不到就退出循环 + var ts = DateTime.Now - dt; + if (ts.TotalSeconds > 60) + { + return; + } + } + + var sm = GetSolutionManager(); + Thread.Sleep(1000); + mainForm.SafeInvoke(() => + { + LoadSolution(sm, opts.file); + }); + if (opts.run) + { + Thread.Sleep(3000); + mainForm.SafeInvoke(() => + { + Solution_Start(mainForm); + }); + } + }); +#endif + } + + public void Solution_Stop_Thread(Options opts) + { +#if NET48 + System.Threading.ThreadPool.QueueUserWorkItem(delegate + { + var mainForm = GetMainForm(); + if (mainForm == null) + { + return; + } + var sm = GetSolutionManager(); + if (opts.stop) + { + // 没有停止的需要停止才能退出 + if (framework.StrategyManager.Status != StrategyStatus.Stopped) + { + Thread.Sleep(1000); + mainForm.SafeInvoke(() => + { + Solution_Stop(mainForm); + }); + } + } + if (opts.exit) + { + Thread.Sleep(3000); + mainForm.SafeInvoke(() => + { + File_Exit(mainForm); + }); + } + }); +#endif + } + + +#if NET48 + + private Form GetMainForm() + { + foreach (Form f in Application.OpenForms) + { + if (f.Name == "MainForm") + return f; + } + return null; + } + private void Solution_Start(Form from) + { + Type type = from.GetType(); + var m = type.GetMethod("menuSolution_Start_Click", BindingFlags.NonPublic | BindingFlags.Instance); + m.Invoke(from, new object[] { null, null }); + } + + private void Solution_Stop(Form from) + { + Type type = from.GetType(); + var m = type.GetMethod("menuSolution_Stop_Click", BindingFlags.NonPublic | BindingFlags.Instance); + m.Invoke(from, new object[] { null, null }); + } + + private void File_Exit(Form from) + { + Type type = from.GetType(); + var m = type.GetMethod("menuFile_Exit_Click", BindingFlags.NonPublic | BindingFlags.Instance); + m.Invoke(from, new object[] { null, null }); + } +#endif + } +} diff --git a/QuantBox.API.Provider/Host/ProviderItem.cs b/QuantBox.APIProvider/Host/ProviderItem.cs similarity index 100% rename from QuantBox.API.Provider/Host/ProviderItem.cs rename to QuantBox.APIProvider/Host/ProviderItem.cs diff --git a/QuantBox.API.Provider/PathHelper.cs b/QuantBox.APIProvider/PathHelper.cs similarity index 100% rename from QuantBox.API.Provider/PathHelper.cs rename to QuantBox.APIProvider/PathHelper.cs diff --git a/QuantBox.API.Provider/Properties/AssemblyInfo.cs b/QuantBox.APIProvider/Properties/AssemblyInfo.cs similarity index 94% rename from QuantBox.API.Provider/Properties/AssemblyInfo.cs rename to QuantBox.APIProvider/Properties/AssemblyInfo.cs index 30febab..1f25849 100644 --- a/QuantBox.API.Provider/Properties/AssemblyInfo.cs +++ b/QuantBox.APIProvider/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.9.8.4")] -[assembly: AssemblyFileVersion("0.9.8.4")] \ No newline at end of file +[assembly: AssemblyVersion("0.9.8.5")] +[assembly: AssemblyFileVersion("0.9.8.5")] \ No newline at end of file diff --git a/QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.ApiItem.datasource b/QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.ApiItem.datasource similarity index 100% rename from QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.ApiItem.datasource rename to QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.ApiItem.datasource diff --git a/QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.ServerItem.datasource b/QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.ServerItem.datasource similarity index 100% rename from QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.ServerItem.datasource rename to QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.ServerItem.datasource diff --git a/QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.UserItem.datasource b/QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.UserItem.datasource similarity index 100% rename from QuantBox.API.Provider/Properties/DataSources/QuantBox.APIProvider.Single.UserItem.datasource rename to QuantBox.APIProvider/Properties/DataSources/QuantBox.APIProvider.Single.UserItem.datasource diff --git a/QuantBox.API.Provider/PropertySorter.cs b/QuantBox.APIProvider/PropertySorter.cs similarity index 100% rename from QuantBox.API.Provider/PropertySorter.cs rename to QuantBox.APIProvider/PropertySorter.cs diff --git a/QuantBox.APIProvider/QuantBox.APIProvider.csproj b/QuantBox.APIProvider/QuantBox.APIProvider.csproj new file mode 100644 index 0000000..bfa0720 --- /dev/null +++ b/QuantBox.APIProvider/QuantBox.APIProvider.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0;net48 + false + QuantBox.APIProvider + QuantBox.APIProvider + + + + + + + + + + + + + + + + C:\Program Files\SmartQuant Ltd\OpenQuant 2014\SmartQuant.dll + + + + + C:\Program Files\SmartQuant Ltd\OpenQuant 2014\XAPI_CSharp.dll + + + + + + + + diff --git a/QuantBox.APIProvider/QuantBox.APIProvider.csproj.user b/QuantBox.APIProvider/QuantBox.APIProvider.csproj.user new file mode 100644 index 0000000..34a18ad --- /dev/null +++ b/QuantBox.APIProvider/QuantBox.APIProvider.csproj.user @@ -0,0 +1,12 @@ + + + + + + Form + + + Form + + + \ No newline at end of file diff --git a/QuantBox.APIProvider/Single/ApiItem.cs b/QuantBox.APIProvider/Single/ApiItem.cs new file mode 100644 index 0000000..10c5cca --- /dev/null +++ b/QuantBox.APIProvider/Single/ApiItem.cs @@ -0,0 +1,121 @@ +using QuantBox.APIProvider.UI; +using System; +using System.ComponentModel; +using XAPI; + +namespace QuantBox.APIProvider.Single +{ +#if NET48 + using System.Drawing.Design; +#endif + /// + /// 由用户选择Dll,然后加载,得到 + /// + /// + public class ApiItem : ICloneable + { + public const string CATEGORY_INFO = "Information"; + public const string CATEGORY_TYPE = "Type"; + + internal BindingList LinkedUserList; + internal BindingList LinkedServerList; + + private string _dllPath; + private string _typeName; + + private IXApi CheckApi(string typeName, string dllPath) + { + if (string.IsNullOrEmpty(typeName)) + { + TypeName = "XAPI.Callback.XApi, XAPI_CSharp"; + return null; + } + var api = XApiHelper.CreateInstance(typeName, dllPath); + try + { + Type = api.GetApiTypes; + Name = api.GetApiName; + Version = api.GetApiVersion; + + // 取公共部分 + UseType = UseType & Type; + } + catch (Exception ex) + { + api = null; + Type = ApiType.None; + Name = ex.Message; + Version = "请使用depends检查一下是否缺少依赖"; + UseType = ApiType.None; + } + return api; + } +#if NET48 + [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))] +#endif + public string DllPath + { + get + { + return _dllPath; + } + set + { + _dllPath = value; + Api = CheckApi(_typeName, _dllPath); + } + } + + public string TypeName + { + get + { + return _typeName; + } + set + { + _typeName = value; + Api = CheckApi(_typeName, _dllPath); + } + } + + [Browsable(false)] + internal IXApi Api { get; set; } + + [Category(CATEGORY_INFO)] + [ReadOnly(true)] + public string Name { get; set; } + + [Category(CATEGORY_INFO)] + [ReadOnly(true)] + public string Version { get; set; } + + [TypeConverter(typeof(UserItemConverter))] + public int User { get; set; } + [TypeConverter(typeof(ServerItemConverter))] + public int Server { get; set; } + + public string LogPrefix { get; set; } + + + [Category(CATEGORY_TYPE)] + [ReadOnly(true)] + public ApiType Type { get; set; } +#if NET48 + [Editor(typeof(ApiTypeSelectorEditor), typeof(UITypeEditor))] +#endif + [Category(CATEGORY_TYPE)] + public ApiType UseType { get; set; } + + + public override string ToString() + { + return string.Format("LogPrefix={0};UseType={1};Name={2}", LogPrefix, UseType, Name); + } + + public object Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/QuantBox.API.Provider/Single/BaseMap.cs b/QuantBox.APIProvider/Single/BaseMap.cs similarity index 69% rename from QuantBox.API.Provider/Single/BaseMap.cs rename to QuantBox.APIProvider/Single/BaseMap.cs index fbd8ac0..4c54317 100644 --- a/QuantBox.API.Provider/Single/BaseMap.cs +++ b/QuantBox.APIProvider/Single/BaseMap.cs @@ -12,7 +12,7 @@ namespace QuantBox.APIProvider.Single { class BaseMap { - private Framework framework; + protected Framework framework; protected SingleProvider provider; public BaseMap(Framework framework, SingleProvider provider) @@ -27,21 +27,10 @@ public ExecutionReport CreateReport( SQ.ExecType? execType, SQ.OrderStatus? orderStatus) { - ExecutionReport report = new ExecutionReport(); + ExecutionReport report = new ExecutionReport(record.Order); report.DateTime = framework.Clock.DateTime; - report.Order = record.Order; - report.Instrument = record.Order.Instrument; - - report.Side = record.Order.Side; - report.OrdType = record.Order.Type; - report.TimeInForce = record.Order.TimeInForce; - - report.OrdQty = record.Order.Qty; - report.Price = record.Order.Price; - report.StopPx = record.Order.StopPx; - report.AvgPx = record.AvgPx; report.CumQty = record.CumQty; report.LeavesQty = record.LeavesQty; @@ -55,6 +44,28 @@ public ExecutionReport CreateReport( return report; } + public ExecutionReport CreateReport( + Order order, + SQ.ExecType? execType, + SQ.OrderStatus? orderStatus) + { + ExecutionReport report = new ExecutionReport(order); + + report.DateTime = framework.Clock.DateTime; + + report.AvgPx = order.AvgPx; + report.CumQty = order.CumQty; + report.LeavesQty = order.LeavesQty; + + if (execType != null) + report.ExecType = execType.Value; + + if (orderStatus != null) + report.OrdStatus = orderStatus.Value; + + return report; + } + public void EmitExecutionReport(OrderRecord record, SQ.ExecType execType, SQ.OrderStatus orderStatus) { ExecutionReport report = CreateReport(record, execType, orderStatus); diff --git a/QuantBox.API.Provider/Single/Extensions.cs b/QuantBox.APIProvider/Single/Extensions.cs similarity index 79% rename from QuantBox.API.Provider/Single/Extensions.cs rename to QuantBox.APIProvider/Single/Extensions.cs index b64743d..ef16f50 100644 --- a/QuantBox.API.Provider/Single/Extensions.cs +++ b/QuantBox.APIProvider/Single/Extensions.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using XAPI.Callback; +using XAPI; using NLog; @@ -11,7 +11,7 @@ namespace QuantBox.APIProvider.Single { public static class XAPI_Extensions { - public static Logger GetLog(this XApi api) + public static Logger GetLog(this IXApi api) { return (api.Log as Logger); } diff --git a/QuantBox.API.Provider/Single/ExternalOrderRecord.cs b/QuantBox.APIProvider/Single/ExternalOrderRecord.cs similarity index 100% rename from QuantBox.API.Provider/Single/ExternalOrderRecord.cs rename to QuantBox.APIProvider/Single/ExternalOrderRecord.cs diff --git a/QuantBox.API.Provider/Single/HistoricalDataRecord.cs b/QuantBox.APIProvider/Single/HistoricalDataRecord.cs similarity index 100% rename from QuantBox.API.Provider/Single/HistoricalDataRecord.cs rename to QuantBox.APIProvider/Single/HistoricalDataRecord.cs diff --git a/QuantBox.API.Provider/Single/InstrumentJson.cs b/QuantBox.APIProvider/Single/InstrumentJson.cs similarity index 100% rename from QuantBox.API.Provider/Single/InstrumentJson.cs rename to QuantBox.APIProvider/Single/InstrumentJson.cs diff --git a/QuantBox.API.Provider/Single/MarketDataRecord.cs b/QuantBox.APIProvider/Single/MarketDataRecord.cs similarity index 76% rename from QuantBox.API.Provider/Single/MarketDataRecord.cs rename to QuantBox.APIProvider/Single/MarketDataRecord.cs index e54420b..34c2584 100644 --- a/QuantBox.API.Provider/Single/MarketDataRecord.cs +++ b/QuantBox.APIProvider/Single/MarketDataRecord.cs @@ -16,7 +16,7 @@ public class MarketDataRecord public string Symbol_Dot; public string Symbol_Dot_Exchange; - public Instrument Instrument; + public string Instrument; public bool TradeRequested; public bool QuoteRequested; @@ -24,10 +24,12 @@ public class MarketDataRecord // 记录上次行情 public DepthMarketDataNClass DepthMarket; - public MarketDataRecord(Instrument instrument) + public SortedSet Ids; + + public MarketDataRecord() { - this.Instrument = instrument; - this.DepthMarket = new DepthMarketDataNClass(); + DepthMarket = new DepthMarketDataNClass(); + Ids = new SortedSet(); } } } diff --git a/QuantBox.APIProvider/Single/NoTypeConverterJsonConverter.cs b/QuantBox.APIProvider/Single/NoTypeConverterJsonConverter.cs new file mode 100644 index 0000000..280c123 --- /dev/null +++ b/QuantBox.APIProvider/Single/NoTypeConverterJsonConverter.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuantBox.APIProvider.Single +{ + public class NoTypeConverterJsonConverter : JsonConverter + { + static readonly IContractResolver resolver = new NoTypeConverterContractResolver(); + + class NoTypeConverterContractResolver : DefaultContractResolver + { + protected override JsonContract CreateContract(Type objectType) + { + if (typeof(T).IsAssignableFrom(objectType)) + { + var contract = this.CreateObjectContract(objectType); + contract.Converter = null; // Also null out the converter to prevent infinite recursion. + return contract; + } + return base.CreateContract(objectType); + } + } + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value); + } + } +} diff --git a/QuantBox.APIProvider/Single/OrderMap.cs b/QuantBox.APIProvider/Single/OrderMap.cs new file mode 100644 index 0000000..58d3e83 --- /dev/null +++ b/QuantBox.APIProvider/Single/OrderMap.cs @@ -0,0 +1,374 @@ +using XAPI; +using SmartQuant; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using SQ = SmartQuant; + +namespace QuantBox.APIProvider.Single +{ + class OrderMap : BaseMap + { + private ConcurrentDictionary externalOrders; // 新单 + // LocalID到Order的映射 + private ConcurrentDictionary pendingOrders; // 新单 + // ID到Order + private Dictionary workingOrders; // 挂单 + + // Order.ID与LocalID或ID的映射,没有收到回报时是LocalID,收到后要更新为ID + private Dictionary orderIDs; // 撤单时映射 + private ConcurrentDictionary pendingCancels; // 撤单拒绝时使用 + private ConcurrentDictionary tradesDict; // 防止重复收到成交回报 + private OrderRecord GetExternalOrder(ref TradeField field) + { + ExternalOrderRecord record; + if (externalOrders.TryGetValue(field.InstrumentID, out record)) + { + if (field.OpenClose == OpenCloseType.Open) + { + return field.Side == XAPI.OrderSide.Buy ? record.BuyOpen : record.SellOpen; + } + return field.Side == XAPI.OrderSide.Buy ? record.BuyClose : record.SellClose; + } + return null; + } + private void SetExternalOrder(Order order, ref OrderField field) + { + var orderRecord = new OrderRecord(order); + ExternalOrderRecord record = new ExternalOrderRecord(); + record = externalOrders.GetOrAdd(order.Instrument.Symbol, record); + if (field.OpenClose == OpenCloseType.Open) + { + if (field.Side == XAPI.OrderSide.Buy) + { + record.BuyOpen = orderRecord; + } + else + { + record.SellOpen = orderRecord; + } + } + else + { + if (field.Side == XAPI.OrderSide.Buy) + { + record.BuyClose = orderRecord; + } + else + { + record.SellClose = orderRecord; + } + } + } + public OrderMap(Framework framework, SingleProvider provider) + : base(framework, provider) + { + externalOrders = new ConcurrentDictionary(); + pendingOrders = new ConcurrentDictionary(); + workingOrders = new Dictionary(); + orderIDs = new Dictionary(); + pendingCancels = new ConcurrentDictionary(); + tradesDict = new ConcurrentDictionary(); + } + + public void Clear() + { + pendingOrders.Clear(); + workingOrders.Clear(); + orderIDs.Clear(); + pendingCancels.Clear(); + tradesDict.Clear(); + } + + public void DoOrderSend(ref OrderField[] ordersArray, Order order) + { + if (Convert.ToInt32(order.Qty) == int.MaxValue) + { + SetExternalOrder(order, ref ordersArray[0]); + } + else + { + DoOrderSend(ref ordersArray, new List() { order }); + } + } + + public void DoOrderSend(ref OrderField[] ordersArray, List ordersList) + { + lock (this) + { + // 这里其实返回的是LocalID + string outstr = provider._TdApi.SendOrder(ordersArray); + string[] OrderIds = outstr.Split(';'); + + int i = 0; + foreach (var orderId in OrderIds) + { + if (string.IsNullOrEmpty(orderId)) + { + // 直接将单子拒绝 + EmitExecutionReport(new OrderRecord(ordersList[i]), SQ.ExecType.ExecRejected, SQ.OrderStatus.Rejected, "Provider ErrorCode:" + orderId); + } + else + { + // 记下了本地ID,用于立即撤单时供API来定位 + this.orderIDs[ordersList[i].Id] = orderId; + //Console.WriteLine(orderId); + this.pendingOrders[orderId] = new OrderRecord(ordersList[i]); + + ordersList[i].Fields[9] = orderId; + } + ++i; + } + } + } + + public void DoOrderCancel(Order order) + { + DoOrderCancel(new List() { order }); + } + + public void DoOrderCancel(List ordersList) + { + lock (this) + { + OrderRecord[] recordList = new OrderRecord[ordersList.Count]; + string[] OrderIds = new string[ordersList.Count]; + + for (int i = 0; i < ordersList.Count; ++i) + { + // 如果需要下单的过程中撤单,这里有可能返回LocalID或ID + if (orderIDs.TryGetValue(ordersList[i].Id, out OrderIds[i])) + { + if (this.workingOrders.TryGetValue(OrderIds[i], out recordList[i])) + { + // 订单已经下到柜台上了 + pendingCancels[OrderIds[i]] = recordList[i]; + } + else if (this.pendingOrders.TryGetValue(OrderIds[i], out recordList[i])) + { + // 订单还没有下到柜台,需要撤单 + pendingCancels[OrderIds[i]] = recordList[i]; + } + } + else if (ordersList[i].Fields[9] != null) + { + OrderIds[i] = (string)ordersList[i].Fields[9]; + recordList[i] = new OrderRecord(ordersList[i]); + } + } + + string outstr = provider._TdApi.CancelOrder(OrderIds); + string[] errs = outstr.Split(';'); + + { + int i = 0; + foreach (var e in errs) + { + if (!string.IsNullOrEmpty(e) && e != "0") + { + if (recordList[i] != null) + { + EmitExecutionReport(recordList[i], SQ.ExecType.ExecCancelReject, recordList[i].Order.Status, "Provider ErrorCode:" + e); + } + } + ++i; + } + } + } + } + + public void Process(ref OrderField order, NLog.Logger log) + { + // 所有的成交信息都不处理,交给TradeField处理 + if (order.ExecType == XAPI.ExecType.Trade) + return; + + lock (this) + { + OrderRecord record; + + switch (order.ExecType) + { + case XAPI.ExecType.New: + if (this.pendingOrders.TryRemove(order.LocalID, out record)) + { + this.workingOrders[order.ID] = record; + // 将LocalID更新为ID + this.orderIDs[record.Order.Id] = order.ID; + EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); + } + else + { + //log.Warn("New,找不到订单,pendingOrders.Count={0}", pendingOrders.Count); + } + break; + case XAPI.ExecType.Rejected: + if (this.pendingOrders.TryRemove(order.LocalID, out record)) + { + orderIDs.Remove(record.Order.Id); + record.LeavesQty = 0; + EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); + } + else if (this.workingOrders.TryGetValue(order.ID, out record)) + { + // 比如说出现超出涨跌停时,先会到ProcessNew,所以得再多判断一次 + workingOrders.Remove(order.ID); + orderIDs.Remove(record.Order.Id); + record.LeavesQty = 0; + EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); + } + break; + case XAPI.ExecType.Cancelled: + if (this.workingOrders.TryGetValue(order.ID, out record)) + { + workingOrders.Remove(order.ID); + orderIDs.Remove(record.Order.Id); + record.LeavesQty = 0; + EmitExecutionReport(record, SQ.ExecType.ExecCancelled, SQ.OrderStatus.Cancelled, order.Text()); + } + else if (this.pendingOrders.TryRemove(order.LocalID, out record)) + { + orderIDs.Remove(record.Order.Id); + record.LeavesQty = 0; + EmitExecutionReport(record, (SQ.ExecType)order.ExecType, (SQ.OrderStatus)order.Status, order.Text()); + } + break; + case XAPI.ExecType.PendingCancel: + if (this.workingOrders.TryGetValue(order.ID, out record)) + { + EmitExecutionReport(record, SQ.ExecType.ExecPendingCancel, SQ.OrderStatus.PendingCancel); + } + else if (this.pendingOrders.TryGetValue(order.LocalID, out record)) + { + EmitExecutionReport(record, SQ.ExecType.ExecPendingCancel, SQ.OrderStatus.PendingCancel); + } + break; + case XAPI.ExecType.CancelReject: + if (this.pendingCancels.TryRemove(order.ID, out record)) + { + // 已经收到第一回报的情况下 + EmitExecutionReport(record, SQ.ExecType.ExecCancelReject, (SQ.OrderStatus)order.Status, order.Text()); + } + else if (this.pendingCancels.TryRemove(order.LocalID, out record)) + { + // 没有收到第一条回报的情况下 + EmitExecutionReport(record, SQ.ExecType.ExecCancelReject, (SQ.OrderStatus)order.Status, order.Text()); + } + //else if (this.workingOrders.TryGetValue(order.ID, out record)) + //{ + // // 撤单回报延时的情况下 + // EmitExecutionReport(record, SQ.ExecType.ExecCancelReject, (SQ.OrderStatus)order.Status, order.Text()); + //} + break; + } + } + } + + public void Process(ref TradeField trade, NLog.Logger log) + { + lock (this) + { + OrderRecord record; + if (!workingOrders.TryGetValue(trade.ID, out record)) + { + record = GetExternalOrder(ref trade); + } + if (record != null) + { + // CTP出现过重复发送委托与成交的情况,需要过滤。并提示 + // 同一交易ID同一方向不会同时出现,出现表示系统重复提示 + // 不向方向表示自成交了 + TradeField field = null; + if (tradesDict.TryGetValue(trade.TradeID, out field)) + { + // 有取出,可能是自成交,也可能是重复发单 + if (trade.Side == field.Side) + { + log.Error("重复收到成交回报!"); + // 后面不再处理 + return; + } + else + { + log.Warn("出现自成交!"); + // 自成交也得处理 + } + } + else + { + tradesDict[trade.TradeID] = trade; + } + + record.AddFill(trade.Price, (int)trade.Qty); + SQ.ExecType execType = SQ.ExecType.ExecTrade; + SQ.OrderStatus orderStatus = (record.LeavesQty > 0) ? SQ.OrderStatus.PartiallyFilled : SQ.OrderStatus.Filled; + ExecutionReport report = CreateReport(record, execType, orderStatus); + report.LastPx = trade.Price; + report.LastQty = trade.Qty; + provider.EmitExecutionReport(report); + } + else + { + // log.Warn("Trade,找不到订单,workingOrders.Count={0}", workingOrders.Count); + } + } + } + + public void ProcessExpired(ref InstrumentStatusField instrumentStatus, NLog.Logger log) + { + // 根据当前挂单来统计 + //if (workingOrders.Count == 0) + // return; + + if (instrumentStatus.InstrumentStatus != TradingPhaseType.Closed) + return; + + foreach (var order in framework.OrderManager.Orders) + { + // 量有点多,需要判断一下 + if (order.IsDone) + continue; + + var inst = order.Instrument; + + string altSymbol; + string altExchange; + string apiSymbol; + string apiExchange; + double apiTickSize; + string apiProductID; + + this.provider.GetApi_Symbol_Exchange_TickSize(inst, this.provider.Id, + out altSymbol, out altExchange, + out apiSymbol, out apiExchange, + out apiTickSize, + out apiProductID); + + if (apiProductID != instrumentStatus.InstrumentID) + continue; + + ExecutionReport report = CreateReport(order, SQ.ExecType.ExecExpired, SQ.OrderStatus.Expired); + report.LeavesQty = 0; + report.Text = "收盘,插件标记定单过期"; + provider.EmitExecutionReport(report); + } + } + + public void ProcessNew(ref QuoteField quote, QuoteRecord record) + { + OrderRecord askRecord = new OrderRecord(record.AskOrder); + this.workingOrders[quote.AskID] = askRecord; + this.orderIDs[askRecord.Order.Id] = quote.AskID; + EmitExecutionReport(askRecord, (SQ.ExecType)quote.ExecType, (SQ.OrderStatus)quote.Status); + + OrderRecord bidRecord = new OrderRecord(record.BidOrder); + this.workingOrders[quote.BidID] = bidRecord; + this.orderIDs[bidRecord.Order.Id] = quote.BidID; + EmitExecutionReport(bidRecord, (SQ.ExecType)quote.ExecType, (SQ.OrderStatus)quote.Status); + } + } +} diff --git a/QuantBox.API.Provider/Single/OrderRecord.cs b/QuantBox.APIProvider/Single/OrderRecord.cs similarity index 100% rename from QuantBox.API.Provider/Single/OrderRecord.cs rename to QuantBox.APIProvider/Single/OrderRecord.cs diff --git a/QuantBox.API.Provider/Single/QuoteMap.cs b/QuantBox.APIProvider/Single/QuoteMap.cs similarity index 100% rename from QuantBox.API.Provider/Single/QuoteMap.cs rename to QuantBox.APIProvider/Single/QuoteMap.cs diff --git a/QuantBox.API.Provider/Single/QuoteRecord.cs b/QuantBox.APIProvider/Single/QuoteRecord.cs similarity index 100% rename from QuantBox.API.Provider/Single/QuoteRecord.cs rename to QuantBox.APIProvider/Single/QuoteRecord.cs diff --git a/QuantBox.APIProvider/Single/ServerItem.cs b/QuantBox.APIProvider/Single/ServerItem.cs new file mode 100644 index 0000000..2ba7078 --- /dev/null +++ b/QuantBox.APIProvider/Single/ServerItem.cs @@ -0,0 +1,36 @@ +using XAPI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +#if NET48 +using System.Drawing.Design; +#endif + +namespace QuantBox.APIProvider.Single + +{ + [DefaultProperty("Label")] + public class ServerItem : ICloneable + { + [Category("标签")] + public string Label { get; set; } + +#if NET48 + [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(UITypeEditor))] +#endif + public string Path { get; set; } + + public override string ToString() + { + return string.Format("Label={0}", this.Label); + } + + public object Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/QuantBox.APIProvider/Single/SessionTimeItem.cs b/QuantBox.APIProvider/Single/SessionTimeItem.cs new file mode 100644 index 0000000..ff5bbeb --- /dev/null +++ b/QuantBox.APIProvider/Single/SessionTimeItem.cs @@ -0,0 +1,105 @@ +using Newtonsoft.Json; +using OrderedPropertyGrid; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuantBox.APIProvider.Single +{ + // 发现想让属性显示排序,但发现Json转换出问题了,所以还是决定不排序了 + [TypeConverter(typeof(PropertySorter))] + [JsonConverter(typeof(NoTypeConverterJsonConverter))] + public class SessionTimeItem + { + public const string CATEGORY_DAY_OF_WEEK = "DayOfWeek"; + + private static string[] DayOfWeekChinese = new string[] { "日", "一", "二", "三", "四", "五", "六" }; + + private List DayOfWeekList = null; + + [PropertyOrder(1)] + public TimeSpan SessionStart { get; set; } + [PropertyOrder(2)] + public TimeSpan SessionEnd { get; set; } + + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(1)] + public bool Sunday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(2)] + public bool Monday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(3)] + public bool Tuesday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(4)] + public bool Wednesday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(5)] + public bool Thursday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(6)] + public bool Friday { get; set; } + [Category(CATEGORY_DAY_OF_WEEK)] + [PropertyOrder(7)] + public bool Saturday { get; set; } + + public bool Contains(DayOfWeek dayOfWeek) + { + if (DayOfWeekList == null) + DayOfWeekList = GetDayOfWeekList(); + + return DayOfWeekList.Contains(dayOfWeek); + } + + public List GetDayOfWeekList() + { + var list = new List(); + if (Sunday) list.Add(DayOfWeek.Sunday); + if (Monday) list.Add(DayOfWeek.Monday); + if (Tuesday) list.Add(DayOfWeek.Tuesday); + if (Wednesday) list.Add(DayOfWeek.Wednesday); + if (Thursday) list.Add(DayOfWeek.Thursday); + if (Friday) list.Add(DayOfWeek.Friday); + if (Saturday) list.Add(DayOfWeek.Saturday); + + DayOfWeekList = list; + + return list; + } + + public string GetDayOfWeekString() + { + var strs = new List(); + var list = GetDayOfWeekList(); + foreach (var l in list) + { + strs.Add(DayOfWeekChinese[Convert.ToInt16(l)]); + } + return string.Join("", strs); + } + + + public override string ToString() + { + var list = new List() { SessionStart, SessionEnd }; + var strs = new List(); + foreach (var ts in list) + { + if (ts.TotalDays >= 1.0) + { + strs.Add(ts.ToString(@"d\:mm")); + } + else + { + strs.Add(ts.ToString(@"hh\:mm")); + } + + } + return $"[{string.Join(",", strs)}] {{{GetDayOfWeekString()}}}"; + } + } +} diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.Connection.cs b/QuantBox.APIProvider/Single/SingleProvider.API.Connection.cs similarity index 66% rename from QuantBox.API.Provider/Single/SingleProvider.API.Connection.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Connection.cs index b3f066e..11dbf5d 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.API.Connection.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.API.Connection.cs @@ -1,47 +1,44 @@ using SmartQuant; using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; -using XAPI.Callback; using XAPI; using NLog; using QuantBox.Extensions; +using System.Threading; +using System.IO; namespace QuantBox.APIProvider.Single { -/* - 插件状态分解 + /* + 插件状态分解 - 人为: - 用户主动连接,1.连接成功,2连接失败,3,连接成功登录失败,4.登录成功,但初始化失败 - 用户主动断开, - 定时: - 定时连接 - 定时断开 - 其它: - 网络连上 - 网络断开 + 人为: + 用户主动连接,1.连接成功,2连接失败,3,连接成功登录失败,4.登录成功,但初始化失败 + 用户主动断开, + 定时: + 定时连接 + 定时断开 + 其它: + 网络连上 + 网络断开 - */ -/* - * OQ里正在断开连接不能轻易用,因为会导致右键时不能连接也不能断开 -*/ + */ + /* + * OQ里正在断开连接不能轻易用,因为会导致右键时不能连接也不能断开 + */ public partial class SingleProvider { // 实际的连接,由这个来 - internal XApi _TdApi; - internal XApi _MdApi; - internal XApi _L2Api; - internal XApi _QuoteRequestApi; - internal XApi _HdApi; - internal XApi _ItApi; - internal XApi _QueryApi; + internal IXApi _TdApi; + internal IXApi _MdApi; + internal IXApi _L2Api; + internal IXApi _QuoteRequestApi; + internal IXApi _HdApi; + internal IXApi _ItApi; + internal IXApi _QueryApi; private void _Connect(bool bFromUI) { @@ -80,7 +77,7 @@ private void _Connect(bool bFromUI) { if (item.UseType > 0) { - XApi api = ConnectToApi(item); + IXApi api = ConnectToApi(item); assign(item, api); } else @@ -134,78 +131,91 @@ private void _Disconnect(bool bFromUI) } - private int nDisconnectCount = 0; - void _Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private void CheckConnection(System.Timers.ElapsedEventArgs e) { - lock(this) + do { - _Timer.Enabled = false; + // 列表为空,表示不处理。这时没有自动重连 + if (SessionTimeList == null || SessionTimeList.Count == 0) + break; - do + var stl = SessionTimeList.Where(x => x.Contains(e.SignalTime.DayOfWeek)).ToList(); + if (stl.Count == 0) { - // 列表为空,表示不处理。这时没有自动重连 - if (SessionTimeList == null || SessionTimeList.Count == 0) - break; + break; + } - bool bTryConnect = true; + bool bTryConnect = true; + TimeSpan ts = e.SignalTime.TimeOfDay; + SessionTimeItem st_current = null; + SessionTimeItem st_next = null; + foreach (var st in stl) + { + // 如果当前时间在交易范围内,要开启重连 + // 如果当前时间不在交易范围内,要主动断开 - SessionTimeItem st_current = null; - SessionTimeItem st_next = null; - foreach (var st in SessionTimeList.ToList()) + if (ts < st.SessionStart) + { + // 停 + bTryConnect = false; + st_next = st; + break; + } + else if (ts <= st.SessionEnd) { - // 如果当前时间在交易范围内,要开启重连 - // 如果当前时间不在交易范围内,要主动断开 - TimeSpan ts = e.SignalTime.TimeOfDay; - if (ts < st.SessionStart) - { - // 停 - bTryConnect = false; - st_next = st; - } - else if (ts <= st.SessionEnd) - { - // 启动 - bTryConnect = true; - st_current = st; - break; - } - else - { - // 停 - bTryConnect = false; - st_next = st; - } + // 启动 + bTryConnect = true; + st_current = st; + break; } + else + { + // 停 + bTryConnect = false; + st_next = st; + } + } - if (bTryConnect) + if (bTryConnect) + { + // 没有连接要连上,有连接要设置时间 + if (!IsConnected) { - // 没有连接要连上,有连接要设置时间 - if (!IsConnected) - { - xlog.Info("当前[{0}]在交易时段[{1}],主动连接", e.SignalTime.TimeOfDay, st_current); - _Connect(false); - } + xlog.Info($"当前[{e.SignalTime.TimeOfDay}]在交易时段[{st_current}],主动连接"); + _Connect(false); + } - // 初始化查询间隔 - SetApiReconnectInterval(_ReconnectInterval); + // 初始化查询间隔 + SetApiReconnectInterval(_ReconnectInterval); - nDisconnectCount = 0; - } - else + nDisconnectCount = 0; + } + else + { + // 关闭查询间隔 + SetApiReconnectInterval(0); + // 由于定时器设置的是20秒,所以这里正好是5分钟显示一次 + if (nDisconnectCount % (3 * 5) == 0) { - // 关闭查询间隔 - SetApiReconnectInterval(0); - // 由于定时器设置的是20秒,所以这里正好是5分钟显示一次 - if (nDisconnectCount % (3*5) == 0) - { - xlog.Info("当前[{0}]在非交易时段,主动断开连接,下次要连接的时段为[{1}]", e.SignalTime.TimeOfDay, st_next); - - // 要断开连接 - _Disconnect(false); - } - ++nDisconnectCount; + xlog.Info($"当前[{e.SignalTime.TimeOfDay}]在非交易时段,主动断开连接,下次要连接的时段为[{st_next}](仅限当日)"); + + // 要断开连接 + _Disconnect(false); } - } while (false); + ++nDisconnectCount; + } + } while (false); + } + + + private int nDisconnectCount = 0; + void _Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + lock (this) + { + _Timer.Enabled = false; + + CheckConnection(e); // 查询持仓和资金 QueryAccountPosition_OnTimer(); @@ -225,16 +235,16 @@ private void OnConnectionStatus_callback(object sender, ConnectionStatus status, { if (userLogin.RawErrorID != 0) { - (sender as XApi).GetLog().Info("{0}:{1}", status, userLogin.ToFormattedStringShort()); + (sender as IXApi).GetLog().Info("{0}:{1}", status, userLogin.ToFormattedStringShort()); } else { - (sender as XApi).GetLog().Info("{0}:{1}", status, userLogin.ToFormattedStringLong()); + (sender as IXApi).GetLog().Info("{0}:{1}", status, userLogin.ToFormattedStringLong()); } } else { - (sender as XApi).GetLog().Info("{0}", status); + (sender as IXApi).GetLog().Info("{0}", status); } switch (status) @@ -259,7 +269,7 @@ private void OnConnectionStatus_Done(object sender, ConnectionStatus status) { if (item.UseType > 0) { - if(!IsApiConnected(item.Api)) + if (!IsApiConnected(item.Api)) { bCheckOk = false; break; @@ -268,10 +278,12 @@ private void OnConnectionStatus_Done(object sender, ConnectionStatus status) } // 每个连接都检查是否连上,如果连上,开始进行一些基本的查询 - if(bCheckOk) + if (bCheckOk) { - base.Status = ProviderStatus.Connected; + // 晚一点通知上层,这个更稳定一些 + // base.Status = ProviderStatus.Connected; + // 这个查询不能太快,否则CTP报错 QueryAccountPositionInstrument_Logined(); } } @@ -284,7 +296,7 @@ private void OnConnectionStatus_Disconnected(object sender, ConnectionStatus sta 3.主动断开连接 4.被动断开,需要重连 */ - switch(base.Status) + switch (base.Status) { case ProviderStatus.Connected: // 以前连接成功了,现在需要试着重连 @@ -302,7 +314,7 @@ private void OnConnectionStatus_Disconnected(object sender, ConnectionStatus sta #region XApi小功能 - private void assign(ApiItem item, XApi api) + private void assign(ApiItem item, IXApi api) { if ((item.UseType & ApiType.MarketData) == ApiType.MarketData) { @@ -334,7 +346,7 @@ private void assign(ApiItem item, XApi api) } } - public XApi GetXApi(ApiType type) + public IXApi GetXApi(ApiType type) { switch (type) { @@ -355,35 +367,26 @@ public XApi GetXApi(ApiType type) } } - private XApi ConnectToApi(ApiItem item) + private IXApi ConnectToApi(ApiItem item) { //lock (this) { DisconnectToApi(item); - XApi api = item.Api; + IXApi api = item.Api; if (api == null) { - api = new XApi(PathHelper.MakeAbsolutePath(item.DllPath)); + api = XApiHelper.CreateInstance(item.TypeName, item.DllPath); item.Api = api; } - api.Server = ServerList[item.Server].ToStruct(); - if (item.UserList.Count > 0) - { - foreach (var it in item.UserList) - { - api.UserList.Add(it.ToStruct()); - } - } - else - { - api.User = UserList[item.User].ToStruct(); - } + string szServerPath = ServerList[item.Server].Path; + string szUserPath = UserList[item.User].Path; + string szUserLabel = UserList[item.User].Label; // 更新Log名字,这样在日志中可以进行识别 - api.Log = LogManager.GetLogger(string.Format("{0}.{1}.{2}", Name, item.LogPrefix, api.User.UserID)); + api.Log = LogManager.GetLogger(string.Format("{0}.{1}.{2}", Name, item.LogPrefix, szUserLabel)); if (api.IsConnected) return api; @@ -412,9 +415,10 @@ private XApi ConnectToApi(ApiItem item) api.OnRspQryHistoricalTicks = OnRspQryHistoricalTicks_callback; api.OnRspQryHistoricalBars = OnRspQryHistoricalBars_callback; - api.OnRspQrySettlementInfo = OnRspQrySettlementInfo; + api.OnRspQrySettlementInfo = OnRspQrySettlementInfo_callback; + api.OnRtnInstrumentStatus = OnRtnInstrumentStatus_callback; - api.Connect(); + api.Connect(szServerPath, szUserPath, Path.GetTempPath()); return api; } @@ -423,21 +427,19 @@ private XApi ConnectToApi(ApiItem item) private void DisconnectToApi(ApiItem item) { - if(item.Api != null) + if (item.Api != null) { // 直接销毁 _DisconnectToApi(item.Api); - - //// 在线程中销毁 - //Task task = Task.Factory.StartNew( - // ()=> { _DisconnectToApi(item.Api); } - // ); + + // 立即断开连接后,登录时自动查资金与持仓的功能在线程中还在运行,会报错 + assign(item, null); item.Api = null; } } - private void _DisconnectToApi(XApi api) + private void _DisconnectToApi(IXApi api) { try { @@ -460,7 +462,7 @@ private void _DisconnectToApi(XApi api) } } - private bool IsApiConnected(XApi api) + private bool IsApiConnected(IXApi api) { return (api != null && api.IsConnected); } @@ -492,17 +494,17 @@ private void SetApiReconnectInterval(int reconnectInterval) #region 其它非关键功能 private void OnRtnError_callback(object sender, ref ErrorField error) { - (sender as XApi).GetLog().Error("OnRtnError:" + error.ToFormattedString()); + (sender as IXApi).GetLog().Error("OnRtnError:" + error.ToFormattedString()); } private void OnLog_callback(object sender, ref LogField log) { - (sender as XApi).GetLog().Info("OnLog:" + log.ToFormattedString()); + (sender as IXApi).GetLog().Info("OnLog:" + log.ToFormattedString()); } private void OnRtnQuoteRequest_callback(object sender, ref QuoteRequestField quoteRequest) { - (sender as XApi).GetLog().Info("OnRtnQuoteRequest:" + quoteRequest.ToFormattedString()); + (sender as IXApi).GetLog().Info("OnRtnQuoteRequest:" + quoteRequest.ToFormattedString()); MarketDataRecord record; if (!marketDataRecords.TryGetValue(quoteRequest.Symbol, out record)) @@ -510,31 +512,55 @@ private void OnRtnQuoteRequest_callback(object sender, ref QuoteRequestField quo return; } - NewsEx news = new NewsEx(DateTime.Now, this.id, record.Instrument.Id, NewsUrgency.Flash, "", "", quoteRequest.ToFormattedString()); - news.ResponseType = XAPI.ResponseType.OnRtnQuoteRequest; - news.UserData = quoteRequest; - EmitData(news); + foreach (var _id in record.Ids) + { + NewsEx news = new NewsEx(DateTime.Now, this.id, _id, NewsUrgency.Flash, "", "", quoteRequest.ToFormattedString()); + + news.ResponseType = XAPI.ResponseType.OnRtnQuoteRequest; + news.UserData = quoteRequest; + EmitData(news); + } } private void QueryAccountPositionInstrument_Logined() { - ReqQueryField query = default(ReqQueryField); + // OnRtnError:[XErrorID = 0; RawErrorID = 90; Text = CTP:查询未就绪,请稍后重试; Source = OnRspError] + Thread thread2 = new Thread(QueryAccountPositionInstrument_Thread); + thread2.Start(); + } + + private void QueryAccountPositionInstrument_Thread() + { + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = DefaultPortfolioID1; query.PortfolioID2 = DefaultPortfolioID2; query.PortfolioID3 = DefaultPortfolioID3; query.Business = DefaultBusiness; + + Thread.Sleep(3000); + // 查合约 + if (IsApiConnected(_ItApi)) + { + _ItApi.ReqQuery(QueryType.ReqQryInstrument, query); + } + // 查持仓,查资金 + Thread.Sleep(3000); if (IsApiConnected(_QueryApi)) { - _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, ref query); - _QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, ref query); + _dictPositions_current.Clear(); + _QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, query); } - // 查合约 - if (IsApiConnected(_ItApi)) + // 晚一点通知上层会不会更稳定一些? + base.Status = ProviderStatus.Connected; + + Thread.Sleep(3000); + if (IsApiConnected(_QueryApi)) { - _ItApi.ReqQuery(QueryType.ReqQryInstrument, ref query); + _dictAccounts_current.Clear(); + _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, query); } } @@ -543,25 +569,27 @@ private void QueryAccountPosition_OnTimer() if (!IsApiConnected(_QueryApi)) return; - ReqQueryField query = default(ReqQueryField); + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = DefaultPortfolioID1; query.PortfolioID2 = DefaultPortfolioID2; query.PortfolioID3 = DefaultPortfolioID3; query.Business = DefaultBusiness; - _QueryAccountCount -= (int)_Timer.Interval / 1000; - if (_QueryAccountCount <= 0) - { - _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, ref query); - _QueryAccountCount = _QueryAccountInterval; - } - _QueryPositionCount -= (int)_Timer.Interval / 1000; if (_QueryPositionCount <= 0) { - _QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, ref query); + _dictPositions_current.Clear(); + _QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, query); _QueryPositionCount = _QueryPositionInterval; } + + _QueryAccountCount -= (int)_Timer.Interval / 1000; + if (_QueryAccountCount <= 0) + { + _dictAccounts_current.Clear(); + _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, query); + _QueryAccountCount = _QueryAccountInterval; + } } #endregion diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.HistoricalData.cs b/QuantBox.APIProvider/Single/SingleProvider.API.HistoricalData.cs similarity index 99% rename from QuantBox.API.Provider/Single/SingleProvider.API.HistoricalData.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.HistoricalData.cs index 998543a..076118b 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.API.HistoricalData.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.API.HistoricalData.cs @@ -155,7 +155,7 @@ private void OnRspQryHistoricalTicks_callback(object sender, IntPtr pTicks, int volume = obj.Volume; } - if(EnableEmitHistoricalData) + if(EnablEmitHistoricalData) { HistoricalData data = new HistoricalData { @@ -217,7 +217,7 @@ private void OnRspQryHistoricalBars_callback(object sender, IntPtr pBars, int si } } - if(EnableEmitHistoricalData) + if(EnablEmitHistoricalData) { HistoricalData data = new HistoricalData { diff --git a/QuantBox.APIProvider/Single/SingleProvider.API.MarketData.cs b/QuantBox.APIProvider/Single/SingleProvider.API.MarketData.cs new file mode 100644 index 0000000..28ba4ce --- /dev/null +++ b/QuantBox.APIProvider/Single/SingleProvider.API.MarketData.cs @@ -0,0 +1,255 @@ +using SmartQuant; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using XAPI.Callback; +using XAPI; +using QuantBox.Extensions; + +namespace QuantBox.APIProvider.Single +{ + public partial class SingleProvider + { + private DateTime _dateTime = DateTime.Now; + private DateTime _exchangeDateTime = DateTime.Now; + + private void OnRtnDepthMarketData_callback(object sender, ref DepthMarketDataNClass pDepthMarketData) + { + if (!_enableEmitData) + return; + + try + { + // 传过来的Symbol,有可能是不带点,有可能是带点的 + // 要同时存成完整版,因为下单时 + MarketDataRecord record; + if (!marketDataRecords.TryGetValue(pDepthMarketData.Symbol, out record)) + { + return; + } + + // 取出上次的行情记录 + DepthMarketDataNClass depthMarket = record.DepthMarket; + + //将更新字典的功能提前,因为如果一开始就OnTrade中下单,涨跌停没有更新 + record.DepthMarket = pDepthMarketData; + + _dateTime = DateTime.Now; + try + { + _exchangeDateTime = pDepthMarketData.ExchangeDateTime(); + } + catch + { + _exchangeDateTime = _dateTime; + (sender as XApi).GetLog().Error("{0} ExchangeDateTime有误,现使用LocalDateTime代替,请找API开发人员处理API中的时间兼容问题。", pDepthMarketData.ToFormattedStringExchangeDateTime()); + } + + + if (_emitBidAskFirst) + { + if (_emitBidAsk) + { + FireBid(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + FireAsk(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + } + FireTrade(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + } + else + { + FireTrade(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + if (_emitBidAsk) + { + FireBid(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + FireAsk(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + } + } + + if (_emitLevel2Snapshot) + { + FireLevel2Snapshot(record.Ids, _dateTime, _exchangeDateTime, pDepthMarketData, depthMarket); + } + } + catch (Exception ex) + { + (sender as XApi).GetLog().Error(ex); + } + } + + private void FireTrade(SortedSet Ids, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) + { + //行情过来时是今天累计成交量,得转换成每个tick中成交量之差 + double volume = pDepthMarketData.Volume - DepthMarket.Volume; + // 以前第一条会导致集合竞价后的第一条没有成交量,这种方法就明确了上一笔是空数据 + if (0 == DepthMarket.TradingDay && 0 == DepthMarket.ActionDay) + { + //没有接收到最开始的一条,所以这计算每个Bar的数据时肯定超大,强行设置为0 + volume = 0; + } + else if (volume < 0) + { + //如果隔夜运行,会出现今早成交量0-昨收盘成交量,出现负数,所以当发现为负时要修改 + volume = pDepthMarketData.Volume; + } + + foreach (var _id in Ids) + { + // 使用新的类,保存更多信息 + var trade = new TradeEx( + _dateTime, + _exchangeDateTime, + id, + _id, + pDepthMarketData.LastPrice, + (int)volume) + { + DepthMarketData = pDepthMarketData + }; + + // 启用底层数据上传 + EmitData(trade); + } + } + + private void FireLevel2Snapshot(SortedSet Ids, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) + { + //行情过来时是今天累计成交量,得转换成每个tick中成交量之差 + double volume = pDepthMarketData.Volume - DepthMarket.Volume; + // 以前第一条会导致集合竞价后的第一条没有成交量,这种方法就明确了上一笔是空数据 + if (0 == DepthMarket.TradingDay && 0 == DepthMarket.ActionDay) + { + //没有接收到最开始的一条,所以这计算每个Bar的数据时肯定超大,强行设置为0 + volume = 0; + } + else if (volume < 0) + { + //如果隔夜运行,会出现今早成交量0-昨收盘成交量,出现负数,所以当发现为负时要修改 + volume = pDepthMarketData.Volume; + } + + foreach (var _id in Ids) + { + List bids = new List(); + if (pDepthMarketData.Bids != null) + { + foreach (var d in pDepthMarketData.Bids) + { + Bid bid = new Bid( + _dateTime, + _exchangeDateTime, + id, + _id, + d.Price, + d.Size); + bids.Add(bid); + } + } + + List asks = new List(); + if (pDepthMarketData.Asks != null) + { + foreach (var d in pDepthMarketData.Asks) + { + Ask ask = new Ask( + _dateTime, + _exchangeDateTime, + id, + _id, + d.Price, + d.Size); + asks.Add(ask); + } + } + + var l2s = new Level2Snapshot(_dateTime, _exchangeDateTime, id, _id, bids.ToArray(), asks.ToArray()) + { + + }; + + // 启用底层数据上传 + EmitData(l2s); + } + } + + private void FireBid(SortedSet Ids, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) + { + double price = 0.0; + int size = 0; + + // 当出现涨跌停时,有可能是一开始就是涨跌停,也有可能慢慢变成涨跌停 + if(pDepthMarketData.Bids == null || pDepthMarketData.Bids.Length == 0) + { + } + else + { + if (DepthMarket.Bids != null && DepthMarket.Bids.Length > 0) + { + if (DepthMarket.Bids[0].Size == pDepthMarketData.Bids[0].Size + && DepthMarket.Bids[0].Price == pDepthMarketData.Bids[0].Price) + { + // 由于与上次一样,不能动 + return; + } + } + + price = pDepthMarketData.Bids[0].Price; + size = pDepthMarketData.Bids[0].Size; + } + + foreach (var _id in Ids) + { + Bid bid = new Bid( + _dateTime, + _exchangeDateTime, + id, + _id, + price, + size); + + EmitData(bid); + } + } + + private void FireAsk(SortedSet Ids, DateTime _dateTime, DateTime _exchangeDateTime, DepthMarketDataNClass pDepthMarketData, DepthMarketDataNClass DepthMarket) + { + double price = 0.0; + int size = 0; + + // 当出现涨跌停时,有可能是一开始就是涨跌停,也有可能慢慢变成涨跌停 + if (pDepthMarketData.Asks == null || pDepthMarketData.Asks.Length == 0) + { + } + else + { + if (DepthMarket.Asks != null && DepthMarket.Asks.Length > 0) + { + if (DepthMarket.Asks[0].Size == pDepthMarketData.Asks[0].Size + && DepthMarket.Asks[0].Price == pDepthMarketData.Asks[0].Price) + { + // 由于与上次一样,不能动 + return; + } + } + + price = pDepthMarketData.Asks[0].Price; + size = pDepthMarketData.Asks[0].Size; + } + + foreach (var _id in Ids) + { + Ask ask = new Ask( + _dateTime, + _exchangeDateTime, + id, + _id, + price, + size); + + EmitData(ask); + } + } + } +} diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.Order.cs b/QuantBox.APIProvider/Single/SingleProvider.API.Order.cs similarity index 73% rename from QuantBox.API.Provider/Single/SingleProvider.API.Order.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Order.cs index 64b5a64..81f111d 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.API.Order.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.API.Order.cs @@ -1,15 +1,10 @@ using SmartQuant; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; using QuantBox.Extensions; using XAPI.Callback; using XAPI; -using Newtonsoft.Json; using SQ = SmartQuant; @@ -18,47 +13,7 @@ namespace QuantBox.APIProvider.Single public partial class SingleProvider { #region 价格修正 - //public double FixPrice(double price, SmartQuant.OrderSide Side, double tickSize, double LowerLimitPrice, double UpperLimitPrice) - //{ - // //没有设置就直接用 - // if (tickSize > 0) - // { - // decimal remainder = ((decimal)price % (decimal)tickSize); - // if (remainder != 0) - // { - // if (Side == SmartQuant.OrderSide.Buy) - // { - // price = Math.Round(Math.Ceiling(price / tickSize) * tickSize, 6); - // } - // else - // { - // price = Math.Round(Math.Floor(price / tickSize) * tickSize, 6); - // } - // } - // else - // { - // //正好能整除,不操作 - // } - // } - - // if (0 == UpperLimitPrice - // && 0 == LowerLimitPrice) - // { - // //涨跌停无效 - - // } - // else - // { - // //防止价格超过涨跌停 - // if (price >= UpperLimitPrice) - // price = UpperLimitPrice; - // else if (price <= LowerLimitPrice) - // price = LowerLimitPrice; - // } - // return price; - //} - - public double FixPrice(MarketDataRecord record,double price, SmartQuant.OrderSide Side, double tickSize) + public double FixPrice(MarketDataRecord record, double price, SmartQuant.OrderSide Side, double tickSize) { double LowerLimitPrice = record.DepthMarket.LowerLimitPrice; double UpperLimitPrice = record.DepthMarket.UpperLimitPrice; @@ -89,7 +44,7 @@ public double FixPrice(MarketDataRecord record,double price, SmartQuant.OrderSid { //涨跌停无效 _TdApi.GetLog().Warn("Symbol:{0},Symbol_Dot_Exchange:{1},LowerLimitPrice && UpperLimitPrice 为0,没有进行价格修正", - record.Symbol,record.Symbol_Dot_Exchange); + record.Symbol, record.Symbol_Dot_Exchange); } else { @@ -183,11 +138,13 @@ private void CmdNewOrderSingle(ExecutionCommand command) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(command.Instrument, + GetApi_Symbol_Exchange_TickSize(command.Instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); OrderField[] fields = new OrderField[1]; @@ -198,7 +155,7 @@ private void CmdNewOrderSingle(ExecutionCommand command) MarketDataRecord record; if (marketDataRecords.TryGetValue(command.Instrument.Symbol, out record)) { - switch(command.OrdType) + switch (command.OrdType) { case SQ.OrderType.Market: case SQ.OrderType.MarketOnClose: @@ -215,7 +172,7 @@ private void CmdNewOrderSingle(ExecutionCommand command) } // 市价单使用限价单模拟 - if(SwitchMakertOrderToLimitOrder) + if (SwitchMakertOrderToLimitOrder) { fields[0].Type = XAPI.OrderType.Limit; } @@ -224,10 +181,9 @@ private void CmdNewOrderSingle(ExecutionCommand command) } if (HasPriceLimit) { - //price = FixPrice(price, command.Side, apiTickSize, record.DepthMarket.LowerLimitPrice, record.DepthMarket.UpperLimitPrice); - price = FixPrice(record,price, command.Side, apiTickSize); + price = FixPrice(record, price, command.Side, apiTickSize); } - + fields[0].Price = price; } @@ -247,11 +203,13 @@ private void CmdNewOrderList(ExecutionCommand command) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(orders[i].Instrument, + GetApi_Symbol_Exchange_TickSize(command.Instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); ToOrderStruct(ref fields[i], orders[i], apiSymbol, apiExchange); } @@ -261,6 +219,8 @@ private void CmdNewOrderList(ExecutionCommand command) private void ToOrderStruct(ref OrderField field, Order order, string apiSymbol, string apiExchange) { + field = new OrderField(); + field.InstrumentID = apiSymbol; field.ExchangeID = apiExchange; field.Price = order.Price; @@ -278,31 +238,71 @@ private void ToOrderStruct(ref OrderField field, Order order, string apiSymbol, field.PortfolioID2 = GetPortfolioID2(order); field.PortfolioID3 = GetPortfolioID3(order); field.Business = GetBusiness(order); + + //OpenClose2SubSide(ref field, order); } private void OnRtnOrder_callback(object sender, ref OrderField order) { - (sender as XApi).GetLog().Debug("OnRtnOrder:" + order.ToFormattedString()); - try + lock (this) { - orderMap.Process(ref order); - } - catch (Exception ex) - { - (sender as XApi).GetLog().Error(ex); + var log = (sender as XApi).GetLog(); + log.Debug("OnRtnOrder:" + order.ToFormattedString()); + + // 由策略来收回报 + if (OnRtnOrder != null) + OnRtnOrder(sender, ref order); + + try + { + orderMap.Process(ref order, log); + } + catch (Exception ex) + { + log.Error(ex); + } } } private void OnRtnTrade_callback(object sender, ref TradeField trade) { - (sender as XApi).GetLog().Debug("OnRtnTrade:" + trade.ToFormattedString()); - try + lock (this) { - orderMap.Process(ref trade); + var log = (sender as XApi).GetLog(); + log.Debug("OnRtnTrade:" + trade.ToFormattedString()); + + // 由策略来收回报 + if (OnRtnTrade != null) + OnRtnTrade(sender, ref trade); + + try + { + orderMap.Process(ref trade, log); + } + catch (Exception ex) + { + log.Error(ex); + } } - catch (Exception ex) + } + + private void OnRtnOrder_Expired(ref InstrumentStatusField instrumentStatus) + { + if (_TdApi == null) + return; + + lock (this) { - (sender as XApi).GetLog().Error(ex); + var log = _TdApi.GetLog(); + + try + { + orderMap.ProcessExpired(ref instrumentStatus, log); + } + catch (Exception ex) + { + log.Error(ex); + } } } } diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.Quote.cs b/QuantBox.APIProvider/Single/SingleProvider.API.Quote.cs similarity index 85% rename from QuantBox.API.Provider/Single/SingleProvider.API.Quote.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Quote.cs index 7ce52ad..542d95e 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.API.Quote.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.API.Quote.cs @@ -1,13 +1,6 @@ -using Newtonsoft.Json; -using NLog; -using QuantBox.Extensions; +using QuantBox.Extensions; using SmartQuant; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; using XAPI; using XAPI.Callback; @@ -47,11 +40,13 @@ private void CmdNewQuote(ExecutionCommand command) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(command.Instrument, + GetApi_Symbol_Exchange_TickSize(command.Instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); Order bidOrder; Order askOrder; @@ -69,10 +64,12 @@ private void CmdCancelQuote(ExecutionCommand command) private void OnRtnQuote_callback(object sender, ref QuoteField quote) { (sender as XApi).GetLog().Debug("OnRtnQuote:" + quote.ToFormattedString()); - try { + try + { quoteMap.Process(ref quote); } - catch (Exception ex) { + catch (Exception ex) + { (sender as XApi).GetLog().Error(ex); } } diff --git a/QuantBox.APIProvider/Single/SingleProvider.API.cs b/QuantBox.APIProvider/Single/SingleProvider.API.cs new file mode 100644 index 0000000..06a3e48 --- /dev/null +++ b/QuantBox.APIProvider/Single/SingleProvider.API.cs @@ -0,0 +1,425 @@ +using SmartQuant; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using XAPI.Callback; +using XAPI; +using NLog; +using System.Reflection; +using QuantBox.Extensions; +using System.IO; + +namespace QuantBox.APIProvider.Single +{ + public partial class SingleProvider + { + static SingleProvider() + { + NLog.LogManager.LoadConfiguration(Path.Combine(PathHelper.RootPath.LocalPath, "NLog.config")); + } + public DelegateOnRspQryInvestorPosition OnRspQryInvestorPosition { get; set; } + public DelegateOnRspQryTradingAccount OnRspQryTradingAccount { get; set; } + public DelegateOnRtnInstrumentStatus OnRtnInstrumentStatus { get; set; } + public DelegateOnRtnOrder OnRtnOrder { get; set; } + public DelegateOnRtnTrade OnRtnTrade { get; set; } + + public DelegateOnRspQryOrder OnRspQryOrder { get; set; } + public DelegateOnRspQryTrade OnRspQryTrade { get; set; } + + //记录合约列表,从实盘合约名到对象的映射 + private readonly Dictionary _dictInstruments = new Dictionary(); + // 品种交易状态 + private readonly Dictionary _dictInstrumentsStatus = new Dictionary(); + + // 账号信息 + private SortedDictionary _dictAccounts_current = new SortedDictionary(); + private SortedDictionary _dictAccounts_last = new SortedDictionary(); + + // 持仓信息 + private SortedDictionary _dictPositions_current = new SortedDictionary(); + private SortedDictionary _dictPositions_last = new SortedDictionary(); + + public static int GetDate(DateTime dt) + { + return dt.Year * 10000 + dt.Month * 100 + dt.Day; + } + + public static int GetTime(DateTime dt) + { + return dt.Hour * 10000 + dt.Minute * 100 + dt.Second; + } + + public static DateTime GetDateTime(int yyyyMMdd, int hhmmss, int Millisecond) + { + int yyyy = yyyyMMdd / 10000; + int MM = yyyyMMdd % 10000 / 100; + int dd = yyyyMMdd % 100; + int hh = hhmmss / 10000; + int mm = hhmmss % 10000 / 100; + int ss = hhmmss % 100; + DateTime dt = new DateTime(yyyy, MM, dd, hh, mm, ss, Millisecond); + return dt; + } + + private void OnRspQryInstrument_callback(object sender, ref InstrumentField instrument, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryInstrument"); + return; + } + + _dictInstruments[instrument.Symbol] = instrument; + + if (bIsLast) + { + (sender as XApi).GetLog().Info("合约列表已经接收完成,共 {0} 条", _dictInstruments.Count); + } + } + + private void OnRspQryTradingAccount_callback(object sender, ref AccountField account, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryTradingAccount"); + } + else + { + if (IsLogOnRspQryTradingAccount) + (sender as XApi).GetLog().Info("OnRspQryTradingAccount:" + account.ToFormattedString()); + } + + // 由策略来收回报 + if (OnRspQryTradingAccount != null) + OnRspQryTradingAccount(sender, ref account, size1, bIsLast); + + if (size1 > 0) + { + _dictAccounts_current[account.AccountID] = account; + } + + if (!bIsLast) + return; + + var list = MergeAccounts(_dictAccounts_current, _dictAccounts_last); + alog.Info(string.Join("\n", list)); + + _dictAccounts_last = _dictAccounts_current; + _dictAccounts_current = new SortedDictionary(); + + if (!IsConnected) + return; + + foreach (var acc in _dictAccounts_last.Values) + { + string currency = "CNY"; + + AccountData ad = new AccountData(DateTime.Now, AccountDataType.AccountValue, + acc.AccountID, this.id, this.id); + + Type type = typeof(AccountField); + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (FieldInfo field in fields) + { + ad.Fields.Add(field.Name, currency, field.GetValue(acc)); + } + // 将对像完全设置进去,等着取出 + ad.Fields.Add(AccountDataFieldEx.USER_DATA, currency, acc); + ad.Fields.Add(AccountDataFieldEx.DATE, currency, GetDate(DateTime.Today)); + + try + { + EmitAccountData(ad); + } + catch (Exception ex) + { + (sender as XApi).GetLog().Error(ex); + } + } + } + + private List MergeAccounts(SortedDictionary dict_curr, SortedDictionary dict_last) + { + var list = new List(); + // 交集 + { + var keys = dict_curr.Keys.Intersect(dict_last.Keys); + foreach (var key in keys) + { + var curr = dict_curr[key]; + var last = dict_last[key]; + + list.Add(AccountMsg_Long(curr, last)); + } + } + + // 差集,表示新增 + { + var keys = dict_curr.Keys.Except(dict_last.Keys); + foreach (var key in keys) + { + var curr = dict_curr[key]; + var last = new AccountField(); + + list.Add(AccountMsg_Long(curr, last)); + } + } + return list; + + } + + private void OnRspQryInvestor_callback(object sender, ref InvestorField investor, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryInvestor"); + return; + } + + (sender as XApi).GetLog().Info("OnRspQryInvestor:{0}", investor.ToFormattedString()); + } + + private void OnRspQryInvestorPosition_callback(object sender, ref PositionField position, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryInvestorPosition"); + } + else if (position.Position != 0) + { + // UFX中已经过期的持仓也会推送,所以这里过滤一下不显示 + if (IsLogOnRspQryInvestorPosition) + (sender as XApi).GetLog().Info("OnRspQryInvestorPosition:" + position.ToFormattedString()); + } + + // 由策略来收回报 + if (OnRspQryInvestorPosition != null) + OnRspQryInvestorPosition(sender, ref position, size1, bIsLast); + + // 需要保证从上一个查询到现在,列表中的数据是本次查询的所有数据 + if (size1 > 0) + { + _dictPositions_current[position.ID] = position; + } + + if (!bIsLast) + return; + + PositionsMsg_Long(_dictPositions_current); + + // 比较两次容器的区别 + var list = MergePositions(_dictPositions_current, _dictPositions_last); + _dictPositions_last = _dictPositions_current; + _dictPositions_current = new SortedDictionary(); + + if (!IsConnected) + return; + + // 没有持仓通知的合约,也通知为0 + foreach (var pos in list) + { + PositionFieldEx item; + if (!positions.TryGetValue(pos.Symbol, out item)) + { + item = new PositionFieldEx(); + positions[pos.Symbol] = item; + } + item.AddPosition(pos); + + AccountData ad = new AccountData(DateTime.Now, AccountDataType.Position, + pos.AccountID, this.id, this.id); + + ad.Fields.Add(AccountDataField.SYMBOL, item.Symbol); + ad.Fields.Add(AccountDataField.EXCHANGE, item.Exchange); + ad.Fields.Add(AccountDataField.QTY, item.Qty); + ad.Fields.Add(AccountDataField.LONG_QTY, item.LongQty); + ad.Fields.Add(AccountDataField.SHORT_QTY, item.ShortQty); + + ad.Fields.Add(AccountDataFieldEx.USER_DATA, item); + ad.Fields.Add(AccountDataFieldEx.DATE, GetDate(DateTime.Today)); + + try + { + EmitAccountData(ad); + } + catch (Exception ex) + { + (sender as XApi).GetLog().Error(ex); + } + } + } + + private List MergePositions(SortedDictionary dict_curr, SortedDictionary dict_last) + { + // 由于可能收不到接口推送的持仓已经清空的消息,所以只能使用两次进行比较 + var list = new List(); + + // 新的全部添加 + foreach (var v in dict_curr.Values) + { + list.Add(v); + } + + // 老的需要更改数据为0 + { + var keys = dict_last.Keys.Except(dict_curr.Keys); + foreach (var key in keys) + { + var last = dict_last[key]; + // 修改时间 + last.Position = 0; + list.Add(last); + } + } + + return list; + } + + private void OnRspQrySettlementInfo_callback(object sender, ref SettlementInfoClass settlementInfo, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQrySettlementInfo"); + return; + } + + if (bIsLast) + { + (sender as XApi).GetLog().Info("OnRspQrySettlementInfo:" + Environment.NewLine + settlementInfo.Content); + } + } + + + private void OnRspQryQuote_callback(object sender, ref QuoteField quote, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryQuote"); + return; + } + + (sender as XApi).GetLog().Info("OnRspQryQuote:" + quote.ToFormattedString()); + } + + private void OnRspQryTrade_callback(object sender, ref TradeField trade, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryTrade"); + + } + else + { + (sender as XApi).GetLog().Info("OnRspQryTrade:" + trade.ToFormattedString()); + } + if (OnRspQryTrade != null) + OnRspQryTrade(this, ref trade, size1, bIsLast); + } + + private void OnRspQryOrder_callback(object sender, ref OrderField order, int size1, bool bIsLast) + { + if (size1 <= 0) + { + (sender as XApi).GetLog().Info("OnRspQryOrder"); + } + else + { + (sender as XApi).GetLog().Info("OnRspQryOrder:" + order.ToFormattedString()); + } + if (OnRspQryOrder != null) + OnRspQryOrder(this, ref order, size1, bIsLast); + } + + private void OnRtnInstrumentStatus_callback(object sender, ref InstrumentStatusField instrumentStatus) + { + if (OnRtnInstrumentStatus != null) + OnRtnInstrumentStatus(sender, ref instrumentStatus); + + // 记录下来,后期可能要用到 + _dictInstrumentsStatus[instrumentStatus.Symbol] = instrumentStatus; + + // 考虑收盘后,主动为一些合约标记成过期 + OnRtnOrder_Expired(ref instrumentStatus); + + // 合约状态信息太多了,也不关心,这里屏蔽显示 + if (IsLogOnRtnInstrumentStatus) + (sender as XApi).GetLog().Info("OnRtnInstrumentStatus:" + instrumentStatus.ToFormattedString()); + } + + public InstrumentStatusField GetInstrumentStatus(string symbol) + { + InstrumentStatusField instrumentStatus; + if (_dictInstrumentsStatus.TryGetValue(symbol, out instrumentStatus)) + { + return instrumentStatus; + } + return instrumentStatus; + } + + private string AccountMsg_Long(AccountField current, AccountField last) + { + double risk = current.CurrMargin * 100.0 / current.Balance; + double balance_1 = (current.Balance - current.Deposit + current.Withdraw) - current.PreBalance; + double balance_2 = current.Balance - last.Balance; + + string str = $"{risk:F2}%/{current.PositionProfit:F0}/{balance_1:F0}/{balance_2:F0}" + + $"\n{current.CloseProfit:F0}/{current.Commission:F0}/{current.Available:F0}" + + $"\n风险度/持仓盈亏/日间权益差/区间权益差" + + $"\n平仓盈亏/手续费/可用资金\n" + + $"\n{current.Withdraw:F0}/{current.Deposit:F0}" + + $"\n出/入金" + + $"\n" + + $"\n{current.Balance:F0}-*+*-{current.PreBalance:F0}=*" + + $"\n(动态权益-入金+出金)-昨结权益=日间权益差" + + $"\n动态权益-上期动态权益=区间权益差" + + $"\n" + + $"\n{current.CurrMargin:F0}/*=*" + + $"\n占用保证金/动态权益=风险度" + + $"\n" + + $"\n>>AccountID:{current.AccountID}<<" + + $"\n>>{DateTime.Now.ToLongTimeString()}<<"; + + return str; + } + + private string AccountMsg_Short(AccountField current, AccountField last) + { + double risk = current.CurrMargin * 100.0 / current.Balance; + double balance_1 = (current.Balance - current.Deposit + current.Withdraw) - current.PreBalance; + double balance_2 = current.Balance - last.Balance; + + string str = ""; + + str += string.Format("{0:F2}%/{1:F0}/{2:F0}/{3:F0}", risk, current.PositionProfit, balance_1, balance_2); + str += string.Format("\n{0:F0}/{1:F0}/{2:F0}", current.CloseProfit, current.Commission, current.Available); + str += string.Format("\n{0:F0}", current.Balance); + str += string.Format("\n风险度/持仓盈亏/日间权益差/区间权益差"); + str += string.Format("\n平仓盈亏/手续费/可用资金"); + str += string.Format("\n动态权益"); + str += string.Format("\n>>AccountID:{0}<<", current.AccountID); + + return str; + } + + private string PositionsMsg_Long(SortedDictionary positions) + { + if (positions.Count == 0) + return null; + + string str = ""; + foreach (var p in positions.Values) + { + str += $"{p.Symbol},{p.Side.ToString()},{p.HistoryPosition}+{p.TodayPosition}={p.Position}\n"; + + } + str += $"\n合约,多空,昨+今=总"; + str += $"\n>>{DateTime.Now.ToLongTimeString()}<<"; + plog.Info(str); + + return str; + } + } +} diff --git a/QuantBox.API.Provider/Single/SingleProvider.DataProvider.cs b/QuantBox.APIProvider/Single/SingleProvider.DataProvider.cs similarity index 64% rename from QuantBox.API.Provider/Single/SingleProvider.DataProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.DataProvider.cs index 6d93f1b..caa93bf 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.DataProvider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.DataProvider.cs @@ -16,7 +16,7 @@ private void SubscribeForTest(Instrument instrument) { Bid bid = new Bid(DateTime.Now, this.id, instrument.Id, 100, 5); EmitData(bid); - + Ask ask = new Ask(DateTime.Now, this.id, instrument.Id, 101, 5); EmitData(ask); } @@ -40,21 +40,30 @@ public override void Subscribe(Instrument instrument) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(instrument, + GetApi_Symbol_Exchange_TickSize(instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); + // 如果是对CTP接口,使用UFX的参数进行订阅是否有问题?IF1802.7,目前猜没有问题 string Symbol_Dot_Exchange = string.Format("{0}.{1}", apiSymbol, apiExchange); string Symbol_Dot = string.Format("{0}.", apiSymbol); string Symbol = apiSymbol; MarketDataRecord record; + if (!marketDataRecords.TryGetValue(Symbol_Dot_Exchange, out record)) { - record = new MarketDataRecord(instrument); - record.Symbol = Symbol; + record = new MarketDataRecord(); + + record.TradeRequested = true; + record.QuoteRequested = true; + record.MarketDepthRequested = true; + + record.Symbol = apiSymbol; record.Exchange = apiExchange; record.Symbol_Dot = Symbol_Dot; record.Symbol_Dot_Exchange = Symbol_Dot_Exchange; @@ -63,13 +72,15 @@ public override void Subscribe(Instrument instrument) marketDataRecords[Symbol_Dot_Exchange] = record; marketDataRecords[Symbol_Dot] = record; marketDataRecords[Symbol] = record; - } - - record.TradeRequested = true; - record.QuoteRequested = true; - record.MarketDepthRequested = true; - Subscribe(record); + record.Instrument = instrument.Symbol; + record.Ids.Add(instrument.Id); + Subscribe(record); + } + else + { + record.Ids.Add(instrument.Id); + } } public override void Unsubscribe(Instrument instrument) @@ -79,11 +90,13 @@ public override void Unsubscribe(Instrument instrument) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(instrument, + GetApi_Symbol_Exchange_TickSize(instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); string Symbol_Dot_Exchange = string.Format("{0}.{1}", apiSymbol, apiExchange); string Symbol_Dot = string.Format("{0}.", apiSymbol); @@ -93,25 +106,30 @@ public override void Unsubscribe(Instrument instrument) MarketDataRecord record; if (marketDataRecords.TryGetValue(Symbol_Dot_Exchange, out record)) { - Unsubscribe(record); - - // 多次订阅也无所谓 - record.TradeRequested = false; - record.QuoteRequested = false; - record.MarketDepthRequested = false; + record.Ids.Remove(instrument.Id); + + if (record.Ids.Count == 0) + { + Unsubscribe(record); + + // 多次订阅也无所谓 + record.TradeRequested = false; + record.QuoteRequested = false; + record.MarketDepthRequested = false; + + // 移除 + marketDataRecords.Remove(Symbol_Dot_Exchange); + marketDataRecords.Remove(Symbol_Dot); + marketDataRecords.Remove(Symbol); + } } - - // 移除 - marketDataRecords.Remove(Symbol_Dot_Exchange); - marketDataRecords.Remove(Symbol_Dot); - marketDataRecords.Remove(Symbol); } private void Subscribe(MarketDataRecord record) { if (IsApiConnected(_MdApi)) { - _MdApi.GetLog().Info("订阅合约 {0} {1} {2}", record.Instrument.Symbol, record.Symbol, record.Exchange); + _MdApi.GetLog().Info("订阅合约:Symbol:{0};InstrumentID:{1};ExchangeID:{2}", record.Instrument, record.Symbol, record.Exchange); _MdApi.Subscribe(record.Symbol, record.Exchange); } else @@ -128,15 +146,16 @@ private void Subscribe(MarketDataRecord record) private void Unsubscribe(MarketDataRecord record) { - if (_MdApi != null) + if (IsApiConnected(_MdApi)) { - _MdApi.GetLog().Info("退订合约 {0} {1} {2}", record.Instrument.Symbol, record.Symbol, record.Exchange); + _MdApi.GetLog().Info("退订合约:Symbol:{0};InstrumentID:{1};ExchangeID:{2}", record.Instrument, record.Symbol, record.Exchange); _MdApi.Unsubscribe(record.Symbol, record.Exchange); } - if (_QuoteRequestApi != null && SubscribeQuote) + if (SubscribeQuote) { - _QuoteRequestApi.UnsubscribeQuote(record.Symbol, record.Exchange); + if (IsApiConnected(_QuoteRequestApi)) + _QuoteRequestApi.UnsubscribeQuote(record.Symbol, record.Exchange); } } } diff --git a/QuantBox.API.Provider/Single/SingleProvider.ExecutionProvider.cs b/QuantBox.APIProvider/Single/SingleProvider.ExecutionProvider.cs similarity index 100% rename from QuantBox.API.Provider/Single/SingleProvider.ExecutionProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.ExecutionProvider.cs diff --git a/QuantBox.API.Provider/Single/SingleProvider.HistoricalDataProvider.cs b/QuantBox.APIProvider/Single/SingleProvider.HistoricalDataProvider.cs similarity index 89% rename from QuantBox.API.Provider/Single/SingleProvider.HistoricalDataProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.HistoricalDataProvider.cs index e111a04..031ce5c 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.HistoricalDataProvider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.HistoricalDataProvider.cs @@ -19,11 +19,13 @@ private HistoricalDataRequestField ToStruct(HistoricalDataRequest request) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(request.Instrument, + GetApi_Symbol_Exchange_TickSize(request.Instrument, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); HistoricalDataRequestField field = new HistoricalDataRequestField(); field.Symbol = request.Instrument.Symbol; @@ -34,9 +36,9 @@ private HistoricalDataRequestField ToStruct(HistoricalDataRequest request) field.Time1 = GetTime(request.DateTime1); field.Time2 = GetTime(request.DateTime2); field.DataType = (XAPI.DataObjetType)request.DataType; - if(request.BarType.HasValue) + if (request.BarType.HasValue) field.BarType = (XAPI.BarType)request.BarType.Value; - if(request.BarSize.HasValue) + if (request.BarSize.HasValue) field.BarSize = (int)request.BarSize.Value; //field.RequestId; //field.Count; @@ -52,7 +54,7 @@ void IHistoricalDataProvider.Cancel(string requestId) } public override void Send(HistoricalDataRequest request) { - if(!IsApiConnected(_HdApi)) + if (!IsApiConnected(_HdApi)) { EmitHistoricalDataEnd(request.RequestId, RequestResult.Error, "Provider is not connected."); xlog.Error("历史行情服务器没有连接"); @@ -60,7 +62,7 @@ public override void Send(HistoricalDataRequest request) } int iRet = 1; - switch(request.DataType) + switch (request.DataType) { case DataObjectType.Bid: case DataObjectType.Ask: diff --git a/QuantBox.API.Provider/Single/SingleProvider.InstrumentProvider.cs b/QuantBox.APIProvider/Single/SingleProvider.InstrumentProvider.cs similarity index 93% rename from QuantBox.API.Provider/Single/SingleProvider.InstrumentProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.InstrumentProvider.cs index 68dc26c..f6794c0 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.InstrumentProvider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.InstrumentProvider.cs @@ -20,7 +20,7 @@ public override void Send(InstrumentDefinitionRequest request) private void ReturnInstrumentDefinition(InstrumentDefinitionRequest request) { - Dictionary> dict = new Dictionary>(); + Dictionary> dict = new Dictionary>(); List instruments = new List(); @@ -92,19 +92,19 @@ private void ReturnInstrumentDefinition(InstrumentDefinitionRequest request) description.Underlying = contract.UnderlyingInstrID; instrument.Description = JsonConvert.SerializeObject(description, description.GetType(), null); - - if(!string.IsNullOrWhiteSpace(contract.UnderlyingInstrID)) + + if (!string.IsNullOrWhiteSpace(contract.UnderlyingInstrID)) { // 要求先导入标的合约,再导入期权,如果在这里生成会丢失合约信息 Instrument UnderlyingInstrID = framework.InstrumentManager.Get(contract.UnderlyingInstrID); - if(UnderlyingInstrID == null) + if (UnderlyingInstrID == null) { //xlog.Warn("合约:{0},存在标的物字段,请先导入标的物合约{1},导入后放在Parent属性中", contract.Symbol, contract.UnderlyingInstrID); List list = null; - if(!dict.TryGetValue(contract.UnderlyingInstrID,out list)) + if (!dict.TryGetValue(contract.UnderlyingInstrID, out list)) { list = new List(); - dict.Add(contract.UnderlyingInstrID,list); + dict.Add(contract.UnderlyingInstrID, list); } list.Add(contract.Symbol); } @@ -120,6 +120,8 @@ private void ReturnInstrumentDefinition(InstrumentDefinitionRequest request) int yyyy = contract.ExpireDate / 10000; int MM = contract.ExpireDate % 10000 / 100; int dd = contract.ExpireDate % 100; + // 居然有期权传回来的到期时间没有日 + dd = Math.Max(dd, 1); instrument.Maturity = new DateTime(yyyy, MM, dd); } @@ -131,7 +133,7 @@ private void ReturnInstrumentDefinition(InstrumentDefinitionRequest request) instruments.Add(instrument); } - if(dict.Count>0) + if (dict.Count > 0) { xlog.Warn("标的物合约必须先导入,然后再Request,衍生品合约的Legs才会有一条记录指向标的物"); foreach (var kv in dict) @@ -141,7 +143,7 @@ private void ReturnInstrumentDefinition(InstrumentDefinitionRequest request) xlog.Warn("请将以上合约先导入"); xlog.Warn("如果在Request后列表中没有标的物合约,则需要手工添加"); } - + instruments.Sort(SortInstrument); diff --git a/QuantBox.APIProvider/Single/SingleProvider.Other.cs b/QuantBox.APIProvider/Single/SingleProvider.Other.cs new file mode 100644 index 0000000..137b9fc --- /dev/null +++ b/QuantBox.APIProvider/Single/SingleProvider.Other.cs @@ -0,0 +1,45 @@ +using XAPI; +using SmartQuant; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuantBox.APIProvider.Single +{ + public partial class SingleProvider + { + // 得到API中的合约名与交易所 + public void GetApi_Symbol_Exchange_TickSize(Instrument instrument, byte id, + out string altSymbol, out string altExchange, + out string apiSymbol, out string apiExchange, + out double apiTickSize, + out string apiProductID) + { + // 取合约别名 + altSymbol = instrument.GetSymbol(id); + altExchange = instrument.GetExchange(id); + apiTickSize = instrument.TickSize; + + // 取合约在API中的名字 + apiSymbol = altSymbol; + apiExchange = altExchange; + apiProductID = ""; + + // 对于UFX,没有实现查询合约的功能,所以这里其实使用的是AltID中的信息 + // 屏蔽这个功能,订阅的合约就根据设置来了 + if(true) + { + InstrumentField _Instrument; + if (_dictInstruments.TryGetValue(altSymbol, out _Instrument)) + { + apiSymbol = _Instrument.InstrumentID; + apiExchange = _Instrument.ExchangeID; + apiTickSize = _Instrument.PriceTick; + apiProductID = _Instrument.ProductID; + } + } + } + } +} diff --git a/QuantBox.API.Provider/Single/SingleProvider.Provider.cs b/QuantBox.APIProvider/Single/SingleProvider.Provider.cs similarity index 92% rename from QuantBox.API.Provider/Single/SingleProvider.Provider.cs rename to QuantBox.APIProvider/Single/SingleProvider.Provider.cs index 8b9694a..9ef770a 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.Provider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.Provider.cs @@ -11,14 +11,13 @@ using XAPI; using XAPI.Callback; -//using System.Threading.Tasks.Dataflow; - - namespace QuantBox.APIProvider.Single { public partial class SingleProvider:Provider { private Logger xlog; + private Logger alog; + private Logger plog; private Logger barLog; private Logger tickLog; @@ -38,7 +37,8 @@ public SingleProvider(Framework framework) ~SingleProvider() { - Save(); + // OQ多开时,其中一个改了,另一个关闭会导致保存丢失 + // Save(); } private static JsonSerializerSettings jSetting = new JsonSerializerSettings() @@ -58,14 +58,17 @@ public void Init(byte id, string name) // 只是简单设置,等登录时将把账号设置上,X日志由于一些信息没法 xlog = LogManager.GetLogger(Name + ".X"); + alog = LogManager.GetLogger(Name + ".A"); + plog = LogManager.GetLogger(Name + ".P"); barLog = LogManager.GetLogger("Bar"); tickLog = LogManager.GetLogger("Tick"); // 以下初始化的值在,初始化后由软件读取文件中的参数据后又设置回来 LastPricePlusNTicks = 10; EmitBidAsk = true; + EmitLevel2Snapshot = false; //UpdateInstrument = true; - EnableEmitHistoricalData = true; + EnablEmitHistoricalData = true; FilterDateTime = true; EnableEmitData = true; HasPriceLimit = true; @@ -139,7 +142,7 @@ private void Save(string path,string file,object obj) } } - internal void Save() + public void Save() { Save(ConfigPath, "SessionTimeList.json", SessionTimeList); Save(ConfigPath, "ServerList.json", ServerList); @@ -147,7 +150,7 @@ internal void Save() Save(ConfigPath, "ApiList.json", ApiList); } - private void Load() + public void Load() { SessionTimeList = new BindingList(); UserList = new BindingList(); @@ -188,22 +191,24 @@ private void Load() private int _QueryAccountCount = 0; private int _QueryPositionCount = 0; - public override void Connect() + protected override void OnConnect() { + Load(); + _QueryAccountCount = _QueryAccountInterval; _QueryPositionCount = _QueryPositionInterval; // 启动重连定时器 _Timer.Elapsed -= _Timer_Elapsed; // 改小用来测试连接销毁,用完要改回去 - _Timer.Interval = 20 * 1000; + _Timer.Interval = 30 * 1000; _Timer.Enabled = true; _Timer.Elapsed += _Timer_Elapsed; xlog.Info("重连检测定时器开启,检测频率(毫秒):{0}", _Timer.Interval); _Connect(true); } - public override void Disconnect() + protected override void OnDisconnect() { // 关闭重连定时器 _Timer.Elapsed -= _Timer_Elapsed; @@ -212,6 +217,8 @@ public override void Disconnect() xlog.Info("重连检测定时器关闭"); _Disconnect(true); + + Save(); } public override void Clear() diff --git a/QuantBox.API.Provider/Single/SingleProvider.Settings.cs b/QuantBox.APIProvider/Single/SingleProvider.Settings.cs similarity index 81% rename from QuantBox.API.Provider/Single/SingleProvider.Settings.cs rename to QuantBox.APIProvider/Single/SingleProvider.Settings.cs index 21cb19e..54e51f3 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.Settings.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.Settings.cs @@ -1,17 +1,14 @@ -using NLog; -using QuantBox.APIProvider.UI; -using XAPI; +using XAPI; using SmartQuant; -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Drawing.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + +using QuantBox.APIProvider.UI; namespace QuantBox.APIProvider.Single { +#if NET48 + using System.Drawing.Design; +#endif public partial class SingleProvider : Provider { private const string CATEGORY_SETTINGS = "Settings"; @@ -22,10 +19,12 @@ public partial class SingleProvider : Provider private const string CATEGORY_QUOTE_REQUEST = "Settings - QuoteRequest"; private const string CATEGORY_INSTRUMENT = "Settings - Instrument"; private const string CATEGORY_HISTORICAL_DATA = "Settings - HistoricalData"; - + private const string CATEGORY_LOG_INFO = "Settings - Log Info"; + private bool _enableEmitData; private bool _emitBidAsk; private bool _emitBidAskFirst; + private bool _emitLevel2Snapshot; #region 行情配置 @@ -54,6 +53,13 @@ public bool EmitBidAskFirst get { return _emitBidAskFirst; } set { _emitBidAskFirst = value; } } + [Category(CATEGORY_MARKETDATA)] + [Description("【行情】触发OnLevel2Snapshot事件")] + public bool EmitLevel2Snapshot + { + get { return _emitLevel2Snapshot; } + set { _emitLevel2Snapshot = value; } + } #endregion @@ -121,31 +127,31 @@ public int QueryPositionInterval #region 通用 + +#if NET48 + [Editor(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(UITypeEditor))] +#endif [Category(CATEGORY_COMMON)] [Description("配置文件路径")] - [Editor(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(UITypeEditor))] - public string ConfigPath - { - get { return _configPath; } - set - { - _configPath = value; - Load(); - } - } - private string _configPath; + public string ConfigPath { get; set; } [Category(CATEGORY_COMMON)] [Description("交易时段列表,当前时间在这些列表中将启用重连机制,不在此列表中将主动断开,列表为空将不处理")] public BindingList SessionTimeList { get; set; } #endregion - [Category(CATEGORY_SETTINGS), Editor(typeof(ApiManagerTypeEditor), typeof(UITypeEditor)), - Description("综合设置")] +#if NET48 + [Editor(typeof(ApiManagerTypeEditor), typeof(UITypeEditor))] +#endif + [Category(CATEGORY_SETTINGS)] + [Description("综合设置")] public string AllConfig { get; set; } - [Category(CATEGORY_SETTINGS), Editor(typeof(ApiControlTypeEditor), typeof(UITypeEditor)), - Description("综合控制")] +#if NET48 + [Editor(typeof(ApiManagerTypeEditor), typeof(UITypeEditor))] +#endif + [Category(CATEGORY_SETTINGS)] + [Description("综合控制")] public string AllControl { get; set; } [Browsable(false)] @@ -173,11 +179,21 @@ public string ConfigPath [Category(CATEGORY_HISTORICAL_DATA)] [Description("【历史】是否触发EmitHistoricalData事件")] [DisplayName("EmitHistoricalData")] - public bool EnableEmitHistoricalData { get; set; } + public bool EnablEmitHistoricalData { get; set; } [Category(CATEGORY_HISTORICAL_DATA)] [Description("【历史】是否过滤数据日期和时间")] public bool FilterDateTime { get; set; } #endregion + + [Category(CATEGORY_LOG_INFO)] + [Description("【日志】是否显示OnRspQryInvestorPosition记录")] + public bool IsLogOnRspQryInvestorPosition { get; set; } + [Category(CATEGORY_LOG_INFO)] + [Description("【日志】是否显示OnRspQryTradingAccount记录")] + public bool IsLogOnRspQryTradingAccount { get; set; } + [Category(CATEGORY_LOG_INFO)] + [Description("【日志】是否显示OnRtnInstrumentStatus记录")] + public bool IsLogOnRtnInstrumentStatus { get; set; } } } diff --git a/QuantBox.APIProvider/Single/UserItem.cs b/QuantBox.APIProvider/Single/UserItem.cs new file mode 100644 index 0000000..5b97973 --- /dev/null +++ b/QuantBox.APIProvider/Single/UserItem.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using XAPI; + +#if NET48 +using System.Drawing.Design; +#endif + +namespace QuantBox.APIProvider.Single +{ + [DefaultProperty("Label")] + public class UserItem:ICloneable + { + [Category("标签")] + public string Label + { + get; + set; + } +#if NET48 + [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(UITypeEditor))] +#endif + public string Path { get; set; } + + public override string ToString() + { + return string.Format("Label={0}", this.Label); + } + + public object Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/QuantBox.API.Provider/UI/ApiControlForm.Designer.cs b/QuantBox.APIProvider/UI/ApiControlForm.Designer.cs similarity index 99% rename from QuantBox.API.Provider/UI/ApiControlForm.Designer.cs rename to QuantBox.APIProvider/UI/ApiControlForm.Designer.cs index 881b246..3a43a92 100644 --- a/QuantBox.API.Provider/UI/ApiControlForm.Designer.cs +++ b/QuantBox.APIProvider/UI/ApiControlForm.Designer.cs @@ -1,5 +1,6 @@ namespace QuantBox.APIProvider.UI { +#if NET48 partial class ApiControlForm { /// @@ -20,7 +21,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - #region Windows Form Designer generated code + #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify @@ -188,7 +189,7 @@ private void InitializeComponent() } - #endregion + #endregion private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.Label label1; @@ -204,4 +205,5 @@ private void InitializeComponent() private System.Windows.Forms.TextBox textBox_PortfolioID2; private System.Windows.Forms.TextBox textBox_PortfolioID1; } +#endif } \ No newline at end of file diff --git a/QuantBox.API.Provider/UI/ApiControlForm.cs b/QuantBox.APIProvider/UI/ApiControlForm.cs similarity index 78% rename from QuantBox.API.Provider/UI/ApiControlForm.cs rename to QuantBox.APIProvider/UI/ApiControlForm.cs index 28b6118..87463c6 100644 --- a/QuantBox.API.Provider/UI/ApiControlForm.cs +++ b/QuantBox.APIProvider/UI/ApiControlForm.cs @@ -1,17 +1,12 @@ using QuantBox.APIProvider.Single; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; + using XAPI; namespace QuantBox.APIProvider.UI { +#if NET48 + using System.Windows.Forms; public partial class ApiControlForm : Form { public ApiControlForm() @@ -27,46 +22,46 @@ public void Init(SingleProvider provider) private void button_QueryOrder_Click(object sender, EventArgs e) { - ReqQueryField query = default(ReqQueryField); + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = textBox_PortfolioID1.Text; query.PortfolioID2 = textBox_PortfolioID2.Text; query.PortfolioID3 = textBox_PortfolioID3.Text; query.Business = (BusinessType)Enum.Parse(typeof(BusinessType),comboBox_BusinessType.Text); - provider._QueryApi.ReqQuery(QueryType.ReqQryOrder,ref query); + provider._QueryApi.ReqQuery(QueryType.ReqQryOrder, query); } private void button_QueryTrade_Click(object sender, EventArgs e) { - ReqQueryField query = default(ReqQueryField); + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = textBox_PortfolioID1.Text; query.PortfolioID2 = textBox_PortfolioID2.Text; query.PortfolioID3 = textBox_PortfolioID3.Text; query.Business = (BusinessType)Enum.Parse(typeof(BusinessType), comboBox_BusinessType.Text); - provider._QueryApi.ReqQuery(QueryType.ReqQryTrade, ref query); + provider._QueryApi.ReqQuery(QueryType.ReqQryTrade, query); } private void button_QueryAccount_Click(object sender, EventArgs e) { - ReqQueryField query = default(ReqQueryField); + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = textBox_PortfolioID1.Text; query.PortfolioID2 = textBox_PortfolioID2.Text; query.PortfolioID3 = textBox_PortfolioID3.Text; query.Business = (BusinessType)Enum.Parse(typeof(BusinessType), comboBox_BusinessType.Text); - provider._QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, ref query); + provider._QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, query); } private void button_QueryPosition_Click(object sender, EventArgs e) { - ReqQueryField query = default(ReqQueryField); + ReqQueryField query = new ReqQueryField(); query.PortfolioID1 = textBox_PortfolioID1.Text; query.PortfolioID2 = textBox_PortfolioID2.Text; query.PortfolioID3 = textBox_PortfolioID3.Text; query.Business = (BusinessType)Enum.Parse(typeof(BusinessType), comboBox_BusinessType.Text); - provider._QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, ref query); + provider._QueryApi.ReqQuery(QueryType.ReqQryInvestorPosition, query); } private void ApiControlForm_Load(object sender, EventArgs e) @@ -74,4 +69,5 @@ private void ApiControlForm_Load(object sender, EventArgs e) comboBox_BusinessType.DataSource = Enum.GetValues(typeof(BusinessType)); } } +#endif } diff --git a/QuantBox.API.Provider/UI/ApiControlForm.resx b/QuantBox.APIProvider/UI/ApiControlForm.resx similarity index 100% rename from QuantBox.API.Provider/UI/ApiControlForm.resx rename to QuantBox.APIProvider/UI/ApiControlForm.resx diff --git a/QuantBox.API.Provider/UI/ApiControlTypeEditor.cs b/QuantBox.APIProvider/UI/ApiControlTypeEditor.cs similarity index 85% rename from QuantBox.API.Provider/UI/ApiControlTypeEditor.cs rename to QuantBox.APIProvider/UI/ApiControlTypeEditor.cs index 6b33d85..365ff2d 100644 --- a/QuantBox.API.Provider/UI/ApiControlTypeEditor.cs +++ b/QuantBox.APIProvider/UI/ApiControlTypeEditor.cs @@ -1,15 +1,14 @@ -using QuantBox.APIProvider.Single; -using System; -using System.Collections.Generic; +using System; using System.ComponentModel; -using System.Drawing.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms.Design; + +using QuantBox.APIProvider.Single; namespace QuantBox.APIProvider.UI { +#if NET48 + using System.Drawing.Design; + using System.Windows.Forms.Design; + class ApiControlTypeEditor : UITypeEditor { // Methods @@ -41,4 +40,5 @@ public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext contex return base.GetEditStyle(context); } } +#endif } diff --git a/QuantBox.API.Provider/UI/ApiManagerForm.Designer.cs b/QuantBox.APIProvider/UI/ApiManagerForm.Designer.cs similarity index 83% rename from QuantBox.API.Provider/UI/ApiManagerForm.Designer.cs rename to QuantBox.APIProvider/UI/ApiManagerForm.Designer.cs index 69267ed..cb5030b 100644 --- a/QuantBox.API.Provider/UI/ApiManagerForm.Designer.cs +++ b/QuantBox.APIProvider/UI/ApiManagerForm.Designer.cs @@ -1,5 +1,6 @@ namespace QuantBox.APIProvider.UI { +#if NET48 partial class ApiManagerForm { /// @@ -20,7 +21,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - #region Windows Form Designer generated code +#region Windows Form Designer generated code /// /// Required method for Designer support - do not modify @@ -61,9 +62,11 @@ private void InitializeComponent() this.listBox_UserList.DataSource = this.userItemBindingSource; this.listBox_UserList.FormattingEnabled = true; this.listBox_UserList.HorizontalScrollbar = true; - this.listBox_UserList.Location = new System.Drawing.Point(14, 19); + this.listBox_UserList.ItemHeight = 15; + this.listBox_UserList.Location = new System.Drawing.Point(19, 22); + this.listBox_UserList.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.listBox_UserList.Name = "listBox_UserList"; - this.listBox_UserList.Size = new System.Drawing.Size(313, 108); + this.listBox_UserList.Size = new System.Drawing.Size(416, 124); this.listBox_UserList.TabIndex = 0; this.listBox_UserList.SelectedIndexChanged += new System.EventHandler(this.listBox_UserList_SelectedIndexChanged); // @@ -76,9 +79,10 @@ private void InitializeComponent() this.propertyGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.propertyGrid.Location = new System.Drawing.Point(439, -1); + this.propertyGrid.Location = new System.Drawing.Point(585, -1); + this.propertyGrid.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.propertyGrid.Name = "propertyGrid"; - this.propertyGrid.Size = new System.Drawing.Size(435, 468); + this.propertyGrid.Size = new System.Drawing.Size(580, 540); this.propertyGrid.TabIndex = 1; this.propertyGrid.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid_PropertyValueChanged); // @@ -88,18 +92,21 @@ private void InitializeComponent() this.groupBox1.Controls.Add(this.button_RemoveUser); this.groupBox1.Controls.Add(this.button_AddUser); this.groupBox1.Controls.Add(this.listBox_UserList); - this.groupBox1.Location = new System.Drawing.Point(12, 12); + this.groupBox1.Location = new System.Drawing.Point(16, 14); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(421, 142); + this.groupBox1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.groupBox1.Size = new System.Drawing.Size(561, 164); this.groupBox1.TabIndex = 2; this.groupBox1.TabStop = false; this.groupBox1.Text = "UserList"; // // button_CopyUser // - this.button_CopyUser.Location = new System.Drawing.Point(333, 77); + this.button_CopyUser.Location = new System.Drawing.Point(444, 89); + this.button_CopyUser.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_CopyUser.Name = "button_CopyUser"; - this.button_CopyUser.Size = new System.Drawing.Size(75, 23); + this.button_CopyUser.Size = new System.Drawing.Size(100, 27); this.button_CopyUser.TabIndex = 1; this.button_CopyUser.Text = "Copy"; this.button_CopyUser.UseVisualStyleBackColor = true; @@ -107,9 +114,10 @@ private void InitializeComponent() // // button_RemoveUser // - this.button_RemoveUser.Location = new System.Drawing.Point(333, 48); + this.button_RemoveUser.Location = new System.Drawing.Point(444, 55); + this.button_RemoveUser.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_RemoveUser.Name = "button_RemoveUser"; - this.button_RemoveUser.Size = new System.Drawing.Size(75, 23); + this.button_RemoveUser.Size = new System.Drawing.Size(100, 27); this.button_RemoveUser.TabIndex = 1; this.button_RemoveUser.Text = "Remove"; this.button_RemoveUser.UseVisualStyleBackColor = true; @@ -117,9 +125,10 @@ private void InitializeComponent() // // button_AddUser // - this.button_AddUser.Location = new System.Drawing.Point(333, 19); + this.button_AddUser.Location = new System.Drawing.Point(444, 22); + this.button_AddUser.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_AddUser.Name = "button_AddUser"; - this.button_AddUser.Size = new System.Drawing.Size(75, 23); + this.button_AddUser.Size = new System.Drawing.Size(100, 27); this.button_AddUser.TabIndex = 1; this.button_AddUser.Text = "Add"; this.button_AddUser.UseVisualStyleBackColor = true; @@ -131,18 +140,21 @@ private void InitializeComponent() this.groupBox2.Controls.Add(this.button_RemoveServer); this.groupBox2.Controls.Add(this.listBox_ServerList); this.groupBox2.Controls.Add(this.button_AddServer); - this.groupBox2.Location = new System.Drawing.Point(12, 160); + this.groupBox2.Location = new System.Drawing.Point(16, 185); + this.groupBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(421, 142); + this.groupBox2.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.groupBox2.Size = new System.Drawing.Size(561, 164); this.groupBox2.TabIndex = 2; this.groupBox2.TabStop = false; this.groupBox2.Text = "ServerList"; // // button_CopyServer // - this.button_CopyServer.Location = new System.Drawing.Point(333, 80); + this.button_CopyServer.Location = new System.Drawing.Point(444, 92); + this.button_CopyServer.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_CopyServer.Name = "button_CopyServer"; - this.button_CopyServer.Size = new System.Drawing.Size(75, 23); + this.button_CopyServer.Size = new System.Drawing.Size(100, 27); this.button_CopyServer.TabIndex = 1; this.button_CopyServer.Text = "Copy"; this.button_CopyServer.UseVisualStyleBackColor = true; @@ -150,9 +162,10 @@ private void InitializeComponent() // // button_RemoveServer // - this.button_RemoveServer.Location = new System.Drawing.Point(333, 51); + this.button_RemoveServer.Location = new System.Drawing.Point(444, 59); + this.button_RemoveServer.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_RemoveServer.Name = "button_RemoveServer"; - this.button_RemoveServer.Size = new System.Drawing.Size(75, 23); + this.button_RemoveServer.Size = new System.Drawing.Size(100, 27); this.button_RemoveServer.TabIndex = 1; this.button_RemoveServer.Text = "Remove"; this.button_RemoveServer.UseVisualStyleBackColor = true; @@ -163,17 +176,20 @@ private void InitializeComponent() this.listBox_ServerList.DataSource = this.serverItemBindingSource; this.listBox_ServerList.FormattingEnabled = true; this.listBox_ServerList.HorizontalScrollbar = true; - this.listBox_ServerList.Location = new System.Drawing.Point(14, 19); + this.listBox_ServerList.ItemHeight = 15; + this.listBox_ServerList.Location = new System.Drawing.Point(19, 22); + this.listBox_ServerList.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.listBox_ServerList.Name = "listBox_ServerList"; - this.listBox_ServerList.Size = new System.Drawing.Size(313, 108); + this.listBox_ServerList.Size = new System.Drawing.Size(416, 124); this.listBox_ServerList.TabIndex = 0; this.listBox_ServerList.SelectedIndexChanged += new System.EventHandler(this.listBox_ServerList_SelectedIndexChanged); // // button_AddServer // - this.button_AddServer.Location = new System.Drawing.Point(333, 22); + this.button_AddServer.Location = new System.Drawing.Point(444, 25); + this.button_AddServer.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_AddServer.Name = "button_AddServer"; - this.button_AddServer.Size = new System.Drawing.Size(75, 23); + this.button_AddServer.Size = new System.Drawing.Size(100, 27); this.button_AddServer.TabIndex = 1; this.button_AddServer.Text = "Add"; this.button_AddServer.UseVisualStyleBackColor = true; @@ -187,18 +203,21 @@ private void InitializeComponent() this.groupBox3.Controls.Add(this.button_RemoveApi); this.groupBox3.Controls.Add(this.listBox_ApiList); this.groupBox3.Controls.Add(this.button_AddApi); - this.groupBox3.Location = new System.Drawing.Point(12, 314); + this.groupBox3.Location = new System.Drawing.Point(16, 362); + this.groupBox3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(421, 142); + this.groupBox3.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.groupBox3.Size = new System.Drawing.Size(561, 164); this.groupBox3.TabIndex = 2; this.groupBox3.TabStop = false; this.groupBox3.Text = "ApiList"; // // button_CopyApi // - this.button_CopyApi.Location = new System.Drawing.Point(333, 76); + this.button_CopyApi.Location = new System.Drawing.Point(444, 88); + this.button_CopyApi.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_CopyApi.Name = "button_CopyApi"; - this.button_CopyApi.Size = new System.Drawing.Size(75, 23); + this.button_CopyApi.Size = new System.Drawing.Size(100, 27); this.button_CopyApi.TabIndex = 1; this.button_CopyApi.Text = "Copy"; this.button_CopyApi.UseVisualStyleBackColor = true; @@ -206,9 +225,10 @@ private void InitializeComponent() // // button_RemoveApi // - this.button_RemoveApi.Location = new System.Drawing.Point(333, 47); + this.button_RemoveApi.Location = new System.Drawing.Point(444, 54); + this.button_RemoveApi.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_RemoveApi.Name = "button_RemoveApi"; - this.button_RemoveApi.Size = new System.Drawing.Size(75, 23); + this.button_RemoveApi.Size = new System.Drawing.Size(100, 27); this.button_RemoveApi.TabIndex = 1; this.button_RemoveApi.Text = "Remove"; this.button_RemoveApi.UseVisualStyleBackColor = true; @@ -222,9 +242,11 @@ private void InitializeComponent() this.listBox_ApiList.DataSource = this.apiItemBindingSource; this.listBox_ApiList.FormattingEnabled = true; this.listBox_ApiList.HorizontalScrollbar = true; - this.listBox_ApiList.Location = new System.Drawing.Point(14, 19); + this.listBox_ApiList.ItemHeight = 15; + this.listBox_ApiList.Location = new System.Drawing.Point(19, 22); + this.listBox_ApiList.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.listBox_ApiList.Name = "listBox_ApiList"; - this.listBox_ApiList.Size = new System.Drawing.Size(313, 108); + this.listBox_ApiList.Size = new System.Drawing.Size(416, 124); this.listBox_ApiList.TabIndex = 0; this.listBox_ApiList.SelectedIndexChanged += new System.EventHandler(this.listBox_ApiList_SelectedIndexChanged); // @@ -234,9 +256,10 @@ private void InitializeComponent() // // button_AddApi // - this.button_AddApi.Location = new System.Drawing.Point(333, 18); + this.button_AddApi.Location = new System.Drawing.Point(444, 21); + this.button_AddApi.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.button_AddApi.Name = "button_AddApi"; - this.button_AddApi.Size = new System.Drawing.Size(75, 23); + this.button_AddApi.Size = new System.Drawing.Size(100, 27); this.button_AddApi.TabIndex = 1; this.button_AddApi.Text = "Add"; this.button_AddApi.UseVisualStyleBackColor = true; @@ -244,15 +267,17 @@ private void InitializeComponent() // // ApiManagerForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(873, 468); + this.ClientSize = new System.Drawing.Size(1164, 540); this.Controls.Add(this.groupBox3); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.propertyGrid); + this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.Name = "ApiManagerForm"; this.Text = "ApiManagerForm"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.ApiManagerForm_FormClosed); this.Load += new System.EventHandler(this.ApiManagerForm_Load); ((System.ComponentModel.ISupportInitialize)(this.userItemBindingSource)).EndInit(); this.groupBox1.ResumeLayout(false); @@ -264,7 +289,7 @@ private void InitializeComponent() } - #endregion +#endregion private System.Windows.Forms.ListBox listBox_UserList; private System.Windows.Forms.PropertyGrid propertyGrid; @@ -286,4 +311,5 @@ private void InitializeComponent() private System.Windows.Forms.BindingSource serverItemBindingSource; private System.Windows.Forms.BindingSource apiItemBindingSource; } +#endif } \ No newline at end of file diff --git a/QuantBox.API.Provider/UI/ApiManagerForm.cs b/QuantBox.APIProvider/UI/ApiManagerForm.cs similarity index 96% rename from QuantBox.API.Provider/UI/ApiManagerForm.cs rename to QuantBox.APIProvider/UI/ApiManagerForm.cs index 14dd317..e493594 100644 --- a/QuantBox.API.Provider/UI/ApiManagerForm.cs +++ b/QuantBox.APIProvider/UI/ApiManagerForm.cs @@ -7,10 +7,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Windows.Forms; + namespace QuantBox.APIProvider.UI { +#if NET48 + using System.Windows.Forms; public partial class ApiManagerForm : Form { public ApiManagerForm() @@ -154,6 +156,7 @@ private void listBox_ApiList_SelectedIndexChanged(object sender, EventArgs e) public void Init(SingleProvider provider) { this.provider = provider; + provider.Load(); } private void ApiManagerForm_Load(object sender, EventArgs e) @@ -162,5 +165,11 @@ private void ApiManagerForm_Load(object sender, EventArgs e) serverItemBindingSource.DataSource = provider.ServerList; apiItemBindingSource.DataSource = provider.ApiList; } + + private void ApiManagerForm_FormClosed(object sender, FormClosedEventArgs e) + { + provider.Save(); + } } +#endif } diff --git a/QuantBox.API.Provider/UI/ApiManagerForm.resx b/QuantBox.APIProvider/UI/ApiManagerForm.resx similarity index 100% rename from QuantBox.API.Provider/UI/ApiManagerForm.resx rename to QuantBox.APIProvider/UI/ApiManagerForm.resx diff --git a/QuantBox.API.Provider/UI/ApiManagerTypeEditor.cs b/QuantBox.APIProvider/UI/ApiManagerTypeEditor.cs similarity index 85% rename from QuantBox.API.Provider/UI/ApiManagerTypeEditor.cs rename to QuantBox.APIProvider/UI/ApiManagerTypeEditor.cs index f6273e4..fb58f6c 100644 --- a/QuantBox.API.Provider/UI/ApiManagerTypeEditor.cs +++ b/QuantBox.APIProvider/UI/ApiManagerTypeEditor.cs @@ -1,15 +1,14 @@ -using QuantBox.APIProvider.Single; -using System; -using System.Collections.Generic; +using System; using System.ComponentModel; -using System.Drawing.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms.Design; + +using QuantBox.APIProvider.Single; namespace QuantBox.APIProvider.UI { +#if NET48 + using System.Drawing.Design; + using System.Windows.Forms.Design; + class ApiManagerTypeEditor : UITypeEditor { // Methods @@ -40,4 +39,5 @@ public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext contex return base.GetEditStyle(context); } } +#endif } diff --git a/QuantBox.API.Provider/UI/ApiTypeSelectorEditor.cs b/QuantBox.APIProvider/UI/ApiTypeSelectorEditor.cs similarity index 89% rename from QuantBox.API.Provider/UI/ApiTypeSelectorEditor.cs rename to QuantBox.APIProvider/UI/ApiTypeSelectorEditor.cs index 024f5c8..e639961 100644 --- a/QuantBox.API.Provider/UI/ApiTypeSelectorEditor.cs +++ b/QuantBox.APIProvider/UI/ApiTypeSelectorEditor.cs @@ -1,17 +1,16 @@ using QuantBox.APIProvider.Single; using System; -using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; + using XAPI; namespace QuantBox.APIProvider.UI { +#if NET48 + using System.Windows.Forms; + class ApiTypeSelectorEditor : ObjectSelectorEditor { private ObjectSelectorEditor.Selector selector; @@ -42,9 +41,9 @@ protected override void FillTreeWithData(ObjectSelectorEditor.Selector selector, selector.Clear(); foreach (ApiType category in Enum.GetValues(typeof(ApiType))) { - if (category != ApiType.Nono) + if (category != ApiType.None) { - if((instance.Type & category) == category) + if ((instance.Type & category) == category) { selector.AddNode(category.ToString(), (int)category, null).Checked = (instance.UseType & category) == category; } @@ -63,5 +62,5 @@ private void method_0(object sender, TreeViewCancelEventArgs e) e.Cancel = true; } } - +#endif } diff --git a/QuantBox.API.Provider/UI/ComboBoxItemTypeConvert.cs b/QuantBox.APIProvider/UI/ComboBoxItemTypeConvert.cs similarity index 100% rename from QuantBox.API.Provider/UI/ComboBoxItemTypeConvert.cs rename to QuantBox.APIProvider/UI/ComboBoxItemTypeConvert.cs diff --git a/QuantBox.API.Provider/UI/JTypeDescriptor.cs b/QuantBox.APIProvider/UI/JTypeDescriptor.cs similarity index 100% rename from QuantBox.API.Provider/UI/JTypeDescriptor.cs rename to QuantBox.APIProvider/UI/JTypeDescriptor.cs diff --git a/QuantBox.API.Provider/UI/ServerItemConverter.cs b/QuantBox.APIProvider/UI/ServerItemConverter.cs similarity index 100% rename from QuantBox.API.Provider/UI/ServerItemConverter.cs rename to QuantBox.APIProvider/UI/ServerItemConverter.cs diff --git a/QuantBox.API.Provider/UI/UserItemConverter.cs b/QuantBox.APIProvider/UI/UserItemConverter.cs similarity index 81% rename from QuantBox.API.Provider/UI/UserItemConverter.cs rename to QuantBox.APIProvider/UI/UserItemConverter.cs index 2434f31..b45af1a 100644 --- a/QuantBox.API.Provider/UI/UserItemConverter.cs +++ b/QuantBox.APIProvider/UI/UserItemConverter.cs @@ -1,11 +1,7 @@ using QuantBox.APIProvider.Single; -using System; -using System.Collections; -using System.Collections.Generic; + using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + namespace QuantBox.APIProvider.UI { diff --git a/QuantBox.APIProvider_Windows.sln b/QuantBox.APIProvider_Windows.sln deleted file mode 100644 index 6e9aac4..0000000 --- a/QuantBox.APIProvider_Windows.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantBox.APIProvider", "QuantBox.API.Provider\QuantBox.APIProvider.csproj", "{E86E1057-B344-4805-A836-43606154677E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantBox.Extensions", "QuantBox.Extensions\QuantBox.Extensions.csproj", "{D8D82E27-47F5-4579-92AD-416DA86AD601}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E86E1057-B344-4805-A836-43606154677E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E86E1057-B344-4805-A836-43606154677E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E86E1057-B344-4805-A836-43606154677E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E86E1057-B344-4805-A836-43606154677E}.Release|Any CPU.Build.0 = Release|Any CPU - {D8D82E27-47F5-4579-92AD-416DA86AD601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8D82E27-47F5-4579-92AD-416DA86AD601}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8D82E27-47F5-4579-92AD-416DA86AD601}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8D82E27-47F5-4579-92AD-416DA86AD601}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = SMACrossover\Realtime\Realtime.csproj - EndGlobalSection -EndGlobal diff --git a/QuantBox.Extensions/OrderExtensions_OpenCloseType.cs b/QuantBox.Extensions/OrderExtensions_OpenCloseType.cs index bcad954..b356826 100644 --- a/QuantBox.Extensions/OrderExtensions_OpenCloseType.cs +++ b/QuantBox.Extensions/OrderExtensions_OpenCloseType.cs @@ -32,6 +32,7 @@ public static Order CloseToday(this Order order) public static Order SetOpenClose(this Order order, OpenCloseType OpenClose) { + order.SubSide = order.OpenClose2SubSide(OpenClose); order.GetDictionary(index)[OrderTagType.PositionEffect] = (byte)OpenClose; return order; } @@ -39,11 +40,53 @@ public static Order SetOpenClose(this Order order, OpenCloseType OpenClose) public static OpenCloseType? GetOpenClose(this Order order) { object obj = order.GetDictionaryValue(OrderTagType.PositionEffect, index); - if(obj == null) + if (obj == null) { return (OpenCloseType?)obj; } return (OpenCloseType?)(byte)obj; } - } + + public static SubSide OpenClose2SubSide(this Order order, OpenCloseType OpenClose) + { + //多头 + //Buy 就是开 + //Sell 就是平 SubSide 是 Undefined + + //空头 + //Sell 加 SubSide = SellShort 是开仓 + //Buy 加 SubSide = BuyCover 是平仓 + + // 由于使用官方的办法无法指定平今与平昨,所以还是用以前的开平仓的写法 + // 区别只是官方维护了双向持仓 + if (order.Side == SmartQuant.OrderSide.Buy) + { + switch (OpenClose) + { + case OpenCloseType.Open: + order.SubSide = SubSide.Undefined; + break; + case OpenCloseType.Close: + case OpenCloseType.CloseToday: + order.SubSide = SubSide.BuyCover; + break; + } + } + else + { + switch (OpenClose) + { + case OpenCloseType.Open: + order.SubSide = SubSide.SellShort; + break; + case OpenCloseType.Close: + case OpenCloseType.CloseToday: + order.SubSide = SubSide.Undefined; + break; + } + } + + return order.SubSide; + } + } } diff --git a/QuantBox.Extensions/OrderTagType.cs b/QuantBox.Extensions/OrderTagType.cs index e7948eb..a6d852d 100644 --- a/QuantBox.Extensions/OrderTagType.cs +++ b/QuantBox.Extensions/OrderTagType.cs @@ -16,7 +16,7 @@ public class OrderTagType // 在本地就可以处理好的数据类型,没有必要在网络中传输,并且有可能无法序列化 public const int Local = 1; // 留给用户自定义的数据类型,让用户自己发挥的类型,可做临时变量等等 - public const int Custom = 2; + public static int Index_MAX = 2; // ===== 需要跨网络传输的Tag @@ -30,6 +30,7 @@ public class OrderTagType public const int PortfolioID3 = 6; public const int Business = 7; public const int QuoteReqID = 8; // 这个名字可能有错,需要确认 + public static int Network_MAX = 9; // ===== 不需要网络传输的类型,并且有可能无法序列化 @@ -37,5 +38,23 @@ public class OrderTagType public const int SameTimeOrder = 1; // 特殊的用于记录关联的Order,但没有先后循序,如Quote报单 public const int CancelCount = 2; // 定时撤单次数,用来区分是否跟单功能撤单 public const int SendCount = 3;//记录跟单时重发次数 + public static int Local_MAX = 4; } } + +// 使用static 定义,主要是想在不同的模块下,ID不冲突 +// 目前这个写法还没有在不同模块下测试过 + +//public class MyOrderTagType +//{ +// // TODO: 这种方法是否有坑 +// public static int TargetAmount = OrderTagType.Local_MAX + 1; +// public static int MaxQtyPerLot = TargetAmount + 1; +// public static int TimesTicks = MaxQtyPerLot + 1; +// public static int MaxCancelCnt = TimesTicks + 1; + +// static MyOrderTagType() +// { +// OrderTagType.Local_MAX = MaxCancelCnt + 1; +// } +//} diff --git a/QuantBox.Extensions/QuantBox.Extensions.csproj b/QuantBox.Extensions/QuantBox.Extensions.csproj index bd56cfe..1283ef8 100644 --- a/QuantBox.Extensions/QuantBox.Extensions.csproj +++ b/QuantBox.Extensions/QuantBox.Extensions.csproj @@ -1,80 +1,17 @@ - - - + + - Debug - AnyCPU - {D8D82E27-47F5-4579-92AD-416DA86AD601} - Library - Properties - QuantBox.Extensions - QuantBox.Extensions - v4.5.1 - 512 - - - - true - full - false - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\ - TRACE - prompt - 4 - - - + netstandard2.0 + false + - - False + C:\Program Files\SmartQuant Ltd\OpenQuant 2014\SmartQuant.dll - False - - - - - - - False - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\XAPI_CSharp.exe + + C:\Program Files\SmartQuant Ltd\OpenQuant 2014\XAPI_CSharp.dll - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/QuantBox.Extensions/QuantBox.Extensions_Linux.csproj b/QuantBox.Extensions/QuantBox.Extensions_Linux.csproj deleted file mode 100644 index ac1f5d4..0000000 --- a/QuantBox.Extensions/QuantBox.Extensions_Linux.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - - Debug - AnyCPU - {D8D82E27-47F5-4579-92AD-416DA86AD601} - Library - Properties - QuantBox.Extensions - QuantBox.Extensions - v4.5.1 - 512 - - - - true - full - false - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - ..\..\bin\SmartQuant.dll - - - ..\..\bin\QuantBox.XAPI.dll - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index fe735b2..0291a5a 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -#QuantBox.APIProvider +# QuantBox.APIProvider + +OpenQuant2014的行情交易插件,使用XAPI统一接口 + +## 命令启停OQ功能 + +1. 启动OQ,并打开指定策略,并运行 +通过**命令行**传入参数 +- --file: 策略绝对路径。如果空格必须用引号 +- --run: 运行策略。否则只打开策略 +- --id: 识别码。用于监控剪贴板时过滤命令 + +``` +cd "C:\Program Files\SmartQuant Ltd\OpenQuant 2014" +C: +start OpenQuant.exe --file="D:\Users\Kan\Documents\OpenQuant 2014\Solutions\SMACrossover\SMACrossover.sln" --id=100 --run +``` + +2. 停止策略,并退出程序 +通过**剪贴板**传入参数。 +- --id: 识别码。用于监控剪贴板时过滤命令 +- --stop: 停止策略。必须先停止才能退出 +- --exit: 退出程序。 +``` +echo --id=100 --stop --exit | clip +``` +只要向剪贴板复制`--id=100 --stop --exit`即可,这个复制可以手工实现,也可以灵活使用管道符|将echo的回显重定向到剪贴板clip + +## 开盘前自动连接并通知 + +此插件在登录成功时会自动查询资金,可以将其转发到钉钉,这样用户就可以知道连接已经成功。 +1. 设置SessionTimeList,需要每个星期都有,包括周日。 +2. 设置NLog.config,如当前插件名为A99CTP,那么NLog中资金的logger是A99CTP.A +3. 将A99CTP.A转发到WebService,如钉钉 + + +## 定时通知资金与持仓功能 + +通过设置参数QueryAccountInterval(资金查询间隔,秒)和QueryPositionInterval(持仓查询间隔,秒),可以自动查询,并输出日志。 +1. 资金logger:A99CTP.A +2. 持仓logger:A99CTP.P \ No newline at end of file diff --git a/config/Clock.reg b/config/Clock.reg new file mode 100644 index 0000000..081b4af Binary files /dev/null and b/config/Clock.reg differ diff --git a/config/NLog.config b/config/NLog.config new file mode 100644 index 0000000..4fb04bd --- /dev/null +++ b/config/NLog.config @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/NTP.reg b/config/NTP.reg new file mode 100644 index 0000000..c90b456 Binary files /dev/null and b/config/NTP.reg differ diff --git a/config/backup_proj.bat b/config/backup_proj.bat new file mode 100644 index 0000000..fd652b4 --- /dev/null +++ b/config/backup_proj.bat @@ -0,0 +1,6 @@ +cd D:\ +D: +set DATA_YMD=%date:~0,4%%date:~5,2%%date:~8,2% +xcopy S1 S1_ά\%DATA_YMD%\ +xcopy S2 S2_ά\%DATA_YMD%\ +pause \ No newline at end of file diff --git a/config/run_proj.bat b/config/run_proj.bat new file mode 100644 index 0000000..fdc7374 --- /dev/null +++ b/config/run_proj.bat @@ -0,0 +1,9 @@ +cd "C:\Program Files\SmartQuant Ltd\OpenQuant 2014" +C: + +start OpenQuant.exe --file="D:\GitHub\S1\S1.sln" --id=100 --run +ping 127.0.0.1 -n 20 +start OpenQuant.exe --file="D:\GitHub\S2\S2.sln" --id=200 --run +ping 127.0.0.1 -n 20 + +pause diff --git a/config/stop_proj.bat b/config/stop_proj.bat new file mode 100644 index 0000000..a952368 --- /dev/null +++ b/config/stop_proj.bat @@ -0,0 +1,6 @@ +echo --id=100 --stop --exit | clip +ping 127.0.0.1 -n 10 +echo --id=200 --stop --exit | clip +ping 127.0.0.1 -n 10 + +pause \ No newline at end of file diff --git a/python/BatchDelayQueue.py b/python/BatchDelayQueue.py new file mode 100644 index 0000000..4244561 --- /dev/null +++ b/python/BatchDelayQueue.py @@ -0,0 +1,57 @@ +""" +延迟队列 +""" +import threading +import time + + +class BatchQueue: + def __init__(self): + self.buf = [] + + def add_one(self, obj): + self.buf.append(obj) + + def get_all(self): + _buf = self.buf.copy() + self.buf = [] + return _buf + + +class DelayQueue: + + def __init__(self, interval, process_func): + self.queue = BatchQueue() + self.interval = interval + self.process_func = process_func + # 启动定时器 + threading.Timer(self.interval, self.process).start() + + def add(self, obj): + self.queue.add_one(obj) + + def process(self): + buf = self.queue.get_all() + if len(buf) > 0: + self.process_func(buf) + # 定时器需要再激活才可使用 + threading.Timer(self.interval, self.process).start() + + +if __name__ == '__main__': + + def process(buf): + print(buf) + + + def thread_fun(dq): + for i in range(1000): + dq.add(i) + time.sleep(0.01) + + + dq = DelayQueue(5, process) + threading.Thread(target=thread_fun, args=(dq,)).start() + threading.Thread(target=thread_fun, args=(dq,)).start() + threading.Thread(target=thread_fun, args=(dq,)).start() + input() diff --git a/python/DtalkRobot.py b/python/DtalkRobot.py new file mode 100644 index 0000000..e4f8c69 --- /dev/null +++ b/python/DtalkRobot.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +钉钉群自定义机器人 +author:疯狂的技术宅 +github:https://github.com/magician000 + +学习python时做的练习,纯粹为了娱乐 +如果存在bug请自行修改,不提供任何支持 + +官方文档 +https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.z5MWoh&treeId=257&articleId=105735&docType=1 + +这个接口的消息格式命名风格不统一,坑爹呢? +所以不要迷信大公司就怎样规范。 +""" + +import urllib.parse +import urllib.request +import json +import time + +# 代理,一般不使用 +if False: + proxy_support = urllib.request.ProxyHandler({'http': '192.168.1.60:808', 'https': '192.168.1.60:808'}) + # proxy_support = urllib.request.ProxyHandler({'sock5': '192.168.1.60:1080'}) + opener = urllib.request.build_opener(proxy_support) + urllib.request.install_opener(opener) + + +# 自定义机器人的封装类 +class DtalkRobot(object): + def __init__(self, webhook): + super(DtalkRobot, self).__init__() + self.webhook = webhook + + # text类型 + def sendText_webhook(self, webhook, msg, isAtAll=False, atMobiles=[]): + data = {"msgtype": "text", "text": {"content": msg}, "at": {"atMobiles": atMobiles, "isAtAll": isAtAll}} + return self.post_webhook(webhook, data) + + # markdown类型 + def sendMarkdown_webhook(self, webhook, title, text): + data = {"msgtype": "markdown", "markdown": {"title": title, "text": text}} + return self.post_webhook(webhook, data) + + # link类型 + def sendLink(self, title, text, messageUrl, picUrl=""): + data = {"msgtype": "link", + "link": {"text": text, "title": title, "picUrl": picUrl, "messageUrl": messageUrl}} + return self.post(data) + + # ActionCard类型 + def sendActionCard(self, actionCard): + data = actionCard.getData() + return self.post(data) + + # FeedCard类型 + def sendFeedCard(self, links): + data = {"feedCard": {"links": links}, "msgtype": "feedCard"} + return self.post(data) + + def post(self, data): + post_data = json.JSONEncoder().encode(data).encode(encoding='UTF8') + + req = urllib.request.Request(self.webhook, post_data) + req.add_header('Content-Type', 'application/json') + content = urllib.request.urlopen(req).read().decode('UTF-8') + return content + + def post_webhook(self, webhook, data): + post_data = json.JSONEncoder().encode(data).encode(encoding='UTF8') + + req = urllib.request.Request(webhook, post_data) + req.add_header('Content-Type', 'application/json') + content = urllib.request.urlopen(req).read().decode('UTF-8') + # print(content) + return content + + +# ActionCard类型消息结构 +class ActionCard(object): + """docstring for ActionCard""" + title = "" + text = "" + singleTitle = "" + singleURL = "" + btnOrientation = 0 + hideAvatar = 0 + btns = [] + + def __init__(self, arg=""): + super(ActionCard, self).__init__() + self.arg = arg + + def putBtn(self, title, actionURL): + self.btns.append({"title": title, "actionURL": actionURL}) + + def getData(self): + data = {"actionCard": {"title": self.title, "text": self.text, "hideAvatar": self.hideAvatar, + "btnOrientation": self.btnOrientation, "singleTitle": self.singleTitle, + "singleURL": self.singleURL, "btns": self.btns}, "msgtype": "actionCard"} + return data + + +# FeedCard类型消息格式 +class FeedLink(object): + """docstring for FeedLink""" + title = "" + picUrl = "" + messageUrl = "" + + def __init__(self, arg=""): + super(FeedLink, self).__init__() + self.arg = arg + + def getData(self): + data = {"title": self.title, "picURL": self.picUrl, "messageURL": self.messageUrl} + return data + + +if __name__ == "__main__": + webhook = "https://oapi.dingtalk.com/robot/send?access_token=72f6252257f43f402f743b7d698fc21d21939717783a20689e1c1292c20d1903" + robot = DtalkRobot(webhook) + + # print(robot.sendText("xxxx:[" + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + "]", False, + # ["13912345678 "])) + + print(robot.sendMarkdown('报单回报', """# LAST_HEDGE (OI805) +1. 21:00:00.224/6298/0|-2/ +1. 21:00:01.412/6296/0 +## LAST_HEDGE (TA805) +1. 21:00:00.224/5692/0|-6/ +1. 21:00:01.427/5690/0 +### LAST_HEDGE (TA805) +- 21:00:00.224/5692/0|-6/ +- 21:00:01.427/5690/0 +#### LAST_HEDGE (TA805) +- 21:00:00.224/5692/0|-6/ +- 21:00:01.427/5690/0 +##### LAST_HEDGE (TA805) +- 21:00:00.224/5692/0|-6/ +- 21:00:01.427/5690/0 +###### LAST_HEDGE (i1805) +- 21:00:00.427/514.5/0|1/ +- 21:00:01.412/514/0""")) diff --git a/python/dtlogger.py b/python/dtlogger.py new file mode 100644 index 0000000..136b0ee --- /dev/null +++ b/python/dtlogger.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +pip install spyne + + + + + + + + + + + + + + +protected NLog.Logger log_wx = NLog.LogManager.GetLogger("wx"); + +log_wx.Warn("策略启动。"); + +""" +from spyne import Application, rpc, ServiceBase, String +from spyne.protocol.http import HttpRpc +from spyne.protocol.json import JsonDocument +from spyne.server.wsgi import WsgiApplication + +from DtalkRobot import * +from BatchDelayQueue import DelayQueue + +# 99simnow +webhook_a99ctp = "https://oapi.dingtalk.com/robot/send?access_token=11111" +# 98实 +webhook_a98ctp = "https://oapi.dingtalk.com/robot/send?access_token=11111" +# S01策略 +webhook_s01 = "https://oapi.dingtalk.com/robot/send?access_token=11111" + +# 不明的消息都发到模拟平台上 +webhook = webhook_a99ctp + +ip = '0.0.0.0' +port = 8000 + + +def send_msgs(logger, level, title, message): + # 利用logger,可以将消息发向不同的机器人 + if logger == 'ALL': + _webhook = webhook + elif logger == 'A98CTP.A': + _webhook = webhook_a98ctp + elif logger == 'A98CTP.P': + _webhook = webhook_a98ctp + elif logger == 'S01': + _webhook = webhook_s01 + else: + _webhook = webhook + + if title is None or len(title) == 0: + robot.sendText_webhook(_webhook, message) + else: + msg = f"##### {title}\n{message}" + robot.sendMarkdown_webhook(_webhook, title, msg) + + +def process_log_msg(tuples): + msgs = {} + for tp in tuples: + logger, level, title, message = tp + key = (logger, level, title) + lst = msgs.get(key, []) + lst.append(message) + msgs[key] = lst + print("定时处理", len(msgs)) + for k, v in msgs.items(): + # 这里不能太长,否则会被截断 + send_msgs(k[0], k[1], k[2], '\n'.join(v)) + + +class LogService(ServiceBase): + @rpc(String, String, String, String, _returns=String) + def logme(ctx, logger, level, title, message): + delayQueue.add((logger, level, title, message)) + return 'OK' + + +delayQueue = DelayQueue(10, process_log_msg) + +application = Application([LogService], + tns='kan.logger.wechat', + in_protocol=HttpRpc(validator='soft'), + out_protocol=JsonDocument() + ) + +if __name__ == '__main__': + # You can use any Wsgi server. Here, we chose + # Python's built-in wsgi server but you're not + # supposed to use it in production. + from wsgiref.simple_server import make_server + + wsgi_app = WsgiApplication(application) + server = make_server(ip, port, wsgi_app) + + robot = DtalkRobot(webhook) + robot.sendText_webhook(webhook, "启动钉钉日志服务成功") + + # 不退出 + server.serve_forever() diff --git a/python/run_dt.bat b/python/run_dt.bat new file mode 100644 index 0000000..0db0556 --- /dev/null +++ b/python/run_dt.bat @@ -0,0 +1,2 @@ +python dtlogger.py +pause \ No newline at end of file