经过连续一个月的封装斗争,这个T+0的交易工具终于完成了。
交易工具针对团队的手工T+0交易,能非常个性化的辅助交易、完美实现报撤单和查看实时持仓的需求。
一、从技术男的角度来解读这个交易工具的核心技术部分:
工具大量使用了win底层的Api;
1)、鼠标全局钩子:
使用鼠标全局钩子捕捉鼠标的动作,使用到了以下图的Api函数
2)、捕捉全局鼠标动作:
HOOKPROC hProc = new AllWinApi.HOOKPROC(MouseHookProc);
int hHook = AllWinApi.SetWindowsHookEx(EnumHookType.WM_MOUSE_LL, hProc, IntPtr.Zero, 0);
注册了全局钩子以后,使用Api的回调函数开始捕捉鼠标的单击行为
3)、winform调用
实现了这个钩子的封装以后,直接在需要调用的窗体启动时实例化钩子(窗体dispose的时候卸载钩子),如下操作:
4)、注册/卸载快捷键(HotKey)
注册快捷键这一步对工具实现业务逻辑是非常重要的,因为在不同的场景下需要反复使用,所以这里将其封装起来
二、从技术男的角度来解读这个交易工具的核心业务部分:
1)、快捷键enter实现报单:
case 105:
if (hft.ThisForm == null) return;
var myPanel = GetT<Panel>(hft.ThisForm, "FormPanelName");
//var myPanel = GetPanel(hft.ThisForm, "FormPanelName");
var cName5 = hft.ThisForm.Text;
if (!CodeNameFFFFdict.ContainsKey(cName5))
{
MessageBox.Show($"找不到该合约对应的窗体,请重新操作...");
return;
}
var allFFFF5 = CodeNameFFFFdict[cName5];
var TF1 = allFFFF5.checkPanelTimes.F1;
var TF2 = allFFFF5.checkPanelTimes.F2;
var TF3 = allFFFF5.checkPanelTimes.F3;
var TF4 = allFFFF5.checkPanelTimes.F4;
if (tradeField.code != null && myPanel != null && (TF1 > 0 || TF2 > 0 || TF3 > 0 || TF4 > 0)){
try{
tuple_ = GetPanelTradeData(myPanel);
OrderInsertInfoDataAny orderInsert = new OrderInsertInfoDataAny{
InstrumentID = tradeField.code,
Direction = tradeField.directionTypeAny,
CombOffsetFlag = tradeField.offsetFlagTypeAny,
ExchangeID = tradeField.exchangeIDTypeAny,
VolumeTotalOriginal = tuple_.Item1,
LimitPrice = tuple_.Item2
};
var orderR = OnOrderInsertEvent?.Invoke(orderInsert);
myPanel.Visible = false; //隐藏panel控件
allFFFF5.checkPanelTimes = new CheckPanelTimes();
CodeNameFFFFdict[cName5] = allFFFF5;
ten.Log($"发出一笔委托订单:{orderR}");
}
catch (Exception e){
ten.Log($"推送订单失败,error:{e.Message}");
}
/// <summary>
/// 接收委托订单信息
/// </summary>
/// <param name="obj"></param>
private string Form_OnOrderInsertEvent(OrderInsertInfoDataAny obj)
{
return TradeApi.OrderInsert(obj.InstrumentID, obj.ExchangeID, obj.Direction, obj.CombOffsetFlag, obj.LimitPrice, obj.VolumeTotalOriginal);
}
2)、收集所有委托回报、维护未成交队列
/// <summary>
/// 记录所有未成交的RtnOrder
/// 1.从OnRtnOrder开始记录
/// 2.一旦成交(全部、部分),则修改队列中的数据(全部成交则提出队列,部分成交则修改未成交数量)
/// </summary>
public Dictionary<string, OrderReturnInforDataAny> NotTradeQueeingDict;
private void TradeApi_OnRtnOrder(IBaseTdApi arg1, OrderReturnInforDataAny arg2)
{
var traded = EnumOrderStatusTypeAny.AllTraded;
var v_orinal = arg2.VolumeTotalOriginal;
var status = arg2.OrderStatus;
var msg = arg2.OrderStatusMsg;
if (msg == "未成交" || msg == "部分成交")
NotTradeQueeingDict[arg2.OrderRef] = arg2;
else if (status == traded || v_orinal == arg2.VolumeTraded || msg == "已撤单")
{
if (msg == "全部成交")
AllTradedQueeingDict[arg2.OrderRef] = arg2;
NotTradeQueeingDict.Remove(arg2.OrderRef);
}
if (arg2.OrderStatusMsg.Contains("拒绝"))
Log($"{arg2.Code}:OrderRef为{arg2.OrderRef}==>{arg2.OrderStatusMsg}");
if(NotTradeQueeingDict.Count()>=1||(NotTradeQueeingDict.Count()==0&& msg == "已撤单")
||(NotTradeQueeingDict.Count() == 0 && msg == "全部成交"))
OnNotTradeQueeing?.Invoke(NotTradeQueeingDict);
}
/// <summary>
/// 期指和股票撤单
/// </summary>
/// <param name="arg"></param>
public void GetCancle(OrderReturnInforDataAny arg)
{
lock (o)
{
try
{
//实盘撤单逻辑
if (NotTradeQueeingDict.Keys.Contains(arg.OrderRef))
this.TradeApi.OrderAction(arg.Code, arg.OrderSysID, arg.SessionID, arg.FrontID, arg.OrderRef);
else
this.TradeApi.OrderAction(arg.Code, arg.OrderSysID, arg.SessionID, arg.FrontID, arg.OrderRef);
}
catch (Exception e)
{ this.Log($"撤单报错:{e.Message}" ); }
}
}
3)、获取实时账户持仓信息
/// <summary>
/// 持仓明细
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
/// <param name="arg3"></param>
/// <param name="arg4"></param>
/// <param name="arg5"></param>
private void TradeApi_OnRspQryInvestorDetailPosition(IBaseTdApi arg1, OnRspInvestorPositonDelailAny arg2, ErrorInfoAny arg3, int arg4, bool arg5)
{
OnPositionDelailField?.Invoke(arg2);
}
4)、确保行情账户持续在线(断线重连)
private void QuotaApiStock_deep_QuoteApiDisConnected(IBaseMdApi arg1, int arg2)
{
//API断线重连
//启一个线程去执行API重连的任务
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Task d = Task.Run(() =>
{
this.Log("行情:" + arg1.ApiType.ToString() + "," + 5 + "秒后自动重连...");
Thread.Sleep(5000); //sleepN秒
ReConnectFlag = true;
arg1.Init();
});
d.Wait(token);
}
三、工具最后成型的可视化展示:
1)、主界面
2)、委托界面
3)、交易面板
4)、未成交、实时持仓
5)、结案
QQ:1013359736
想进一步了解的朋友们,可以加我QQ,^_^