免责声明:1、本系列教程仅用于个人开发技术学习使用,请勿用于商用、投资等除学习以外的任何领域。否则造成的纠纷或者损失,本人不承担任何责任。2、本系列教程不保证任何功能的正确性、完整性和有效性,请谨慎使用。3、本系列教程不保证任何功能的持续性和可用性,作者保留随时修改、中断、终止本系列教程的权利,且不承担任何责任。4、本系列教程中所涉及的任何第三方资料、链接等均不代表本人的观点和立场,本人不承担任何责任。

开源项目地址:

gitee:https://gitee.com/dreamer_j/WeskyQuantClient

github:https://github.com/LittleLittleRobot/WeskyQuantClient/tree/master

代码更新详解:

本文属于第一篇文章,所以内容是从创建开始,新增了一些基础内容。

目前整体项目结构内容如下:

系统开发环境说明: 

开发工具:VS2022社区版

开发环境.NET版本:.NET 8

备注:【强烈建议使用同款开发工具和SDK版本进行开发或者打开本项目,防止出现不兼容情况。包括项目后续使用的引用的nuget包版本等,强烈建议使用与教程同款进行搭建,否则可能出现兼容性问题,或者与本教程演示的效果不相符的情况】

操作系统:使用的是Windows 11家庭版。系统版本无特殊要求,win10以上都行。

个人系统简要配置如下:无特殊要求,能跑就行。内存建议32G或以上均可。

基础框架开发详细介绍:

 Wesky.Quant.Client是主程序,Wesky.Quant.Client.ExtensionLibrary是拓展程序,主要用于安装外部nuget包。

Wesky.Quant.Client.ExtensionLibrary拓展程序目前安装以上这些nuget包,如果喜欢自己动手实践的,建议采用同版本的包。

gmsdk是掘金平台的SDK,本项目使用的WPF,采用prism框架来实现MVVM,所以需要安装两个prism的包。Serilog用来写入本地日志使用。最下面的OpenTools是我几年前开发的一个简单工具包,里面有EMA计算公式,所以直接引用下来备用。其他几个是系统包,用于操作Json文件、字符编码等使用。

Wesky.Quant.Client主程序,目前包含几个简易结构,Domain文件夹下面用来存放策略、通用服务或者业务等内容,Infrastructure文件夹打算用来存放工具箱、底层基础服务等,目前已有一个读取Json配置文件的帮助类;QuantExtensionServices文件夹会用来存放拓展服务类,一般是启动程序时候进行加载的拓展服务类,例如日志配置服务等;ViewModels和Views是Prism框架的一个开发规范,默认客户端页面(View)和VM业务层(ViewModel)分别存放这两个文件夹下面,prism框架会自动识别。App.xmal是启动项,上面可以进行一些配置,类似webapi程序的startup启动项。

具体展开说明: App.xmal.cs里面,预设了全局异常捕获事件、客户端多开限制、服务注册入口等。具体可参考以下代码:

public partial class App : PrismApplication {     public App()     {         Startup += AppStartup;         Exit += AppExit;     }     private void AppExit(object sender, ExitEventArgs e)     {         //     }     private void AppStartup(object sender, StartupEventArgs e)     {         //UI线程未捕获异常处理事件         DispatcherUnhandledException += App_DispatcherUnhandledException; ;         //Task线程内未捕获异常处理事件         TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; ;         //非UI线程未捕获异常处理事件         AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; ;     }     private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)     {         try         {             string msg;             if (e.ExceptionObject is Exception ex)             {                 msg = ExceptionToString(ex, "非UI线程");             }             else             {                 msg = $"发生了一个错误!信息:{e.ExceptionObject}";             }             Log.Error(msg);         }         catch (Exception ex)         {             string msg = ExceptionToString(ex, "非UI线程 处理函数");             Log.Error(msg);         }     }     private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)     {         try         {             string msg = ExceptionToString(e.Exception, "Task线程");             Log.Error(msg);             e.SetObserved(); //设置该异常已察觉(这样处理后就不会引起程序崩溃)         }         catch (Exception ex)         {             string msg = ExceptionToString(ex, "Task线程 处理函数");             Log.Error(msg);         }     }     private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)     {         try         {             e.Handled = true;             string msg = ExceptionToString(e.Exception, "UI线程");             Log.Error(msg);         }         catch (Exception ex)         {             string msg = ExceptionToString(ex, "UI线程 处理函数");             Log.Error(msg);         }     }     /// <summary>     /// 提取异常信息     /// </summary>     private static string ExceptionToString(Exception ex, string info)     {         StringBuilder str = new StringBuilder($"{DateTime.Now}, {info}发生了一个错误!{Environment.NewLine}");         if (ex.InnerException == null)         {             str.Append($"【对象名称】:{ex.Source}{Environment.NewLine}");             str.Append($"【异常类型】:{ex.GetType().Name}{Environment.NewLine}");             str.Append($"【详细信息】:{ex.Message}{Environment.NewLine}");             str.Append($"【堆栈调用】:{ex.StackTrace}");         }         else         {             str.Append($"【对象名称】:{ex.InnerException.Source}{Environment.NewLine}");             str.Append($"【异常类型】:{ex.InnerException.GetType().Name}{Environment.NewLine}");             str.Append($"【详细信息】:{ex.InnerException.Message}{Environment.NewLine}");             str.Append($"【堆栈调用】:{ex.InnerException.StackTrace}");         }         return str.ToString();     }     static volatile int currentMainThreadID = Thread.CurrentThread.ManagedThreadId;     //这个属性表示当前执行线程是否在主线程中运行     public static bool IsRunInMainThread { get { return Thread.CurrentThread.ManagedThreadId == currentMainThreadID; } }     protected override Window CreateShell()     {         return Container.Resolve<QuantMainView>();     }     private static Mutex instance;     protected override void InitializeShell(Window shell)     {         instance = new Mutex(true, "WeskyQuantClient", out bool createdNew); // 防止同一台电脑上多开         if (createdNew)         {             instance.ReleaseMutex();             base.InitializeShell(shell);         }         else         {             MessageBox.Show("客户端已经启动,请勿重复启动");             System.Windows.Application.Current?.Shutdown();         }     }     protected override void RegisterTypes(IContainerRegistry containerRegistry)     {         /// 全局对象注册         containerRegistry.Register<Dispatcher>(() => System.Windows.Application.Current.Dispatcher);         //  注册弹出窗         containerRegistry.RegisterDialogWindow<DialogWindow>();         // 注册系统服务         var serviceCollection = new ServiceCollection();         serviceCollection.AddSingleton(new ReadJsonHelper());         serviceCollection.AddSerilogConfiguration(); // 初始化日志配置信息(写入本地文件)         serviceCollection.AddHttpClient();         var provide = serviceCollection.BuildServiceProvider();         QuantServiceProviderStatic.QuantServiceProvider = provide;          // 注册编码         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);     } }

日志扩展服务,用于提供通用日志模板,例如每天都新建一个日志文件,并且最多存储200天的日志,超出部分会被自动删除:

public static  class SerilogExtensionService{    public static void AddSerilogConfiguration(this IServiceCollection services)    {        if (services == null)        {            throw new ArgumentNullException(nameof(services));        }        // 初始化本地日志文件写入格式        Log.Logger = new LoggerConfiguration()            .MinimumLevel.Verbose()            .WriteTo.Async(a => a.File(System.Environment.CurrentDirectory + $"\\Logs\\.log", rollingInterval: RollingInterval.Hour,                                            outputTemplate: "{NewLine}DateTime:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}",                                            retainedFileCountLimit: 200))            .CreateLogger();    }}

Json文件读取帮助服务,用于提供一个简易版Json数据读取功能,包括key-value读取方式、key-泛型对象读取方式两种:

public class ReadJsonHelper {     private static IConfiguration _config;     public ReadJsonHelper()     {         string contentPath = $"{AppDomain.CurrentDomain.SetupInformation.ApplicationBase}";         _config = new ConfigurationBuilder()             .SetBasePath(contentPath)            .Add(new JsonConfigurationSource { Path = "weskyquant.json", Optional = false, ReloadOnChange = true })//直接读目录里的json文件            .Build();     }     /// <summary>     /// 读取指定节点的字符串     /// </summary>     /// <param name="sessions"></param>     /// <returns></returns>     public static string Read(params string[] sessions)     {         try         {             if (sessions.Any())             {                 return _config[string.Join(":", sessions)];             }         }         catch         {             return "";         }         return "";     }     public static object ReadObject(string sessions)     {         try         {             if (sessions.Any())             {                 return _config[sessions];             }         }         catch         {             return "";         }         return "";     }     /// <summary>     /// 读取实体信息     /// </summary>     /// <typeparam name="T"></typeparam>     /// <param name="session"></param>     /// <returns></returns>     public static List<T> Read<T>(params string[] session)     {         List<T> list = new List<T>();         _config.Bind(string.Join(":", session), list);         return list;     }     public static string GetGenerateId()     {         long i = 1;         foreach (byte b in Guid.NewGuid().ToByteArray())         {             i *= ((int)b + 1);         }         return string.Format("{0:x}", i - DateTime.Now.Ticks);     } }

由于一些系统服务,或者基于IServiceCollection进行拓展的服务,使用prism不方便注册,所以提供一个自定义的全局容器,用于方便获取系统服务或者拓展服务的实例:

public class QuantServiceProviderStatic {     public static IServiceProvider QuantServiceProvider { get; set; } }

如果以上一些都准备完整,运行程序,会打开一个空白的客户端页面。第一篇序章完成~

如果需要该项目代码的答疑,可上星球进行提问~ 

如果需要源码,请根据文章开头提示自行fork或者下载,我使用的Apache-2.0开源协议许可,大家感兴趣可以自行学习使用。如果对这方面开发技术有疑问(纯新手不建议),可以扫上面的星球加入我的星球,提供技术答疑服务。

也欢迎关注博主公众号:Dotnet Dancer  ,后续新增文章也会公开到该公众号上进行同步。该系列文章仅用于学习交流使用,大家请勿玩飘了就行~

Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐