js与python 通过websocket通信
## user.html
{% extends "monitor.html" %}
{% load staticfiles %}
{% block title %}仿真策略监控{% endblock %}
{% block styles %}
th {
text-align: center;
}
label {
width: 100px;
}
input, select {
width: 160px;
height: 24px;
background: #0F0F0F;
border: 1px solid #3C3C3C;
border-radius: 1px;
}
button {
border: none;
}
thead {
background-color: #444;
}
.bootstrap-table {
border-style: solid;
border-width: 3px;
}
.pagination-info {
display: none
}
.page-list {
display: none
}
#sse {
color: white;
}
#startstg .modal-body {
background-color: #282923;
padding: 0px;
}
{#无数据时鼠标悬浮的背景颜色#}
.table-hover > tbody > tr:hover {
background-color: #444444;
cursor: pointer;
}
{#无数据时的奇数背景颜色#}
{# .table-striped > tbody > tr:nth-of-type(odd) {#}
{# background-color: transparent;#}
{# }#}
#StgFileShow, #ConFileShow {
background-color: #282923;
color: #90918b;
border-width: 0px;
width: 448px;
height: 100%;
}
#stg_check {
padding: 10px;
}
.row {
margin-top: 10px;
}
.left {
width: 60%;
height: 400px;
float: left;
}
.right {
width: 37%;
height: 400px;
float: left;
{#margin-right: 30px;#}
}
.buttom {
width: 97%;
height: 400px;
float: left;
margin-top: 90px;
}
{#tab表头的选中的样式#}
.nav-tabs-custom > .nav-tabs > li.active > a, .nav-tabs-custom > .nav-tabs > li.active:hover > a {
background-color: red;
color: white;
}
{#tab样式#}
.nav-tabs-custom > .nav-tabs > li {
border-top: transparent;
margin-bottom: -2px;
margin-right: 2px;
}
.nav-tabs > li {
float: left;
margin-bottom: -1px;
}
.nav > li > a {
position: relative;
display: block;
padding: 4px 20px;
}
{#tab头的位置及边框颜色透明#}
.nav-tabs-custom > .nav-tabs {
border-bottom-color: rgba(0, 0, 0, 0.2);
border-top-right-radius: 3px;
border-top-left-radius: 3px;
margin-left: 10px;
}
{#tab中的a标签悬浮样式#}
.nav-tabs-custom > .nav-tabs > li > a, .nav-tabs-custom > .nav-tabs > li > a:hover {
background: rgba(255, 255, 255, .15);
margin: 0;
}
.nav-tabs-custom > .nav-tabs > li > a {
color: #d2d6de;
border-radius: 0;
}
.simcheck {
float: left;
margin-top: 15px;
margin-left: 10px
}
.packheadiv {
float: left;
margin-top: 15px;
margin-left: 10px
}
.cancelheadiv {
float: left;
margin-top: 15px;
margin-left: 10px
}
.simcheckhead, .simHead, .packHead {
color: white;
margin-left: 30px;
}
.cancelheadiv, .packheadiv, .traheadiv {
margin-left: 30px;
}
.bootstrap-table .fixed-table-container .table {
width: 100%;
margin-bottom: 0 !important;
color: #939393;
}
.fixed-table-toolbar {
background-color: #141414;
}
.checksearch, .packsearch, .simsearch {
width: 87px;
height: 26px;
background: #3C3C3C;
border-radius: 3px;
}
.cancelorder {
width: 119px;
height: 28px;
background: inherit;
background-color: rgba(255, 204, 153, 1);
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgba(121, 121, 121, 1);
border-radius: 5px;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
color: #2B2B2B;
margin-left: 15%;
cursor: pointer;
}
tr.activetable, input.activetable {
background-color: white;
width: 114px;
height: 30px;
}
{% endblock %}
{% block content %}
仿真策略监控
{{ allHtml.title }}
用户名:
密 码:
合约:
用户登录查看到的数据
{% endblock %}
{% block scripts %}
//页面加载后就调用
{#import lo from "../../../commonStatic/bower_components/moment/src/locale/lo";#}
{#$(function () {#}
{# alert("qew")#}
{# })#}
var timer, clickFlag, stgtype = false;//外部变量,这三个变量是定时器是否存在的标志
//初始化策略列表
$("#RuleTable").bootstrapTable('destroy').bootstrapTable({
uniqueId: "RuleID",
// 策略列表table
columns: [{
field: 'RuleID',
title: '策略ID'
}, {
field: 'RuleName',
title: '策略名称'
}, {
field: 'LServer',
title: '指定服务器',
{#formatter: function (value, row, index) {#}
{# $.post("{% url 'trade:serverfind' %}", {"ruleid": row.RuleID}, function (r) {#}
{# if (r){#}
{# console.log("123adce", r)#}
{# return r#}
{# }#}
{# })#}
{# }#}
}, {
field: 'RuleRunStatus',
title: '运行状态',
formatter: function (value, row, index) {
if (value === "0") {
return "停止"
} else if (value === "1") {
return "启动"
} else {
return value
}
}
}, {
field: 'LOption',
title: '操作',
formatter: function (value, row, index) {
if (row.RuleRunStatus === "1") {
var start = " 启动 ";
var end = "  停止";
} else if (row.RuleRunStatus === "0") {
var start = " 启动 ";
var end = "  停止";
}
return start + end + "  人工录入";
}
},
{
field: 'LRisk',
title: '风控',
}],
onClickRow: function (row, $element, field) {
{#console.log(rules)#}
//当前行的父元素tbody下的所有tr移除样式类
$element.parent().children().removeClass("onclickrow")
$element.addClass("onclickrow")
var ruleid = row.RuleID
//点击一行数据,初始化策略参数和策略指标
RulePropIndic(ruleid)
//刚开始需要把上次的定时器取消掉,然后再进行定时任务。
if (clickFlag) {
clearInterval(timer)
}
clickFlag = true;
timer = setInterval(function () {
//先移除所有的数据,然后再append
$('#StgparaTable').bootstrapTable('removeAll');
$('#StgdicTable').bootstrapTable('removeAll');
for (let key in ruleProps) {
//策略参数
if (key.startsWith(ruleid + "prop")) {
$('#StgparaTable').bootstrapTable('append', ruleProps[key]);
}
//策略指标
else if (key.startsWith(ruleid + "indic")) {
$('#StgdicTable').bootstrapTable('append', ruleProps[key]);
}
}
var clickFlag = false
}, 2000)
//关闭定时器
{#clearInterval(timer)#}
}
})
//初始化策略参数
$("#StgparaTable").bootstrapTable('destroy').bootstrapTable({
{#uniqueId: "RuleID" + "prop_" + "PropKey",#}
// 策略列表table
columns: [{
field: 'RuleID',
title: '策略ID',
visible: false
}, {
field: 'PropKey',
title: '参数名'
}, {
field: 'PropValue',
title: '参数值'
}, {
field: 'Description',
title: '描述',
}, {
field: 'POption',
title: '操作',
formatter: function (value, row, index) {
return "
","+ "'" +row.PropValue+"'"+"," + "'" + row.Description+"')\" class='btn btn-success btn-xs btn-flat btn_operation' data-toggle='modal' data-target='#startstg'> 编辑
"}
}]
})
//初始化策略指标
$("#StgdicTable").bootstrapTable('destroy').bootstrapTable({
uniqueId: "RuleID",
// 策略列表table
columns: [{
field: 'RuleID',
title: '策略ID',
visible: false
}, {
field: 'IndicatorKey',
title: '指标名'
}, {
field: 'IndicatorValue',
title: '指标值'
}, {
field: 'Description',
title: '指标描述',
}]
})
//初始化做市策略
$("#StgMMTable").bootstrapTable('destroy').bootstrapTable({
uniqueId: "RuleID",
// 做市策略table
columns: [
//第一行表头
[{
field: 'RuleID',
title: '',
{#visible: false#}
}, {
field: 'mm',
title: '做市券盘口',
colspan: 2
}, {
field: 'hedge',
title: '对冲券盘口',
colspan: 2
}, {
field: '',
title: '',
colspan: 2
}, {
field: "my",
title: "我方报价",
colspan: 2
}, {
field: '',
title: '',
colspan: 2
}],
//第二行表头
[{
field: "RuleName",
title: "策略名称"
}, {
field: "bid_mm",
title: "Bid"
}, {
field: "ofr_mm",
title: "Ofr"
}, {
field: "bid_hedge",
title: "Bid"
}, {
field: "ofr_hedge",
title: "Ofr"
}, {
field: "spread",
title: "合理利差"
}, {
field: "offset",
title: "偏移"
}, {
field: "bid_my",
title: "Bid"
}, {
field: "ofr_my",
title: "Ofr"
}, {
field: "position",
title: "持仓"
}, {
field: "is_broken",
title: "断腿警告"
}]
]
})
//初始化cta策略
$("#StgCtaTable").bootstrapTable('destroy').bootstrapTable({
uniqueId: "RuleID",
// CTA策略table
columns: [{
field: "RuleName",
title: "策略名称"
}, {
field: "indicator1",
title: "指标1"
}, {
field: "indicator2",
title: "指标2"
}, {
field: "trade_time",
title: "最新成交时间"
}, {
field: "yield",
title: "收益率"
}, {
field: "high",
title: "高"
}, {
field: "low",
title: "低"
}, {
field: "decision",
title: "决策点"
}, {
field: "trigger",
title: "触发点"
},
{
field: "direction",
title: "方向"
},
{
field: "position",
title: "持仓"
},
{
field: "volume_left",
title: "待执行量"
},
{
field: "ytm_signal",
title: "信号价格"
},
{
field: "ytm_limit",
title: "限价"
}]
})
//rules用于存储 策略列表和不同类型的策略, ruleprops用于存储策略参数和策略指标
var rules = {}, ruleProps = {};
var idsarry = []; //windows-server, 47.102.219.52:9000
var ws = $.trade("ws://127.0.0.1:9000/", {//
OnFrontConnected: function () {
$("#console").append("
TD连接成功");
},
OnFrontDisconnected: function () {
$("#console").append("
TD已断开");
},
OnRspUserLogin: function (r) {
//{TID: data:{},RspInfoField:{ErrorID:“”,ErrorMsg:“”}
if (r.RspInfoField.ErrorID == 0) {
$("#console").append("
" + r.data.CustomerID + " 交易登录成功");
user.CustomerID = r.data.CustomerID;
} else
$("#console").append("
交易登录失败:" + r.RspInfoField.ErrorMsg);
},
OnRspError: function (r) {
$("#console").append("
" + r.RspInfoField.ErrorID + ":" + r.RspInfoField.ErrorMsg);
},
OnRtnRule: function (r) {
if (rules[r.data.RuleID] == undefined) {
if (r.data.RuleRunStatus == undefined) {
r.data.RuleRunStatus = 0;
}
$('#RuleTable').bootstrapTable('append', r.data);
} else {
$('#RuleTable').bootstrapTable('updateRow', {
index: r.data.RuleID,
row: r.data
});
}
rules[r.data.RuleID] = r.data;
rules[r.data.RuleID].RuleRunStatus = 0;
{#ruleType[r.data.RuleID] = r.data#}
$("#console").append("
策略信息: " + r.data.RuleID + ":" + r.data.RuleName);
},
OnRtnRuleStatus: function (r) {
rules[r.data.RuleID].RuleRunStatus = r.data.RuleRunStatus;
if (rules[r.data.RuleID].RuleRunStatus == undefined) {
$('#RuleTable').bootstrapTable('append', r.data);
} else {
$('#RuleTable').bootstrapTable('updateRow', {
index: r.data.RuleID,
row: r.data
});
}
$("#console").append("
策略状态 " + r.data.RuleID + ":" + r.data.RuleRunStatus);
},
OnRtnRuleProp: function (r) {
if (rules[r.data.RuleID] !== undefined) {
rules[r.data.RuleID][r.data.PropKey] = r.data.PropValue
}
ruleProps[r.data.RuleID + "prop" + "_" + r.data.PropKey] = r.data
$("#console").append("
策略参数:" + r.data.RuleID + ":" + r.data.PropKey + ":" + r.data.PropValue + ":" + r.data.Description);
},
OnRtnRuleIndicator: function (r) {
if (rules[r.data.RuleID] !== undefined) {
rules[r.data.RuleID][r.data.IndicatorKey] = r.data.IndicatorValue
}
ruleProps[r.data.RuleID + "indic" + "_" + r.data.IndicatorKey] = r.data;
$("#console").append("
策略指标:" + r.data.RuleID + ":" + r.data.IndicatorKey + ":" + r.data.IndicatorValue + ":" + r.data.Description);
},
events: {
say: function (e) {
alert(e.data.name); // 'foo'
alert(e.data.text); // 'baa'
}
}
});
function sendMsg_login() {
var dt = {
"Tid": 1111, "data": {
'CustomerID': '1001',
'Password': '123456',
'MacAddress': '',
'SubInstrumentMethod': '',
'IsRule': ''
}
};
ws.ReqUserLogin("1001", "123456");
}
function submitit() {
$('#rf').submit();
}
function tabs(n) {
if (n == 1) {
$('#fa-stgpara').addClass('active');
$('#fa-stgindic').removeClass('active');
} else {
$('#fa-stgpara').removeClass('active');
$('#fa-stgindic').addClass('active');
}
}
//点击做事类策略或者CTA类策略
function btabs(n) {
initypestg()
if (n == 1) {
$('#fa-stgmm').addClass('active');
$('#fa-stgcta').removeClass('active');
clearInterval(stgtype)
stgtype = setInterval(function () {
//先移除所有的数据,然后再append
$('#StgMMTable').bootstrapTable('removeAll');
$('#StgCtaTable').bootstrapTable('removeAll');
//bootstraptable渲染不同类型的策略
for (let key in rules) {
if (rules[key].hasOwnProperty("bid_mm")) {
$('#StgMMTable').bootstrapTable('append', rules[key]);
} else if (rules[key].hasOwnProperty("trigger")) {
$('#StgCtaTable').bootstrapTable('append', rules[key]);
}
}
}, 2000)
} else {
$('#fa-stgmm').removeClass('active');
$('#fa-stgcta').addClass('active');
clearInterval(stgtype)
stgtype = setInterval(function () {
//先移除所有的数据,然后再append
$('#StgMMTable').bootstrapTable('removeAll');
$('#StgCtaTable').bootstrapTable('removeAll');
//bootstraptable渲染不同类型的策略
for (let key in rules) {
if (rules[key].hasOwnProperty("bid_mm")) {
$('#StgMMTable').bootstrapTable('append', rules[key]);
} else if (rules[key].hasOwnProperty("trigger")) {
$('#StgCtaTable').bootstrapTable('append', rules[key]);
}
}
}, 2000)
}
}
//做事类策略或者cTa类策略的初始化
function initypestg() {
//先移除所有的数据,然后再append
$('#StgMMTable').bootstrapTable('removeAll');
$('#StgCtaTable').bootstrapTable('removeAll');
//bootstraptable渲染做事类策略
for (let key in rules) {
if (rules[key].hasOwnProperty("bid_mm")) {
$('#StgMMTable').bootstrapTable('append', rules[key]);
} else if (rules[key].hasOwnProperty("trigger")) {
$('#StgCtaTable').bootstrapTable('append', rules[key]);
}
}
}
function status(th) {
var StgID = $(th).attr('id');
var S = $(th).attr('status');
Status = S.split('-')[0];
EnvID = S.split('-')[1];
$.post("{{url}}", {
'StgID': JSON.stringify([StgID]),
'Status': JSON.stringify([Status]),
'EnvID': JSON.stringify([EnvID])
}, function (r) {
if (r) {
window.location.reload();
}
})
}
function RefreshIt() {
var opt = {
url: "{{url}}",
silent: true,
query: {
env: $('#env').val()
}
};
$("#RuleTable").bootstrapTable('refresh', opt);
}
function pack(sid) {
$.post('{{url}}', {'sid': sid}, function (r) {
$("#packTable").bootstrapTable('refresh');
})
}
//策略参数和策略指标的定时任务
function RulePropIndic(ruleid) {
//先移除所有的数据,然后再append
$('#StgparaTable').bootstrapTable('removeAll');
$('#StgdicTable').bootstrapTable('removeAll');
for (let key in ruleProps) {
//策略参数
if (key.startsWith(ruleid + "prop")) {
$('#StgparaTable').bootstrapTable('append', ruleProps[key]);
}
//策略指标
else if (key.startsWith(ruleid + "indic")) {
$('#StgdicTable').bootstrapTable('append', ruleProps[key]);
}
}
}
function new_col(idn) {
var h = "
" + '' +'
' οnclick="$(this).parent().parent().remove()">删除行' +
'
' +'
' +"
" +"" +
"
" +"
" +"
";$('#' + idn).append($(h));
};
//上传列表模态框展示
function uploadmodal() {
$("#upmodal").modal("show")
}
//启动策略
function startstg(id) {
ws.ReqOptionStg("start", id);
}
//停止策略
function endstg(id) {
ws.ReqOptionStg("end", id);
}
//编辑策略参数
function modify(id, key, value, desc) {
ws.ReqStgPara(id, key, value, desc)
}
{#加载数据时的样式#}
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading {
align-items: center;
background: #141414;
display: none;
justify-content: center;
position: absolute;
bottom: 0;
width: 100%;
z-index: 1000;
}
{#bootstraptable表格布局样式#}
.bootstrap-table .fixed-table-container .table {
width: 90%;
margin-bottom: 0 !important;
margin: 0 auto;
border: solid #444 !important
}
{#bootstraptable上边框#}
.table-bordered {
border: 1px solid transparent !important;
}
{#bootstraptable左侧边框#}
.table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td {
border: 1px solid rgba(255, 255, 255, .15);
}
{#每一个table的样式#}
.bootstrap-table .fixed-table-container .table thead th .th-inner {
padding: .75rem;
vertical-align: bottom;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid #444 !important;
}
.tab-content {
height: 380px;
}
.stginfo {
height: 400px;
}
.onclickrow {
background-color: #444444;
}
{% endblock %}
// websocket.js
/*
* jQuery Web Sockets Plugin v0.0.4
* https://github.com/dchelimsky/jquery-websocket
* http://code.google.com/p/jquery-websocket/
*
* This document is licensed as free software under the terms of the
* MIT License: http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2010 by shootaroo (Shotaro Tsubouchi).
*/
const TSS_DIALOG = 1;//对话流
const TSS_PRIVATE = 2;//会员私有流
const TSS_PUBLIC = 3;//公共流
const TSS_QUERY = 4;//查询
const TSS_USER = 5;//交易员私有流
//用户启动策略请求
const FTD_TID_ReqOptionStg = 0x00002101;
//编辑策略参数请求
const FTD_TID_ReqStgPara = 0x00002102
//用户登录请求
const FTD_TID_ReqUserLogin = 0x00001001;
//用户登录响应
const FTD_TID_RspUserLogin = 0x00001002;
//用户退出请求
const FTD_TID_ReqUserLogout = 0x00001005;
//用户退出响应
const FTD_TID_RspUserLogout = 0x00001006;
//报单录入请求
const FTD_TID_ReqOrderInsert = 0x00003001;
//报单录入错误时响应
const FTD_TID_RspOrderInsert = 0x00003002;
//报单修改/撤单请求
const FTD_TID_ReqOrderAction = 0x00003003;
//报单修改/撤单错误时响应
const FTD_TID_RspOrderAction = 0x00003004;
//请求查询合约
const FTD_TID_ReqQryInstrument = 0x00002001;
//请求查询合约响应
const FTD_TID_RspQryInstrument = 0x00002002;
//请求查询债券
const FTD_TID_ReqQryBond = 0x00001109;
//请求查询债券响应
const FTD_TID_RspQryBond = 0x0000110A;
//查询债券期货和可交割券关系表
const FTD_TID_ReqQryBondFutureDeliverable = 0x00001105;
//债券期货和可交割券关系表回报
const FTD_TID_RspQryBondFutureDeliverable = 0x00001106;
//请求查询物理账号资金
const FTD_TID_ReqQryAccount = 0x00002005;
//请求查询物理账号资金响应
const FTD_TID_RspQryAccount = 0x00002006;
//请求查询物理账号持仓
const FTD_TID_ReqQryPosition = 0x00002007;
//请求查询物理账号持仓响应
const FTD_TID_RspQryPosition = 0x00002008;
//请求订阅行情
const FTD_TID_ReqSubMarketData = 0x00002009;
//请求退订行情
const FTD_TID_ReqUnSubMarketData = 0x0000200b;
//请求订阅行情BAR
const FTD_TID_ReqSubMarketDataBar = 0x0000200d;
//请求退订行情BAR
const FTD_TID_ReqUnSubMarketDataBar = 0x0000200e;
//请求订阅新本币平台行情
const FTD_TID_ReqSubCFETSMarketData = 0x00001130;
//请求退订新本币平台行情
const FTD_TID_ReqUnsubCFETSMarketData = 0x00001132;
//查询债券成交信息
const FTD_TID_ReqQryBondExecReport = 0x0000110D;
//查询债券成交信息响应
const FTD_TID_RspQryBondExecReport = 0x0000110E;
//请求从行情中心订阅行情
const FTD_TID_ReqSubMDfromMC = 0x00001140;
//请求从行情中心退订行情
const FTD_TID_ReqUnSubMDfromMC = 0x00001142;
//请求订阅策略对应RFQ报价
const FTD_TID_ReqSubRFQByClientID = 0x00001150;
//请求退订策略对应RFQ报价
const FTD_TID_ReqUnSubRFQByClientID = 0x00001152;
//请求增加策略
const FTD_TID_ReqInsertRule = 0x00001010;
//请求增加策略响应
const FTD_TID_RspInsertRule = 0x00001011;
//请求修改策略状态
const FTD_TID_ReqUpdateRuleStatus = 0x00001030;
//请求修改策略状态响应
const FTD_TID_RspUpdateRuleStatus = 0x00001031;
//请求更新策略属性
const FTD_TID_ReqUpdateRuleProp = 0x00001040;
//请求更新策略属性响应
const FTD_TID_RspUpdateRuleProp = 0x00001041;
//策略回报
const FTD_TID_RtnRule = 0x00001012;
//策略运行状态变化回报
const FTD_TID_RtnRuleStatus = 0x00001032;
//策略属性变化回报
const FTD_TID_RtnRuleProp = 0x00001044;
//策略指标回报
const FTD_TID_RtnRuleIndicator = 0x00001045;
//深度行情回报
const FTD_TID_RtnDepthMarketData = 0x00006001;
//行情Bar回报
const FTD_TID_RtnMarketDataBar = 0x00006002;
//报价行情回报
const FTD_TID_RtnQuoteMarket = 0x00001103;
//债券市场成交回报
const FTD_TID_RtnBondExecReport = 0x00001110;
//新本币平台深度行情回报
const FTD_TID_RtnCFETSDepthMarketData = 0x00001133;
//报价接收回报
const FTD_TID_RtnCfetsRFQReceive = 0x00001134;
//CME/CBOT深度行情回报
const FTD_TID_RtnCMEDepthMarketData = 0x00001144;
//报单回报
const FTD_TID_RtnRuleOrder = 0x00001102;
//成交回报
const FTD_TID_RtnRuleTrade = 0x00001111;
//持仓变动回报
const FTD_TID_RtnRulePosition = 0x00001100;
//资金变动回报
const FTD_TID_RtnRuleAccount = 0x00001101;
//Api建立连接回报
const FTD_TID_RtnApiConnected = 0x0000110B;
//Api断开连接回报
const FTD_TID_RtnApiDisconnected = 0x0000110C;
//客户端显示消息回报
const FTD_TID_RtnUserMessage = 0x0000110F;
//用户分账号信息回报
const FTD_TID_RtnCustomerAccountInfo = 0x00001114;
//交易成员基本信息回报
const FTD_TID_RtnCfetsTradeMember = 0x00001135;
//心跳通知
const FTD_TID_NotifyHeartBeat = 0x00004500;
//请求更新合约做市状态
const FTD_TID_ReqUpdMakeMarketStatus = 0x00001115;
//请求更新合约做市状态响应
const FTD_TID_RspUpdMakeMarketStatus = 0x00001116;
//合约做市状态回报
const FTD_TID_RtnMakeMarketStatus = 0x00001117;
//请求更新合约做市参数
const FTD_TID_ReqUpdMakeMarketPara = 0x00001118;
//请求更新合约做市参数响应
const FTD_TID_RspUpdMakeMarketPara = 0x00001119;
//合约做市参数回报
const FTD_TID_RtnMakeMarketPara = 0x0000111A;
//请求查询策略限仓额度
const FTD_TID_ReqQryRulePositionLimit = 0x00001120;
//请求查询策略限仓额度响应
const FTD_TID_RspQryRulePositionLimit = 0x00001121;
//请求更改策略限仓额度
const FTD_TID_ReqRulePositionLimitAction = 0x00001122;
//请求更改策略限仓额度响应
const FTD_TID_RspRulePositionLimitAction = 0x00001123;
//请求订阅价差
const FTD_TID_ReqSubPriceSpread = 0x0000112a;
//请求退订价差
const FTD_TID_ReqUnsubPriceSpread = 0x0000112c;
//合约价差回报
const FTD_TID_RtnInsPriceSpread = 0x0000112b;
(function ($) {
$.extend({
websocket: function (url, s, protocols) {
var ws, _s = s;
var settings = {
message: function () {
console.log("message function is undefined");
},
options: {}, events: {}, ver: "1.0", company: "上海尔易信息科技有限公司"
};
_reconnect = function (url, protocols) {
if (protocols) {
ws = window['MozWebSocket'] ? new MozWebSocket(url, protocols) : window['WebSocket'] ? new WebSocket(url, protocols) : null;
} else {
ws = window['MozWebSocket'] ? new MozWebSocket(url) : window['WebSocket'] ? new WebSocket(url) : null;
}
ws._url = url;
ws._protocols = protocols;
$.extend(settings, $.websocketSettings, _s);
ws.nRequestID = 1;
if (ws) {
$(ws)
.bind('open', settings.OnFrontConnected)
.bind('close', settings.OnFrontDisconnected)
.bind('message', settings.message)
.bind('message', function (e) {
var m = JSON.stringify(e.originalEvent.data);
//eval("m=" + e.originalEvent.data);
var h = settings.events[m.Tid];//调用指定的方法
if (h) h.call(this, m);
});
ws._send = ws.send;
ws.send = function (type, data) {
var m = {Tid: type};//类型
m = $.extend(true, m, $.extend(true, {}, settings.options, m));
if (data) {
delete data["_id"];
m['data'] = data;
}
m['RequestID'] = ws.nRequestID++;
return this._send(JSON.stringify(m));
};
ws.reconnect = function () {
setTimeout(function () {
_reconnect(ws._url, ws._protocols);
}, 1000 * 30);
//_reconnect(ws._url, ws._protocols);
};
}
};
_reconnect(url, protocols);
// $(window).unload(function () {
// ws.close();
// ws = null;
// });
$(window).on("unload",function () {
ws.close();
ws = null;
});
return ws;
}
, trade: function (url, s, protocols) {
var tradeWS = {nRequestID: 1};
var settings = {
OnFrontConnected: function () {
console.log("OnFrontConnected");
},
OnFrontDisconnected: function () {
console.log("OnFrontDisconnected");
},
OnHeartBeatWarning: function (nTimeLapse) {
console.log("OnHeartBeatWarning: " + nTimeLapse);
},
OnRspError: function (r) {
},
OnRspUserLogin: function (r) {
},
OnRspUserLogout: function (r) {
},
OnRtnDepthMarketData: function (r) {
},
OnRspSubMarketData: function (r) {
},
OnRspUnSubMarketData: function (r) {
},
OnRtnRule: function (r) {
},
OnRtnRuleStatus: function (r) {
},
OnRtnRuleProp: function (r) {
},
OnRtnRuleIndicator: function (r) {
},
message: function (msg) {
try {
if (event.data instanceof Blob) {
var reader = new FileReader();
reader.onloadend = function () {
console.log(reader.result);
};
reader.readAsText(event.data, "gbk");//utf-8
} else {
eval("r=" + event.data);
switch (r.Tid) {
case FTD_TID_RtnDepthMarketData: {
tradeWS.OnRtnDepthMarketData(r);
break;
}
case FTD_TID_RspUserLogin: {
tradeWS.OnRspUserLogin(r);
break;
}
// case FTD_TID_RspError: {
// tradeWS.OnRspError(r);
// break;
// }
case FTD_TID_RtnRule: {//策略回报
tradeWS.OnRtnRule(r);
break;
}
case FTD_TID_RtnRuleStatus: {//策略运行状态变化回报
tradeWS.OnRtnRuleStatus(r);
break;
}
case FTD_TID_RtnRuleProp: {//策略属性变化回报
tradeWS.OnRtnRuleProp(r);
break;
}
case FTD_TID_RtnRuleIndicator: {//策略指标回报
tradeWS.OnRtnRuleIndicator(r);
break;
}
case FTD_TID_RspUserLogout: {
tradeWS.OnRspUserLogout(r);
break;
}
case FTD_TID_RspSubMarketData: {
tradeWS.OnRspSubMarketData(r);
break;
}
case FTD_TID_RspUnSubMarketData: {
tradeWS.OnRspUnSubMarketData(r);
break;
}
case FTD_TID_IntlRtnDepthMarketData: {
tradeWS.OnRspError(r);
break;
}
default:
OnHeartBeatWarning(r);
}
console.log("log: " + event.data);
}
} catch (e) {
console.log(e.message + " : " + event.data);
}
},
options: {},
events: {}
};
$.extend(settings, $.websocketSettings, s, tradeWS);
tradeWS.ws = $.websocket(url, settings, protocols);
tradeWS.OnFrontConnected = settings.OnFrontConnected;
tradeWS.OnFrontDisconnected = settings.OnFrontDisconnected;
tradeWS.OnHeartBeatWarning = settings.OnHeartBeatWarning;
tradeWS.OnRspError = settings.OnRspError;
tradeWS.OnRspUserLogin = settings.OnRspUserLogin;
tradeWS.OnRspUserLogout = settings.OnRspUserLogout;
tradeWS.OnRtnDepthMarketData = settings.OnRtnDepthMarketData;
tradeWS.OnRspSubMarketData = settings.OnRspSubMarketData;
tradeWS.OnRspUnSubMarketData = settings.OnRspUnSubMarketData;
tradeWS.OnRtnRule = settings.OnRtnRule;
tradeWS.OnRtnRuleStatus = settings.OnRtnRuleStatus;
tradeWS.OnRtnRuleProp = settings.OnRtnRuleProp;
tradeWS.OnRtnRuleIndicator = settings.OnRtnRuleIndicator;
//启动或停止策略发送到后台
tradeWS.ReqOptionStg = function(optype, ruleid){
this.ws.send(FTD_TID_ReqOptionStg, {
"optype": optype,
"stgid": ruleid
})
};
//策略参数发送到websocket
tradeWS.ReqStgPara = function(ruleid, key, value, desc){
this.ws.send(FTD_TID_ReqStgPara, {
"stgid": ruleid,
"stgname": key,
"stgvalue": value,
"stgdesc": encodeURI(desc) //中文乱码,用url编码,python在后台url解码
})
}
tradeWS.ReqUserLogin = function (u, p) {
this.ws.send(FTD_TID_ReqUserLogin, {
'CustomerID': u,
'Password': p,
'ProductInfo': '',
'InterfaceProductInfo': 'websocket'
});
};
tradeWS.SubscribeMarketData = function (ppInstrumentIDs) {
if (ppInstrumentIDs.length > 0)
this.ws.send(FTD_TID_ReqSubMarketData, {'InstrumentID': ppInstrumentIDs, "ExchangeID": "CFFEX"});
else
alert("请选择要订阅行情的合约!");
};
tradeWS.UnSubscribeMarketData = function (ppInstrumentIDs) {
if (ppInstrumentIDs.length > 0)
this.ws.send(FTD_TID_ReqUnSubMarketData, {'InstrumentIDs': ppInstrumentIDs});
else
alert("请选择要取消订阅行情的合约!");
};
return tradeWS;
}
});
})(jQuery);
## 后端代码(STCHftData.py)
# encoding: UTF-8
from time import sleep
from hft_api import HftApi
import os
import json
import threading
import copy
import multiprocessing
from websocket_server import WebsocketServer
global signal
from enum import Enum
from process_manager import process_manager
import pandas as pd
from sqlalchemy import create_engine
import urllib.parse
class run_status_enum(Enum):
运行中 = 1
已结束 = 2 # 回测模拟
已分析 = 3 # 回测模拟
未运行 = 4
待运行 = 5
待停止 = 6
错误 = 7 # 回测模拟
# signal = threading.Event()
def print_dict(d):
"""按照键值打印一个字典"""
for key, value in d.items():
print(key + ':' + str(value))
def simple_log(func):
"""简单装饰器用于输出函数名"""
def wrapper(*args, **kw):
print(str(func.__name__))
return func(*args, **kw)
return wrapper
class TestMdApi(HftApi):
"""测试用实例"""
def __init__(self, Queue1):
"""Constructor"""
super(TestMdApi, self).__init__()
# 1. 创建1个队列
self.tid_Queue = Queue1
@simple_log
def onFrontConnected(self):
"""服务器连接"""
signal = 1
pass
@simple_log
def onFrontDisconnected(self, n):
"""服务器断开"""
print(n)
signal = 0
@simple_log
def onHeartBeatWarning(self, n):
"""心跳报警"""
print(n)
@simple_log
def onRspError(self, error, n, last):
"""错误"""
print_dict(error)
@simple_log
def onRspUserLogin(self, data, error, data2, n, last):
"""登陆回报"""
print_dict(data)
print_dict(error)
@simple_log
def onRspUserLogout(self, data, error, n, last):
"""登出回报"""
print_dict(data)
print_dict(error)
def onRspUnSubMarketData(self, data, error, n, last):
"""退订合约回报"""
print_dict(data)
print_dict(error)
def onRtnDepthMarketData(self, data):
"""行情推送"""
print_dict(data)
# print('子进程1进程>>', os.getpid())
package = {'Tid': 0x00002008, 'data': data}
self.save_as_json(package, "onRtnDepthMarketData")
# ----------------------------------------------------------------------
def onRtnForQuoteRsp(self, data):
"""行情推送"""
print_dict(data)
def onRspUpdateRuleStatus(self, data, error, n, last):
"""更改C++ 策略更新状态响应"""
# print_dict(error)
package = {'Tid': 0x00001031, 'data': data}
self.save_as_json(package)
def onRspUpdateRuleProp(self, data, error, n, last):
# print_dict(error)
"""更改C++ 策略更新属性响应"""
package = {'Tid': 0x00001041, 'data': data}
self.save_as_json(package, "onRspUpdateRuleProp")
def onRtnRuleIndicator(self, data):
"""策略指标回报"""
package = {'Tid': 0x00001045, 'data': data}
self.save_as_json(package, "onRtnRuleIndicator")
# save ruleid+key
def onRtnRuleProp(self, data):
"""策略属性变化回报"""
package = {'Tid': 0x00001044, 'data': data}
self.save_as_json(package, "onRtnRuleProp")
# save
def onRtnRuleStatus(self, data):
"""策略状态变化回报"""
package = {'Tid': 0x00001032, 'data': data}
self.save_as_json(package)
@simple_log
def onRtnRule(self, data):
"""策略回报"""
package = {'Tid': 0x00001012, 'data': data}
self.save_as_json(package, "onRtnRule")
"""
主动API接口封装
"""
# 在C++环境中创建MdApi对象,传入参数是希望用来保存.con文件的地址
# @param pszFlowPath 存贮订阅信息文件的目录,默认为当前目录
# @return 创建出的UserApi
# modify for udp marketdata
def CreateFtdcMdApi(self, path):
if os.path.exists(path) and path[-1] == '\\':
type = ''
self.createUserApi(path, type)
else:
print('con file path error')
# 删除接口对象本身
# @remark 不再使用本接口对象时,调用该函数删除接口对象
def Release(self):
self.release()
# 初始化
# @remark 初始化运行环境,只有调用后,接口才开始工作
def Init(self):
self.init()
# 等待接口线程结束运行
# @return 线程退出代码
def Join(self):
self.join()
# 退出
def Exit(self):
self.exit()
# 获取当前交易日
# @retrun 获取到的交易日
# @remark 只有登录成功后,才能得到正确的交易日
def GetTradingDay(self):
trade_date = self.getTradingDay()
return trade_date
# 注册前置机网络地址
# @param pszFrontAddress:前置机网络地址。
# @remark 网络地址的格式为:“protocol://ipaddress:port”,如:”tcp://127.0.0.1:17001”。
# @remark “tcp”代表传输协议,“127.0.0.1”代表服务器地址。”17001”代表服务器端口号。
def RegisterFront(self, ip_address):
self.registerFront(ip_address)
# 用户登录请求
def ReqUserLogin(self):
# signal.wait()
# 登陆
print("ready to login")
loginReq = {} # 创建一个空字典
loginReq['CustomerID'] = '1001' # 参数作为字典键值的方式传入
loginReq['Password'] = '123456' # 键名和C++中的结构体成员名对应
loginReq['MacAddress'] = '00-50-56-C0-00-01'
loginReq['SubInstrumentMethod'] = '0'
loginReq['IsRule'] = 0
self.reqUserLogin(loginReq, 1)
# 登出请求
def ReqUserLogout(self):
self.reqUserLogout()
# 订阅行情。
# @param ppInstrumentID 合约ID
# @param nCount 要订阅/退订行情的合约个数
def SubscribeMarketData(self, msg):
subReq = {}
subReq['InstrumentID'] = msg['data']['InstrumentID']
subReq['ExchangeID'] = msg['data']['ExchangeID']
self.reqSubMarketData(subReq, 2)
# self.subscribeMarketData(ppInstrumentID)
# 退订行情。
# @param ppInstrumentID 合约ID
# @param nCount 要订阅/退订行情的合约个数
def UnSubscribeMarketData(self, ppInstrumentID):
self.unSubscribeMarketData(ppInstrumentID)
# 请求修改策略状态
def ReqUpdateRuleStatus(self, req):
self.reqUpdateRuleStatus(req, 3)
# 请求修改策略状态
def ReqUpdateRuleProp(self, req):
self.reqUpdateRuleProp(req, 4)
# 业务流程处理
def subs(self):
self.CreateFtdcMdApi('mdcon\\')
self.RegisterFront("tcp://127.0.0.1:32201")
self.Init()
sleep(1)
self.ReqUserLogin()
sleep(1)
# 请求修改策略状态_启动策略
ruleReq = {} # 创建一个空字典
ruleReq['RuleID'] = 1 # 策略代码
ruleReq['RuleOperType'] = '0' # 策略运行状态类型设定
self.ReqUpdateRuleStatus(ruleReq)
# 请求更新策略属性
# for i in range(0,1000):
ruleReq2 = {} # 创建一个空字典
ruleReq2['RuleID'] = 1 # 策略代码
ruleReq2['PropKey'] = 'OrderPrice' # 策略属性键
ruleReq2['PropValue'] = '99.5' # 策略属性值
ruleReq2['PropType'] = '1' # 策略属性值类型
ruleReq2['PropValueItems'] = '1' # 策略属性值选项
ruleReq2['Description'] = '报单价格'.encode('gbk') # 描述
self.ReqUpdateRuleProp(ruleReq2)
# # 请求修改策略状态_关闭策略
# ruleReq['RuleID'] = 1 # 策略代码
# ruleReq['RuleOperType'] = '1' # 策略运行状态类型设定
# self.ReqUpdateRuleStatus(ruleReq)
# 数据转换
def save_as_json(self, package, func=""):
json_package = json.dumps(package)
# self.tid_Queue.put(json_package)
self.tid_Queue.put(package)
# if self.tid_Queue.qsize() % 10 == 0:
# print('打包 现在队列里面有包数:', self.tid_Queue.qsize())
#####################################
class switch(object):
def __init__(self, value):
self.value = value
self.fall = False
def __iter__(self):
"""Return the match method once, then stop"""
yield self.match
raise StopIteration
def match(self, *args):
"""Indicate whether or not to enter a case suite"""
if self.fall or not args:
return True
elif self.value in args: # changed for v1.5, see below
self.fall = True
return True
else:
return False
# 自定义常量类,类名要大写,不可修改。python中只规定常量大写,建议不可修改,若想不可修改,自定义类。
# 策略回报
FTD_Tid_RtnRule = 0x00001012;
# 策略运行状态变化回报
FTD_Tid_RtnRuleStatus = 0x00001032;
# 策略属性变化回报
FTD_Tid_RtnRuleProp = 0x00001044;
# 策略指标回报
FTD_Tid_RtnRuleIndicator = 0x00001045;
global client_list
client_list = []
global total_dict
class SocketEnv():
def __init__(self, queue1):
"""Constructor"""
super(SocketEnv, self).__init__()
self.total_dict = {} # 全量数据
# tid_Queue 增量数据队列
self.tid_Queue = queue1
r = 'r是什么,r是传递的data,case是r.Tid'
v = 'ten' # 若按照此写法,v=r.Tid
def caseswitch(self, req_msg):
for case in switch(req_msg["Tid"]):
print('前端请求HFT数据:', type(req_msg), req_msg)
# 带on的是回报,不带on的是请求,关注请求即可
if case(FTD_Tid_RtnRule):
# self.api.onRtnDepthMarketData(req_msg)
break
if case(FTD_Tid_RtnRuleStatus):
# self.api.onRspUserLogin(req_msg)
break
if case(FTD_Tid_RtnRuleProp):
# self.api.OnRspError(req_msg)
break
if case(FTD_Tid_RtnRuleIndicator):
# self.api.OnRspUserLogout(req_msg)
break
if case(): # default, could also just omit condition or 'if True'
print('switch case default!')
def startWebsocketServer(self):
def login(msg):
# switch case
if msg["Tid"] == 8201:
api.SubscribeMarketData(msg)
print("行情定阅成功")
else:
data = msg["data"]
try:
if data['CustomerID'] == '1001' and data['Password'] == '123456':
return True
else:
return False
except Exception as e:
print(e)
return False
def onmessage(client, server, msg):
jsonObject = json.loads(msg)
print("前端传来了:", jsonObject)
if jsonObject["data"].get("stgdesc"):
stgdesc = jsonObject['data']
data = urllib.parse.quote(stgdesc)
print(data)
if jsonObject["data"].get("optype"):
try:
optype = jsonObject['data']["optype"]
stgid = jsonObject['data']['stgid']
if optype == "end":
self.init_run(6, stgid)
print("待停止发送成功!!")
elif optype == "start":
self.init_run(5, stgid)
print("待启动发送成功!!")
except Exception as e:
print(e)
islogin = login(jsonObject)
if client in client_list:
pass
# 请求switchcase方法
else:
if islogin:
client_list.append(client)
# 登录成功,推全量数据给用户
print('登录成功,推全量数据给用户')
print(self.total_dict)
for key in self.total_dict:
data = self.total_dict[key]
server.send_message_to_all(json.dumps(data))
return True
else:
pass
# 登录失败
# // server.send_message(client, json.dumps({"Tid": 1111, "data": {"msg": "登陆失败"}}))
def sendDataToAllClient():
while True:
jsonData = self.tid_Queue.get()
if jsonData == None:
sleep(0.1)
if jsonData["Tid"] == FTD_Tid_RtnRule: # 策略
self.total_dict[jsonData['data']['RuleID']] = jsonData
elif jsonData['Tid'] == FTD_Tid_RtnRuleStatus: # 状态4146
self.total_dict[str(jsonData['data']['RuleID']) + '_' + 'status'] = jsonData
elif jsonData["Tid"] == FTD_Tid_RtnRuleProp: # 参数4164
self.total_dict[str(jsonData['data']['RuleID']) + '_' + jsonData['data']['PropValue']] = jsonData
elif jsonData["Tid"] == FTD_Tid_RtnRuleIndicator: # 指标4165
self.total_dict[str(jsonData['data']['RuleID']) + '_' + jsonData['data']['IndicatorKey']] = jsonData
client_list_copy = copy.copy(client_list)
for client in client_list_copy:
sleep(0.1)
server.send_message(client, json.dumps(jsonData))
# 这是服务端,所以需要监听的是本机的ip,通过ipconfig拿到,
# 同时在linux上的客户端也需要监听这个ip和端口。
# server = WebsocketServer(9000, host='172.16.78.228')
# server = WebsocketServer(9000, host='172.19.190.191')
server = WebsocketServer(9000, host='127.0.0.1')
server.set_fn_message_received(onmessage)
t1 = threading.Thread(target=sendDataToAllClient)
t1.start()
server.run_forever()
server.server_close()
def init_run(self, run_status, stg_id):
'''
1运行中 2已结束 3已分析 4未运行 5待运行 6待停止 7错误
'''
# 数据库
user = 'root'
password = 'XCL29hao'
ip_address = '111.231.16.33'
port = 3306
db_name = 'strategy'
# 表名
self.db_approval = 't_paperapproval'
self.db_run = 't_paperrun'
# 配置文件
self.ini_host = '127.0.0.1'
self.ini_port = 50000
self.ini_api_mode = 'HFT_Address_IP'
self.ini_api_host = '127.0.0.1'
self.ini_api_port = '32201'
# python exe文件, 保存路径
self.python_path = 'D:\\program\\Anaconda3'
self.target_path = 'D:'
# 遍历周期
self.sleep_period = 10
# 数据库连接
self.engine_str = f'mysql+mysqlconnector://{user}:{password}@{ip_address}:{port}/{db_name}?charset=utf8'
self.engine = create_engine(self.engine_str)
try:
# 进程管理
process_main = process_manager()
print('运行一次进程启动...')
# 进程管理器
process_main.get_current_process()
# 读取需要启停的进程
content = f'select * from {self.db_run} where StgID = {stg_id}'
sql_df = pd.read_sql_query(content, self.engine)
if len(sql_df) == 0:
return 0
# 类型转换
run_status = int(run_status)
stg_id = str(stg_id)
path = sql_df['RunPath'].tolist()[0]
# 启动进程
if run_status == run_status_enum.待运行.value:
# 运行进程
flag = process_main.start_process(path)
if flag:
# 更新run表
try:
content = f'update {self.db_run} set RunStatus = \
{run_status_enum.运行中.value}, StartTime = \
"{datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")}" \
where StgID = {stg_id}'
pd.read_sql_query(content, self.engine)
except Exception as e:
pass
# 终止进程
elif run_status == run_status_enum.待停止.value:
# 终止进程
flag = process_main.end_process(stg_id)
if flag:
# 更新run表
try:
content = f'update {self.db_run} set RunStatus = \
{run_status_enum.未运行.value}, EndTime = \
"{datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")}" \
where StgID = {stg_id}'
pd.read_sql_query(content, self.engine)
except Exception as e:
pass
except Exception as e:
print(e)
"""
消耗多进程队列的func
"""
def main():
"""
@ERYI.tcy
"""
# 初始化
# print('父进程>>', os.getpid())
# 创建一个实时更新队列-存储实时数据
tid_Queue = multiprocessing.Queue()
p1 = multiprocessing.Process(target=TestMdApi, args=(tid_Queue,))
global api
api = TestMdApi(tid_Queue)
api.subs()
p1.start()
# p1.daemon = True
p2 = multiprocessing.Process(target=SocketEnv, args=(tid_Queue,))
p2.start()
socket_env = SocketEnv(tid_Queue)
# # api的业务操作测试 从hft取数据比如请求订阅行情、收策略回报等
socket_env.startWebsocketServer()
i = 1
while True:
if i == 0:
break
api.Exit()
print("父进程结束")
if __name__ == '__main__':
main()
遇到一些问题
'''
websocket服务端
如果部署在服务器上,则监听的是内网ip(阿里云:命令是ifconfig, windowserver:命令是ipconfig), 而客户端连接的是公网ip(服务器外网访问的ip)
如果部署在本地,则监听的是本机的ip, 客户端连接的是本机的ip,通过ipconfig查看本机(ipv4地址),不然的话有可能会抱胸:在其上下文,该请求地址无效,主要就是ip+端口错误导致的。
'''