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 9ca24b4..0000000 --- a/QuantBox.API.Provider/QuantBox.APIProvider.csproj +++ /dev/null @@ -1,147 +0,0 @@ - - - - - Debug - AnyCPU - {E86E1057-B344-4805-A836-43606154677E} - Library - Properties - QuantBox.APIProvider - QuantBox.APIProvider - v4.6.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.11.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.6\lib\net45\NLog.dll - - - False - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\SmartQuant.dll - - - - - - - - - - - - - - - - - - False - C:\Program Files\SmartQuant Ltd\OpenQuant 2014\XAPI_CSharp.exe - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Form - - - ApiControlForm.cs - - - Form - - - ApiManagerForm.cs - - - - - - - - - - - - - - - - - - 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/ServerItem.cs b/QuantBox.API.Provider/Single/ServerItem.cs deleted file mode 100644 index 46dd4c2..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 = new ServerInfoField(); - 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 069f94c..0000000 --- a/QuantBox.API.Provider/Single/SessionTimeItem.cs +++ /dev/null @@ -1,38 +0,0 @@ -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 - { - [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.cs b/QuantBox.API.Provider/Single/SingleProvider.API.cs deleted file mode 100644 index 7547be8..0000000 --- a/QuantBox.API.Provider/Single/SingleProvider.API.cs +++ /dev/null @@ -1,264 +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); - } - 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(); - - 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) - return; - - 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"); - } - 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) - return; - - 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_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; - - // 合约状态信息太多了,也不关心,这里屏蔽显示 - 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; - } - } -} diff --git a/QuantBox.API.Provider/Single/UserItem.cs b/QuantBox.API.Provider/Single/UserItem.cs deleted file mode 100644 index 5a40954..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 = new UserInfoField(); - - 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 5656d2a..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 100% rename from QuantBox.API.Provider/Properties/AssemblyInfo.cs rename to QuantBox.APIProvider/Properties/AssemblyInfo.cs 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.API.Provider/Single/ApiItem.cs b/QuantBox.APIProvider/Single/ApiItem.cs similarity index 88% rename from QuantBox.API.Provider/Single/ApiItem.cs rename to QuantBox.APIProvider/Single/ApiItem.cs index 37b7545..10c5cca 100644 --- a/QuantBox.API.Provider/Single/ApiItem.cs +++ b/QuantBox.APIProvider/Single/ApiItem.cs @@ -1,18 +1,13 @@ -using Newtonsoft.Json; -using QuantBox.APIProvider.UI; +using QuantBox.APIProvider.UI; 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 { +#if NET48 + using System.Drawing.Design; +#endif /// /// 由用户选择Dll,然后加载,得到 /// @@ -28,9 +23,9 @@ public class ApiItem : ICloneable private string _dllPath; private string _typeName; - private IXApi CheckApi(string typeName,string dllPath) + private IXApi CheckApi(string typeName, string dllPath) { - if(string.IsNullOrEmpty(typeName)) + if (string.IsNullOrEmpty(typeName)) { TypeName = "XAPI.Callback.XApi, XAPI_CSharp"; return null; @@ -55,8 +50,9 @@ private IXApi CheckApi(string typeName,string dllPath) } return api; } - +#if NET48 [Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))] +#endif public string DllPath { get @@ -76,7 +72,8 @@ public string TypeName { return _typeName; } - set { + set + { _typeName = value; Api = CheckApi(_typeName, _dllPath); } @@ -104,8 +101,10 @@ public string TypeName [Category(CATEGORY_TYPE)] [ReadOnly(true)] public ApiType Type { get; set; } - [Category(CATEGORY_TYPE)] +#if NET48 [Editor(typeof(ApiTypeSelectorEditor), typeof(UITypeEditor))] +#endif + [Category(CATEGORY_TYPE)] public ApiType UseType { get; set; } diff --git a/QuantBox.API.Provider/Single/BaseMap.cs b/QuantBox.APIProvider/Single/BaseMap.cs similarity index 72% rename from QuantBox.API.Provider/Single/BaseMap.cs rename to QuantBox.APIProvider/Single/BaseMap.cs index 7f045b5..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) @@ -31,17 +31,6 @@ public ExecutionReport CreateReport( 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 100% rename from QuantBox.API.Provider/Single/Extensions.cs rename to QuantBox.APIProvider/Single/Extensions.cs 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 100% rename from QuantBox.API.Provider/Single/MarketDataRecord.cs rename to QuantBox.APIProvider/Single/MarketDataRecord.cs diff --git a/QuantBox.API.Provider/Single/NoTypeConverterJsonConverter.cs b/QuantBox.APIProvider/Single/NoTypeConverterJsonConverter.cs similarity index 100% rename from QuantBox.API.Provider/Single/NoTypeConverterJsonConverter.cs rename to QuantBox.APIProvider/Single/NoTypeConverterJsonConverter.cs diff --git a/QuantBox.API.Provider/Single/OrderMap.cs b/QuantBox.APIProvider/Single/OrderMap.cs similarity index 90% rename from QuantBox.API.Provider/Single/OrderMap.cs rename to QuantBox.APIProvider/Single/OrderMap.cs index 8e8f7b5..58d3e83 100644 --- a/QuantBox.API.Provider/Single/OrderMap.cs +++ b/QuantBox.APIProvider/Single/OrderMap.cs @@ -228,7 +228,7 @@ public void Process(ref OrderField order, NLog.Logger log) workingOrders.Remove(order.ID); orderIDs.Remove(record.Order.Id); record.LeavesQty = 0; - EmitExecutionReport(record, SQ.ExecType.ExecCancelled, SQ.OrderStatus.Cancelled); + EmitExecutionReport(record, SQ.ExecType.ExecCancelled, SQ.OrderStatus.Cancelled, order.Text()); } else if (this.pendingOrders.TryRemove(order.LocalID, out record)) { @@ -318,6 +318,46 @@ record = GetExternalOrder(ref trade); } } + 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); 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 78% rename from QuantBox.API.Provider/Single/SingleProvider.API.Connection.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Connection.cs index ccefa43..11dbf5d 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.API.Connection.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.API.Connection.cs @@ -1,14 +1,12 @@ 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 NLog; using QuantBox.Extensions; +using System.Threading; +using System.IO; namespace QuantBox.APIProvider.Single { @@ -133,85 +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; - - var stl = SessionTimeList.Where(x => x.Enable).ToList(); - if (stl.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 stl) + if (ts < st.SessionStart) + { + // 停 + bTryConnect = false; + st_next = st; + break; + } + else if (ts <= st.SessionEnd) + { + // 启动 + bTryConnect = true; + st_current = st; + break; + } + else { - // 如果当前时间在交易范围内,要开启重连 - // 如果当前时间不在交易范围内,要主动断开 - TimeSpan ts = e.SignalTime.TimeOfDay; - if (!st.Enable) - continue; - - 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 = 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(); @@ -276,8 +280,10 @@ private void OnConnectionStatus_Done(object sender, ConnectionStatus status) // 每个连接都检查是否连上,如果连上,开始进行一些基本的查询 if (bCheckOk) { - base.Status = ProviderStatus.Connected; + // 晚一点通知上层,这个更稳定一些 + // base.Status = ProviderStatus.Connected; + // 这个查询不能太快,否则CTP报错 QueryAccountPositionInstrument_Logined(); } } @@ -375,11 +381,12 @@ private IXApi ConnectToApi(ApiItem item) item.Api = api; } - api.Server = ServerList[item.Server].ToStruct(); - 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; @@ -411,7 +418,7 @@ private IXApi ConnectToApi(ApiItem item) api.OnRspQrySettlementInfo = OnRspQrySettlementInfo_callback; api.OnRtnInstrumentStatus = OnRtnInstrumentStatus_callback; - api.Connect(); + api.Connect(szServerPath, szUserPath, Path.GetTempPath()); return api; } @@ -425,10 +432,8 @@ private void DisconnectToApi(ApiItem item) // 直接销毁 _DisconnectToApi(item.Api); - //// 在线程中销毁 - //Task task = Task.Factory.StartNew( - // ()=> { _DisconnectToApi(item.Api); } - // ); + // 立即断开连接后,登录时自动查资金与持仓的功能在线程中还在运行,会报错 + assign(item, null); item.Api = null; } @@ -518,6 +523,13 @@ private void OnRtnQuoteRequest_callback(object sender, ref QuoteRequestField quo } private void QueryAccountPositionInstrument_Logined() + { + // 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; @@ -525,17 +537,30 @@ private void QueryAccountPositionInstrument_Logined() 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, 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, query); + _dictAccounts_current.Clear(); + _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, query); } } @@ -550,19 +575,21 @@ private void QueryAccountPosition_OnTimer() query.PortfolioID3 = DefaultPortfolioID3; query.Business = DefaultBusiness; - _QueryAccountCount -= (int)_Timer.Interval / 1000; - if (_QueryAccountCount <= 0) - { - _QueryApi.ReqQuery(QueryType.ReqQryTradingAccount, query); - _QueryAccountCount = _QueryAccountInterval; - } - _QueryPositionCount -= (int)_Timer.Interval / 1000; if (_QueryPositionCount <= 0) { + _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 987ed00..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(EmitHistoricalData) + if(EnablEmitHistoricalData) { HistoricalData data = new HistoricalData { @@ -217,7 +217,7 @@ private void OnRspQryHistoricalBars_callback(object sender, IntPtr pBars, int si } } - if(EmitHistoricalData) + if(EnablEmitHistoricalData) { HistoricalData data = new HistoricalData { diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.MarketData.cs b/QuantBox.APIProvider/Single/SingleProvider.API.MarketData.cs similarity index 100% rename from QuantBox.API.Provider/Single/SingleProvider.API.MarketData.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.MarketData.cs diff --git a/QuantBox.API.Provider/Single/SingleProvider.API.Order.cs b/QuantBox.APIProvider/Single/SingleProvider.API.Order.cs similarity index 91% rename from QuantBox.API.Provider/Single/SingleProvider.API.Order.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Order.cs index 875f36a..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; @@ -143,11 +138,13 @@ private void CmdNewOrderSingle(ExecutionCommand command) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; 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]; @@ -206,11 +203,13 @@ private void CmdNewOrderList(ExecutionCommand command) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; - GetApi_Symbol_Exchange_TickSize(orders[i].Instrument, this.id, + 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); } @@ -267,7 +266,7 @@ private void OnRtnOrder_callback(object sender, ref OrderField order) private void OnRtnTrade_callback(object sender, ref TradeField trade) { - lock(this) + lock (this) { var log = (sender as XApi).GetLog(); log.Debug("OnRtnTrade:" + trade.ToFormattedString()); @@ -286,5 +285,25 @@ private void OnRtnTrade_callback(object sender, ref TradeField trade) } } } + + private void OnRtnOrder_Expired(ref InstrumentStatusField instrumentStatus) + { + if (_TdApi == null) + return; + + lock (this) + { + 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 90% rename from QuantBox.API.Provider/Single/SingleProvider.API.Quote.cs rename to QuantBox.APIProvider/Single/SingleProvider.API.Quote.cs index febe93f..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, this.id, out altSymbol, out altExchange, out apiSymbol, out apiExchange, - out apiTickSize); + out apiTickSize, + out apiProductID); Order bidOrder; Order askOrder; 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 96% rename from QuantBox.API.Provider/Single/SingleProvider.DataProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.DataProvider.cs index 45a2957..caa93bf 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.DataProvider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.DataProvider.cs @@ -40,11 +40,13 @@ public override void Subscribe(Instrument instrument) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; 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); @@ -88,11 +90,13 @@ public override void Unsubscribe(Instrument instrument) string apiSymbol; string apiExchange; double apiTickSize; + string apiProductID; 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); 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 96% rename from QuantBox.API.Provider/Single/SingleProvider.HistoricalDataProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.HistoricalDataProvider.cs index f874b84..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, 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; diff --git a/QuantBox.API.Provider/Single/SingleProvider.InstrumentProvider.cs b/QuantBox.APIProvider/Single/SingleProvider.InstrumentProvider.cs similarity index 100% rename from QuantBox.API.Provider/Single/SingleProvider.InstrumentProvider.cs rename to QuantBox.APIProvider/Single/SingleProvider.InstrumentProvider.cs diff --git a/QuantBox.API.Provider/Single/SingleProvider.Other.cs b/QuantBox.APIProvider/Single/SingleProvider.Other.cs similarity index 84% rename from QuantBox.API.Provider/Single/SingleProvider.Other.cs rename to QuantBox.APIProvider/Single/SingleProvider.Other.cs index 50e387d..137b9fc 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.Other.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.Other.cs @@ -11,10 +11,11 @@ namespace QuantBox.APIProvider.Single public partial class SingleProvider { // 得到API中的合约名与交易所 - private void GetApi_Symbol_Exchange_TickSize(Instrument instrument, byte id, + 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 double apiTickSize, + out string apiProductID) { // 取合约别名 altSymbol = instrument.GetSymbol(id); @@ -24,6 +25,7 @@ private void GetApi_Symbol_Exchange_TickSize(Instrument instrument, byte id, // 取合约在API中的名字 apiSymbol = altSymbol; apiExchange = altExchange; + apiProductID = ""; // 对于UFX,没有实现查询合约的功能,所以这里其实使用的是AltID中的信息 // 屏蔽这个功能,订阅的合约就根据设置来了 @@ -35,6 +37,7 @@ private void GetApi_Symbol_Exchange_TickSize(Instrument instrument, byte id, 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 97% rename from QuantBox.API.Provider/Single/SingleProvider.Provider.cs rename to QuantBox.APIProvider/Single/SingleProvider.Provider.cs index b727da0..9ef770a 100644 --- a/QuantBox.API.Provider/Single/SingleProvider.Provider.cs +++ b/QuantBox.APIProvider/Single/SingleProvider.Provider.cs @@ -16,6 +16,8 @@ namespace QuantBox.APIProvider.Single public partial class SingleProvider:Provider { private Logger xlog; + private Logger alog; + private Logger plog; private Logger barLog; private Logger tickLog; @@ -56,6 +58,8 @@ 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"); @@ -64,7 +68,7 @@ public void Init(byte id, string name) EmitBidAsk = true; EmitLevel2Snapshot = false; //UpdateInstrument = true; - EmitHistoricalData = true; + EnablEmitHistoricalData = true; FilterDateTime = true; EnableEmitData = true; HasPriceLimit = true; @@ -87,7 +91,7 @@ public void Init(byte id, string name) historicalDataIds = new Dictionary(); // ConfigPath在做Setting时已经做了 - //Load(); + Load(); } void SessionTimeList_ListChanged(object sender, ListChangedEventArgs e) diff --git a/QuantBox.API.Provider/Single/SingleProvider.Settings.cs b/QuantBox.APIProvider/Single/SingleProvider.Settings.cs similarity index 93% rename from QuantBox.API.Provider/Single/SingleProvider.Settings.cs rename to QuantBox.APIProvider/Single/SingleProvider.Settings.cs index 302eee1..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"; @@ -130,9 +127,12 @@ 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; set; } [Category(CATEGORY_COMMON)] @@ -140,12 +140,18 @@ public int QueryPositionInterval 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,7 +179,7 @@ public int QueryPositionInterval [Category(CATEGORY_HISTORICAL_DATA)] [Description("【历史】是否触发EmitHistoricalData事件")] [DisplayName("EmitHistoricalData")] - public bool EmitHistoricalData { get; set; } + public bool EnablEmitHistoricalData { get; set; } [Category(CATEGORY_HISTORICAL_DATA)] [Description("【历史】是否过滤数据日期和时间")] 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 92% rename from QuantBox.API.Provider/UI/ApiControlForm.cs rename to QuantBox.APIProvider/UI/ApiControlForm.cs index 4b09b61..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() @@ -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 99% rename from QuantBox.API.Provider/UI/ApiManagerForm.Designer.cs rename to QuantBox.APIProvider/UI/ApiManagerForm.Designer.cs index 7c5d0f2..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 @@ -288,7 +289,7 @@ private void InitializeComponent() } - #endregion +#endregion private System.Windows.Forms.ListBox listBox_UserList; private System.Windows.Forms.PropertyGrid propertyGrid; @@ -310,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 99% rename from QuantBox.API.Provider/UI/ApiManagerForm.cs rename to QuantBox.APIProvider/UI/ApiManagerForm.cs index 1bee632..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() @@ -169,4 +171,5 @@ 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 91% rename from QuantBox.API.Provider/UI/ApiTypeSelectorEditor.cs rename to QuantBox.APIProvider/UI/ApiTypeSelectorEditor.cs index b42b388..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; @@ -44,7 +43,7 @@ protected override void FillTreeWithData(ObjectSelectorEditor.Selector selector, { 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/QuantBox.Extensions.csproj b/QuantBox.Extensions/QuantBox.Extensions.csproj index 7c011a7..1283ef8 100644 --- a/QuantBox.Extensions/QuantBox.Extensions.csproj +++ b/QuantBox.Extensions/QuantBox.Extensions.csproj @@ -1,79 +1,17 @@ - - - + + - Debug - AnyCPU - {D8D82E27-47F5-4579-92AD-416DA86AD601} - Library - Properties - QuantBox.Extensions - QuantBox.Extensions - v4.6.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 - 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