策略核心类
为了保证回测与仿真的策略代码一致性,我们定义一个接口类,将后续编写策略即将用到的属性和方法在该类中一一列出,回测基类和仿真的类只需继承该类即可,下面我们先解析下IStrategy接口。
using System;
using System.Collections.Generic;
namespace EPI.CSharp.Model
{
/// <summary>
/// 策略接口
/// </summary>
public interface IStrategy
{
/// <summary>
/// 加载数据次数
/// </summary>
int LoadDataCount { get; set; }
/// <summary>
/// 策略配置
/// </summary>
Settings Setting { get; set; }
/// <summary>
/// 加载Bar数据
/// </summary>
/// <param name="contract">合约</param>
/// <param name="cycle">周期</param>
/// <param name="start">开始时间(包含)</param>
/// <param name="end">结束时间(不包含)</param>
/// <param name="isBarBaseOnTick">Bar是基于Tick生成</param>
bool LoadBarDatas(string contract, string cycle, string start, string end,
bool isBarBaseOnTick);
/// <summary>
/// 加载Bar数据
/// </summary>
/// <param name="contract">合约</param>
/// <param name="cycle">周期</param>
/// <param name="takeNumber">条数</param>
/// <param name="isBarBaseOnTick">Bar是基于Tick生成</param>
/// <returns></returns>
bool LoadBarDatas(string contract, string cycle, int takeNumber, bool
isBarBaseOnTick);
/// <summary>
/// 数据加载完成
/// </summary>
/// <param name="contract">合约</param>
/// <param name="cycle">周期</param>
/// <param name="barDatas">数据</param>
/// <param name="isLast">是否最后一批数据</param>
void FinishedLoadData(string contract, string cycle, List<BarData>
barDatas, bool isLast);
/// <summary>
/// 初始化
/// </summary>
/// <param name="sender">策略参数</param>
/// <returns></returns>
bool InitStrategy(object sender);
/// <summary>
/// 初始化配置
/// </summary>
/// <param name="setting">配置</param>
/// <returns></returns>
bool InitSetting(string setting);
/// <summary>
/// 初始化入库字段
/// </summary>
void InitDBFields();
/// <summary>
/// 保存入库字段
/// </summary>
void SaveDBFields();
/// <summary>
/// 初始化策略
/// </summary>
bool StartStrategy();
/// <summary>
/// 写日志
/// </summary>
/// <param name="msg">消息</param>
/// <param name="isError">是否错误</param>
void Log(string msg, bool isError = false);
/// <summary>
/// 写日志
/// </summary>
/// <param name="title">标题</param>
/// <param name="ex">错误</param>
void Log(string title, Exception ex);
/// <summary>
/// 获取前根Bar
/// </summary>
/// <param name="contract">合约</param>
/// <param name="cycle">周期</param>
/// <param name="num">前面第几根(0:前一根,1:前前根)</param>
/// <returns></returns>
BarData GetPreData(string contract, string cycle, int num = 0);
/// <summary>
/// 买入
/// </summary>
/// <param name="contract">合约</param>
/// <param name="number">数量</param>
/// <returns></returns>
string Buy(string contract, int number);
/// <summary>
/// 买入
/// </summary>
/// <param name="contract">合约</param>
/// <param name="price">价格</param>
/// <param name="number">数量</param>
/// <returns></returns>
string Buy(string contract, double price, int number);
/// <summary>
/// 卖出
/// </summary>
/// <param name="contract">合约</param>
/// <param name="number">数量</param>
/// <returns></returns>
string Sell(string contract, int number);
/// <summary>
/// 卖出
/// </summary>
/// <param name="contract">合约</param>
/// <param name="price">价格</param>
/// <param name="number">数量</param>
/// <returns></returns>
string Sell(string contract, double price, int number);
/// <summary>
/// 撤销订单
/// </summary>
/// <param name="clientOrderId">订单号</param>
bool Cancel(string clientOrderId);
/// <summary>
/// 用于接收Tick数据(如果在里面写大量逻辑,可能导致丢包)
/// </summary>
/// <param name="tickData"></param>
void RtnTickData(TickData tickData);
/// <summary>
/// 用于接收Bar数据(如果在里面写大量逻辑,可能导致丢包)
/// </summary>
/// <param name="barData">Bar数据</param>
/// <param name="isNewBar">是否新Bar</param>
void RtnBarData(BarData barData, bool isNewBar);
/// <summary>
/// 返回交易回报
/// </summary>
/// <param name="order"></param>
void RtnOrder(Orders order);
/// <summary>
/// 返回成交明细
/// </summary>
/// <param name="trade"></param>
void RtnTrade(Trades trade);
/// <summary>
/// 返回消息
/// </summary>
/// <param name="msg"></param>
void RtnMessage(Messages msg);
/// <summary>
/// 停止策略
/// </summary>
void StopStrategy();
/// <summary>
/// 获取持仓信息
/// </summary>
/// <returns></returns>
Positions GetPosition(string contract);
/// <summary>
/// 获取策略报告
/// </summary>
/// <returns></returns>
Reports GetReport();
/// <summary>
/// 获取合约信息
/// </summary>
/// <param name="contract"></param>
/// <returns></returns>
Contracts GetContract(string contract);
/// 转换参数
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
string[] ConvertParams(object sender);
}
}
策略编写流程
其中需要特殊说明的是:由于加载历史数据采用的是异步方式,我们通过统计返回加载数据完成次数与预设的加载次数相同来判断本次所有数据加载是否完成,即LoadBarDatas的调用次数必须与用户设置LoadDataCount的值相等代表完成,这样才会触发回调FinishedLoadData方法,具体参见Github源码。
回测基类
回测类首选要模拟测试数据,并且异步推送数据,并且实时进行结算,最后生成策略报告。
using EPI.CSharp.Commons;
using EPI.CSharp.Model;
using EPI.CSharp.Model.Enums;
using EPI.CSharp.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace EPI.CSharp.Api
{
public class BaseFastTest:IStrategy
{
#region 私有变量
string preTradeDay;
string minCycle;
bool isChangedDay;
Reports report;
List<BarData> totalCsvDataList; //Csv文件数据
List<DailyMoneys> dailyMoneyList; //每日资金队列
List<BarData> testBarDataList; //测试Bar数据集
List<string> pendingOrderList; //挂单队列
List<Trades> strategyRspTradeList; //策略成交队列
List<Orders> strategyRspOrderList; //策略委托队列
Dictionary<string, DateTime> startTimeDict; //测试开始时间字典
Dictionary<string, List<BarData>> preBarsDict; //获取前几根Bar字典
Dictionary<string, BarData> curBarDict; //实时bar数据字典
Dictionary<string, Contracts> contractDict; //合约信息字典
Dictionary<string, Positions> positionDict; //持仓字典
#endregion
public BaseFastTest(int userId, string strategyId)
{
report = new Reports()
{
Id = Guid.NewGuid().ToString("N"),
StrategyId = strategyId,
UserId = userId
};
totalCsvDataList = new List<BarData>();
pendingOrderList = new List<string>();
testBarDataList = new List<BarData>();
dailyMoneyList = new List<DailyMoneys>();
preBarsDict = new Dictionary<string, List<BarData>>();
curBarDict = new Dictionary<string, BarData>();
contractDict = new Dictionary<string, Contracts>();
positionDict = new Dictionary<string, Positions>();
startTimeDict = new Dictionary<string, DateTime>();
strategyRspTradeList = new List<Trades>();
strategyRspOrderList = new List<Orders>();
}
#region 委托方法
public delegate void OnRtnLogDelegate(string msg, bool isError = false);
public event OnRtnLogDelegate OnRtnLogEvent;
public delegate void OnRtnErrorDelegate(string title, Exception ex);
public event OnRtnErrorDelegate OnRtnErrorEvent;
public delegate void TestFinishedDelegate(
Reports report,
Dictionary<string, Positions> positionDict,
List<DailyMoneys> dailyMoneys,
List<Trades> trades);
public event TestFinishedDelegate OnTestFinishedEvent;
#endregion
#region 公共属性
/// <summary>
/// 策略配置
/// </summary>
public Settings Setting { get; set; }
/// <summary>
/// 加载数据次数
/// </summary>
public int LoadDataCount { get; set; }
public bool IsSavedToCsv { get; set; }
#endregion
#region 私有方法
/// <summary>
/// 设置最小周期
/// </summary>
/// <param name="cycleArray"></param>
void SetMinCycle(string[] cycleArray)
{
if (cycleArray != null && cycleArray.Length > 0)
{
int minNumber = int.MaxValue;
minCycle = "1M";
foreach (var cycle in cycleArray)
{
int number = int.Parse(cycle.Substring(0, cycle.Length - 1));
switch (cycle[cycle.Length - 1])
{
case 'H': number = number * 60; break;
case 'D': number = number * 60 * 24; break;
case 'W': number = number * 60 * 24 * 7; break;
case 'Y': number = number * 60 * 24 * 365; break;
}
if (number < minNumber)
{
minNumber = number;
minCycle = cycle;
}