diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000000000000000000000000000000000000..b586b32965bbc45f0b4e0e243311d9f8e2ba8377 --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,12 @@ +{ + "name": "76", + "host": "192.168.202.76", + "protocol": "sftp", + "port": 22, + "username": "amax", + "password": "pc@asdf", + "remotePath": "/home/amax/testdapruntime/octopus/ctp_access", + "uploadOnSave": true, + "useTempFile": false, + "openSsh": false +} \ No newline at end of file diff --git a/CTP_access.postman_collection.json b/CTP_access.postman_collection.json new file mode 100644 index 0000000000000000000000000000000000000000..8b269d81b78d6b6f88f0e498169824ce1efc3307 --- /dev/null +++ b/CTP_access.postman_collection.json @@ -0,0 +1,1069 @@ +{ + "info": { + "_postman_id": "036d6453-6765-4fbe-942a-e6d12abbe92a", + "name": "CTP_access", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "/subscribe", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"instrument_ids\": [\"au2112\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/subscribe", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "subscribe" + ] + } + }, + "response": [] + }, + { + "name": "/subscribe", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/subscribe", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "subscribe" + ] + } + }, + "response": [] + }, + { + "name": "/unsubscribe", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"instrument_ids\": [\"au2112\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/unsubscribe", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "unsubscribe" + ] + } + }, + "response": [] + }, + { + "name": "/get_trading_day", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/get_trading_day", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "get_trading_day" + ] + } + }, + "response": [] + }, + { + "name": "/query_settlement_info", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/query_settlement_info", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "query_settlement_info" + ] + } + }, + "response": [] + }, + { + "name": "/query_trading_account", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/query_trading_account", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "query_trading_account" + ] + } + }, + "response": [] + }, + { + "name": "/query_investor_position", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/query_investor_position", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "query_investor_position" + ] + } + }, + "response": [] + }, + { + "name": "/query_commission_rate", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/query_commission_rate?instrument_id=au2112", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "query_commission_rate" + ], + "query": [ + { + "key": "instrument_id", + "value": "au2112" + } + ] + } + }, + "response": [] + }, + { + "name": "/query_instrument", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/query_instrument?instrument_id=MA201", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "query_instrument" + ], + "query": [ + { + "key": "instrument_id", + "value": "MA201" + } + ] + } + }, + "response": [] + }, + { + "name": "市价单:/send_market_order上期所(实盘环境不支持,Simnow环境不支持)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"ag2112\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_market_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_market_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "市价单:/send_market_order大商所(实盘环境不支持,Simnow环境不支持)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"i2201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_market_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_market_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "市价单:/send_market_order中金所(实盘支持近2个月合约,远月不支持,SimNow不支持)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"T2203\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_market_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_market_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "市价单:/send_market_order郑商所(实盘支持,SimNow不支持)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"MA201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_market_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_market_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "限价单:/send_limit_order上期所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"sc2201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"limit_price\": 510\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_limit_order?instrument_id=sc2112&direction=买&open_close=开仓&volume=1&price=515.9", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_limit_order" + ], + "query": [ + { + "key": "instrument_id", + "value": "sc2112" + }, + { + "key": "direction", + "value": "买" + }, + { + "key": "open_close", + "value": "开仓" + }, + { + "key": "volume", + "value": "1" + }, + { + "key": "price", + "value": "515.9" + } + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "限价单:/send_limit_order大商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"i2201\",\n \"direction\": \"卖\",\n \"open_close\": \"平仓\",\n \"volume\": 2,\n \"limit_price\": 520\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_limit_order?instrument_id=i2201&direction=买&open_close=开仓&volume=1&price=520", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_limit_order" + ], + "query": [ + { + "key": "instrument_id", + "value": "i2201" + }, + { + "key": "direction", + "value": "买" + }, + { + "key": "open_close", + "value": "开仓" + }, + { + "key": "volume", + "value": "1" + }, + { + "key": "price", + "value": "520" + } + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "限价单:/send_limit_order中金所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"T2203\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"limit_price\": 100\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "限价单:/send_limit_order郑商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"MA201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"limit_price\": 2500\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_limit_order?instrument_id=MA201&direction=买&open_close=开仓&volume=1&price=2665", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_limit_order" + ], + "query": [ + { + "key": "instrument_id", + "value": "MA201" + }, + { + "key": "direction", + "value": "买" + }, + { + "key": "open_close", + "value": "开仓" + }, + { + "key": "volume", + "value": "1" + }, + { + "key": "price", + "value": "2665" + } + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n========================" + }, + "response": [] + }, + { + "name": "条件单:/send_stop_limit_order大商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"i2201\",\n \"direction\": \"卖\",\n \"open_close\": \"平仓\",\n \"volume\": 1,\n \"limit_price\": 534,\n \"stop_price_condition\": \"卖一价大于等于条件价\",\n \"stop_price\": 533\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_fill_or_kill_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_fill_or_kill_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n=========================\n\n\"stop_price_condition\":\"立即\",\n\n\"stop_price_condition\":\"止损\",\n\n\"stop_price_condition\":\"止赢\",\n\n\"stop_price_condition\":\"预埋单\",\n\n\"stop_price_condition\":\"最新价大于条件价\",\n\n\"stop_price_condition\":\"最新价大于等于条件价\",\n\n\"stop_price_condition\":\"最新价小于条件价\",\n\n\"stop_price_condition\":\"最新价小于等于条件价\",\n\n\"stop_price_condition\":\"卖一价大于条件价\",\n\n\"stop_price_condition\":\"卖一价大于等于条件价\",\n\n\"stop_price_condition\":\"卖一价小于条件价\",\n\n\"stop_price_condition\":\"卖一价小于等于条件价\",\n\n\"stop_price_condition\":\"买一价大于条件价\",\n\n\"stop_price_condition\":\"买一价大于等于条件价\",\n\n\"stop_price_condition\":\"买一价小于条件价\",\n\n\"stop_price_condition\":\"买一价小于等于条件价\",\n\n============================\n\n\n\n(stop_price + stop_price_condition 触发生效一个限价单limit_price)\n\n(止损、止赢需要交易所支持,SimNow环境所有交易所都不能支持止损止盈条件单)\n\n(止损、止赢需要交易所支持,券商实盘仿真环境大商所支持止损止盈条件单?)\n\n(条件单有效触发条件,所有交易所支持)\n\n最新价大于条件价\n\n最新价大于等于条件价\n\n最新价小于条件价\n\n最新价小于等于条件价\n\n卖一价大于条件价\n\n卖一价大于等于条件价\n\n卖一价小于条件价\n\n卖一价小于等于条件价\n\n买一价大于条件价\n\n买一价大于等于条件价\n\n买一价小于条件价\n\n买一价小于等于条件价\n\n=========================" + }, + "response": [] + }, + { + "name": "条件单:/send_stop_limit_order郑商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"MA201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"limit_price\": 2525,\n \"stop_price_condition\": \"最新价大于条件价\",\n \"stop_price\": 2510\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_stop_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_stop_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n\n=============================\n\n\"stop_price_condition\":\"立即\",\n\n\"stop_price_condition\":\"止损\",\n\n\"stop_price_condition\":\"止赢\",\n\n\"stop_price_condition\":\"预埋单\",\n\n\"stop_price_condition\":\"最新价大于条件价\",\n\n\"stop_price_condition\":\"最新价大于等于条件价\",\n\n\"stop_price_condition\":\"最新价小于条件价\",\n\n\"stop_price_condition\":\"最新价小于等于条件价\",\n\n\"stop_price_condition\":\"卖一价大于条件价\",\n\n\"stop_price_condition\":\"卖一价大于等于条件价\",\n\n\"stop_price_condition\":\"卖一价小于条件价\",\n\n\"stop_price_condition\":\"卖一价小于等于条件价\",\n\n\"stop_price_condition\":\"买一价大于条件价\",\n\n\"stop_price_condition\":\"买一价大于等于条件价\",\n\n\"stop_price_condition\":\"买一价小于条件价\",\n\n\"stop_price_condition\":\"买一价小于等于条件价\",\n\n============================\n\n(stop_price + stop_price_condition 触发生效一个限价单limit_price)\n\n(止损、止赢需要交易所支持,SimNow环境所有交易所都不能支持止损止盈条件单)\n\n(止损、止赢需要交易所支持,券商实盘仿真环境大商所支持止损止盈条件单?)\n\n(条件单有效触发条件,所有交易所支持)\n\n最新价大于条件价\n\n最新价大于等于条件价\n\n最新价小于条件价\n\n最新价小于等于条件价\n\n卖一价大于条件价\n\n卖一价大于等于条件价\n\n卖一价小于条件价\n\n卖一价小于等于条件价\n\n买一价大于条件价\n\n买一价大于等于条件价\n\n买一价小于条件价\n\n买一价小于等于条件价\n\n=========================" + }, + "response": [] + }, + { + "name": "条件单:/send_stop_limit_order上期所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"rb2201\",\n \"direction\": \"卖\",\n \"open_close\": \"平仓\",\n \"volume\": 3,\n \"limit_price\": 4370,\n \"stop_price_condition\": \"最新价大于条件价\",\n \"stop_price\": 4370\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_stop_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_stop_limit_order" + ] + }, + "description": "\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n=========================\n\n\"stop_price_condition\":\"立即\",\n\n\"stop_price_condition\":\"止损\",\n\n\"stop_price_condition\":\"止赢\",\n\n\"stop_price_condition\":\"预埋单\",\n\n\"stop_price_condition\":\"最新价大于条件价\",\n\n\"stop_price_condition\":\"最新价大于等于条件价\",\n\n\"stop_price_condition\":\"最新价小于条件价\",\n\n\"stop_price_condition\":\"最新价小于等于条件价\",\n\n\"stop_price_condition\":\"卖一价大于条件价\",\n\n\"stop_price_condition\":\"卖一价大于等于条件价\",\n\n\"stop_price_condition\":\"卖一价小于条件价\",\n\n\"stop_price_condition\":\"卖一价小于等于条件价\",\n\n\"stop_price_condition\":\"买一价大于条件价\",\n\n\"stop_price_condition\":\"买一价大于等于条件价\",\n\n\"stop_price_condition\":\"买一价小于条件价\",\n\n\"stop_price_condition\":\"买一价小于等于条件价\",\n\n============================\n\n\n(stop_price + stop_price_condition 触发生效一个限价单limit_price)\n\n(止损、止赢需要交易所支持,SimNow环境所有交易所都不能支持止损止盈条件单)\n\n(止损、止赢需要交易所支持,券商实盘仿真环境大商所支持止损止盈条件单?)\n\n(条件单有效触发条件,所有交易所支持)\n\n最新价大于条件价\n\n最新价大于等于条件价\n\n最新价小于条件价\n\n最新价小于等于条件价\n\n卖一价大于条件价\n\n卖一价大于等于条件价\n\n卖一价小于条件价\n\n卖一价小于等于条件价\n\n买一价大于条件价\n\n买一价大于等于条件价\n\n买一价小于条件价\n\n买一价小于等于条件价\n\n=========================" + }, + "response": [] + }, + { + "name": "止盈单:/send_stop_limit_order大商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"i2201\",\n \"direction\": \"卖\",\n \"open_close\": \"平仓\",\n \"volume\": 1,\n \"limit_price\": 573,\n \"stop_price_condition\": \"止盈\",\n \"stop_price\": 573\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_stop_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_stop_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n=========================\n\n\"stop_price_condition\":\"立即\",\n\n\"stop_price_condition\":\"止损\",\n\n\"stop_price_condition\":\"止赢\",\n\n\"stop_price_condition\":\"预埋单\",\n\n\"stop_price_condition\":\"最新价大于条件价\",\n\n\"stop_price_condition\":\"最新价大于等于条件价\",\n\n\"stop_price_condition\":\"最新价小于条件价\",\n\n\"stop_price_condition\":\"最新价小于等于条件价\",\n\n\"stop_price_condition\":\"卖一价大于条件价\",\n\n\"stop_price_condition\":\"卖一价大于等于条件价\",\n\n\"stop_price_condition\":\"卖一价小于条件价\",\n\n\"stop_price_condition\":\"卖一价小于等于条件价\",\n\n\"stop_price_condition\":\"买一价大于条件价\",\n\n\"stop_price_condition\":\"买一价大于等于条件价\",\n\n\"stop_price_condition\":\"买一价小于条件价\",\n\n\"stop_price_condition\":\"买一价小于等于条件价\",\n\n============================\n\n\n\n(stop_price + stop_price_condition 触发生效一个限价单limit_price)\n\n(止损、止赢需要交易所支持,SimNow环境所有交易所都不能支持止损止盈条件单)\n\n(止损、止赢需要交易所支持,券商实盘仿真环境大商所支持止损止盈条件单?)\n\n(条件单有效触发条件,所有交易所支持)\n\n最新价大于条件价\n\n最新价大于等于条件价\n\n最新价小于条件价\n\n最新价小于等于条件价\n\n卖一价大于条件价\n\n卖一价大于等于条件价\n\n卖一价小于条件价\n\n卖一价小于等于条件价\n\n买一价大于条件价\n\n买一价大于等于条件价\n\n买一价小于条件价\n\n买一价小于等于条件价\n\n=========================" + }, + "response": [] + }, + { + "name": "触价单:/send_market_if_touched_order大商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"i2201\",\n \"direction\": \"卖\",\n \"open_close\": \"平仓\",\n \"volume\": 1,\n \"stop_price_condition\": \"止盈\",\n \"stop_price\": 570\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_market_if_touched_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_market_if_touched_order" + ] + }, + "description": "# API的Body参数枚举值\n\n==========================================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n==========================================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n==========================================\n\n\"stop_price_condition\":\"立即\",\n\n\"stop_price_condition\":\"止损\",\n\n\"stop_price_condition\":\"止赢\",\n\n\"stop_price_condition\":\"预埋单\",\n\n\"stop_price_condition\":\"最新价大于条件价\",\n\n\"stop_price_condition\":\"最新价大于等于条件价\",\n\n\"stop_price_condition\":\"最新价小于条件价\",\n\n\"stop_price_condition\":\"最新价小于等于条件价\",\n\n\"stop_price_condition\":\"卖一价大于条件价\",\n\n\"stop_price_condition\":\"卖一价大于等于条件价\",\n\n\"stop_price_condition\":\"卖一价小于条件价\",\n\n\"stop_price_condition\":\"卖一价小于等于条件价\",\n\n\"stop_price_condition\":\"买一价大于条件价\",\n\n\"stop_price_condition\":\"买一价大于等于条件价\",\n\n\"stop_price_condition\":\"买一价小于条件价\",\n\n\"stop_price_condition\":\"买一价小于等于条件价\",\n\n==========================================\n\n\n(stop_price + stop_price_condition 触发生效一个限价单limit_price)\n\n(止损、止赢需要交易所支持,SimNow环境所有交易所都不能支持止损止盈条件单)\n\n(止损、止赢需要交易所支持,券商实盘仿真环境大商所支持止损止盈条件单?)\n\n(条件单有效触发条件,所有交易所支持)\n\n最新价大于条件价\n\n最新价大于等于条件价\n\n最新价小于条件价\n\n最新价小于等于条件价\n\n卖一价大于条件价\n\n卖一价大于等于条件价\n\n卖一价小于条件价\n\n卖一价小于等于条件价\n\n买一价大于条件价\n\n买一价大于等于条件价\n\n买一价小于条件价\n\n买一价小于等于条件价\n\n=========================" + }, + "response": [] + }, + { + "name": "FAK单:/send_fill_and_kill_limit_order郑商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"MA201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"volume_condition\": \"任何数量\",\n \"limit_price\": 2800\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_fill_and_kill_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_fill_and_kill_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n=========================\n\n\"volume_condition\": \"任何数量\",\n\n\"volume_condition\": \"最小数量\",\n\n=========================" + }, + "response": [] + }, + { + "name": "FOK单:/send_fill_or_kill_limit_order郑商所", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instrument_id\": \"MA201\",\n \"direction\": \"买\",\n \"open_close\": \"开仓\",\n \"volume\": 1,\n \"limit_price\": 2800\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/send_fill_or_kill_limit_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "send_fill_or_kill_limit_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"direction\":\"买\",\n\n\"direction\":\"卖\",\n\n========================\n\n\"open_close\": \"开仓\",\n\n\"open_close\": \"平仓\",\n\n\"open_close\": \"平今\",\n\n\"open_close\": \"平昨\",\n\n=========================" + }, + "response": [] + }, + { + "name": "撤单:/cancel_order", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"exchange_id\": \"CFFEX\",\n \"order_sys_id\": \" 414991\",\n \"cancel_action\": \"删除\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{ip}}:5000/api/v1/cancel_order", + "host": [ + "{{ip}}" + ], + "port": "5000", + "path": [ + "api", + "v1", + "cancel_order" + ] + }, + "description": "# API的Body参数枚举值\n\n========================\n\n\"cancel_action\": \"删除\",\n\n========================" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f8257c0a3a42d4f2763af758c589c0931b72d988..5c6f229b7369205a96cbf1dbcc0be488d44738c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,7 @@ tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" http = "*" -rust_ctp = { git = "https://gitee.com/nodequant/rust_ctp.git", tag = "v0.1.1" } +url="*" +log = "0.4" +env_logger = "0.9" +rust_ctp = { git = "https://gitee.com/nodequant/rust_ctp.git", tag = "v0.4.3" } \ No newline at end of file diff --git a/README.md b/README.md index 28f67fe536fae78821b709e6ee272cf1048fc113..61d1ed04436b75f8c444b93af05817aa1e212ba1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,17 @@ -# CTP_Access +# CTP-Access #### 介绍 用于CTP行情与交易的客户端向外提供服务(http/dapr)。实现一个高性能HTTP异步IO的服务器和集成相应的api。主要创新点为将CTP的异步请求与响应变为HTTP的单个API内的同步请求与响应。 -#### 调试环境——如何与dapr sidecar port(DAPR_GRPC_PORT)交互? +#### 开发环境 +##代码 -1. 先启动一个dapr side car 服务 -2. 可以使用k8s port-portward把k8s pod 的sidecar端口导出 -3. 可以使用dapr的rust-sdk来写程序与dapr sidecar和整个dapr系统交互 +把rust-ctp仓库的cpp_ctp文件夹编译好的so文件copy到工程目录的rust_ctp_client目录下!!! -#### 开发环境 -需要手动同步ctp-client项目对应分支的代码,因为目前没解决cargo本地仓库搭建问题, -不能通过依赖仓库的形式使用ctp-client的代码 +## 镜像 Goland启动一个Docker开发环境 镜像地址: -- octopusquant/ctp-md-access:vim-valgrind-cargoconfig-1.53.0-nightly +- noquant/ctp_access:valgrind-cargoconfig-gitstore-rust1.54.0-etcprofile-ctpenv-nqbot-ctpaccesslib-nettools 该镜像安装有: - rust-1.53.0-nightly @@ -22,11 +19,13 @@ Goland启动一个Docker开发环境 镜像地址: - valgrind - vim - 动态链接库已经软连接到/usr/lib - - ln -s /ctp-md-access/src/cpp-ctp-mdclient/libthostmduserapi_se.so /usr/lib - - ln -s /ctp-md-access/src/cpp-ctp-mdclient/librustmdclient.so /usr/lib + - ln -s /ctp_access/src/rust_ctp_client/libthostmduserapi_se.so /usr/lib + - ln -s /ctp_access/src/rust_ctp_client/librustmdclient.so /usr/lib + - ln -s /ctp_access/src/rust_ctp_client/libthosttraderapi_se.so /usr/lib + - ln -s /ctp_access/src/rust_ctp_client/librusttdclient.so /usr/lib ## 挂载准备 -- 挂载: 当前项目文件夹 —> Docker中的/ctp-md-access +- 挂载: 当前项目文件夹 —> Docker中的/ctp_access ## Docker中cargo配置git依赖 @@ -47,23 +46,111 @@ helper = store ## 可配置环境变量 ## 配置环境变量 -#### N视界 -- CTP_MARKET_DATA_FRONT_ADDR=tcp://210.14.72.12:4602 -- CTP_USER_ID=00283563 -- CTP_USER_PASSWORD=10657982 -- CTP_BROKER_ID=10010 -- CTP_SUB_INSTRUMENT_LIST=au2112,ag2112,rb2110,zn2107,sn2107 -#### Simnow -- CTP_MARKET_DATA_FRONT_ADDR=tcp://180.168.146.187:10111 + +#### 日志设定溢出Level的环境变量 +- RUST_LOG=error + - 面对程序crash前一秒情况,输出crash前一秒的错误信息 + - 面对程序不crash的情况, 其他部件出错,导致本程序状态的错误,帮助告警 +- RUST_LOG=warn + - 面对程序不常见的情况,输出if-else的不常用分支的日志,作为提醒 +- RUST_LOG=info + - 面对没出现bug情况,输出日常运维程序基本状态的日志 + - 面对日常运维情况,输出程序的关键信息,生命周期,状态 +- RUST_LOG=debug + - 面对开发调试情况,更细的用日志 + - 面对修复bug情况,更细的日志 +- RUST_LOG=trace + - 面对需要跟踪进程,线程,函数级别 + +#### Simnow 新环境,正常交易日时间 +- CTP_MARKET_FRONT_ADDR=tcp://180.168.146.187:10212 +- CTP_TRADE_FRONT_ADDR=tcp://180.168.146.187:10202 - CTP_USER_ID=142909 - CTP_USER_PASSWORD=qq632386504 - CTP_BROKER_ID=9999 -- CTP_SUB_INSTRUMENT_LIST=au2112,ag2112,rb2110,zn2107,sn2107 +- CTP_APP_ID=simnow_client_test +- CTP_AUTH_CODE=0000000000000000 +- CTP_USER_PRODUCT_INFO=rust_ctp +- CTP_SUB_INSTRUMENT_LIST=au2112 +#### Simnow 7X24环境, 交易日,16:00~次日09:00;非交易日,16:00~次日15:00 +- CTP_MARKET_FRONT_ADDR=tcp://180.168.146.187:10131 +- CTP_TRADE_FRONT_ADDR=tcp://180.168.146.187:10130 +- CTP_USER_ID=142909 +- CTP_USER_PASSWORD=qq632386504 +- CTP_BROKER_ID=9999 +- CTP_APP_ID=simnow_client_test +- CTP_AUTH_CODE=0000000000000000 +- CTP_USER_PRODUCT_INFO=rust_ctp +- CTP_SUB_INSTRUMENT_LIST=au2112,ag2112 -#### Dapr环境变量 +#### 调试环境——如何与dapr sidecar交互? -- DARP_HTTP_HOST(可不配置,默认为localhost,配置为了方便调试) +1. 先启动一个dapr sidecar 服务 +2. 可以使用k8s port-portward把k8s pod 的sidecar端口导出 +3. 可以使用dapr的rust-sdk来写程序与dapr sidecar和整个dapr系统交互 +``` +kubectl port-forward --address 0.0.0.0 pod/nginx-test-b599bc598-4cqvs 3500:3500 +``` +#### Dapr环境相关变量 +- CTP_EVENT_SINKER(配置为DAPR,CTP的事件会发送到DAPR sidecar,其他DAPR可订阅事件) +- DAPR_HTTP_HOST(可不配置,默认为localhost,配置为了方便调试) - DAPR_HTTP_PORT(可不配置,默认为3500,配置为了方便调试) ##### Dapr必须配置的组件名字 - 状态组件固定名字为statestore + +#### Docker RUN +- docker运行命令 + +``` +docker run -d -it --name ctp_access -p 5000:5000 -v /home/amax/testdapruntime/octopus/ctp_access:/ctp_access noquant/ctp_access:valgrind-cargoconfig-gitstore-rust1.54.0-etcprofile-ctpenv-nqbot-ctpaccesslib-nettools +``` +- 根据Simnow开盘时间/非开盘时间更改环境变量CTP_MARKET_FRONT_ADDR与CTP_TRADE_FRONT_ADDR + +```vim /root/.bashrc``` +- 最后加载环境变量 + +```source /root/.bashrc``` + +## Postman测试 + +1. 根目录的CTP_access.postman_collection.json文件导入postman +2. 配置postman的环境变量{{ip}}为可访问Docker容器的主机ip + +## VScode 使用SFTP远程开发 + +### 安装SFTP 1.15.6版本 + +1. 然后ctrl + alt + p选择SFTP插件 +2. 远程服务器创建接收代码文件的文件夹(/root/ctp_access), 配置以下远程服务器信息到插件参数json列表并保存 +3. 右键目标文件并且选择Upload菜单,即可上次代码文件到服务器 +``` +{ + "name": "gpu01", + "host": "192.168.136.28", + "protocol": "sftp", + "port": 2200, + "username": "root", + "password": "123456", + "remotePath": "/root/ctp_access", + "uploadOnSave": true, + "useTempFile": false, + "openSsh": false +} +``` + +``` +{ + "name": "76", + "host": "192.168.202.76", + "protocol": "sftp", + "port": 22, + "username": "amax", + "password": "pc@asdf", + "remotePath": "/home/amax/testdapruntime/octopus/ctp_access", + "uploadOnSave": true, + "useTempFile": false, + "openSsh": false +} +``` + diff --git a/build.rs b/build.rs deleted file mode 100755 index 65dc821947d040605236d4c0c4760128640dc452..0000000000000000000000000000000000000000 --- a/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rustc-link-lib=rustctpclient"); - println!("cargo:rustc-link-lib=thostmduserapi_se"); -} diff --git a/src/ctp_client/cancel_order.rs b/src/ctp_client/cancel_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..ebe6bb72eb1231aee0d42ce5078b8adec833d41e --- /dev/null +++ b/src/ctp_client/cancel_order.rs @@ -0,0 +1,147 @@ + +use rust_ctp::td_api; + +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; +use crate::result::RspResult; +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler,get_error_event_topic}; + +use super::{PushEvent,PushEventSinker}; +use super::CTP_CLIENT_ID; + +///撤单 +pub fn cancel_order(user_id:&str, + broker_id:&str, + exchange_id: &str, + order_sys_id: &str, + cancel_action:&str, + ) -> CTPResult { + + let ctp_error; + + let reg_err_ok = on_rsp_error(); + + //17.报单操作请求响应 + //如撤单操作的响应。交易核心返回含有错误的撤单操作响应 + //回应参数rsp_info就可以得知被拒原因 + let reg_bad_order_action_ok = on_rsp_order_action(); + + //18.报单操作错误响应 + //交易所在通过OnRspOrderAction检查后,会继续验证撤单指令的合法性,如果交易所认为该指令不合法,交易核心通过此函数转发交易所给出的错误 + let reg_err_order_action_ok = on_err_rtn_order_action(); + + if reg_err_ok && reg_bad_order_action_ok && reg_err_order_action_ok { + + let ret = td_api::cancel_order(user_id, + broker_id, + exchange_id, + order_sys_id, + cancel_action); + + if ret < 0 { + error!("cancel_order error, return code:{}",ret); + + ctp_error = api_return_code(ret); + }else{ + + td_api::EventHandler::UnRspOrderAction(); + td_api::EventHandler::UnErrRtnOrderAction(); + recover_on_error_push_event_handler(); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned(), + }; + + return Ok(serde_json::to_string(&rsp_result)?); + } + }else{ + warn!("please access for a while, cancel_order on_rsp_error:{}/on_rsp_order_action:{}/on_err_rtn_order_action:{} event handler is busy", + reg_err_ok,reg_bad_order_action_ok,reg_err_order_action_ok); + + ctp_error = CTPError::CTPQueryFrequentError(NewCTPError( + "cancel_order too often,please access for a while".to_owned())); + } + + td_api::EventHandler::UnRspOrderAction(); + td_api::EventHandler::UnErrRtnOrderAction(); + recover_on_error_push_event_handler(); + + Err(ctp_error) +} + +fn on_rsp_error() -> bool { + + td_api::EventHandler::UnRspError(); + + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at cancel_order, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let error_event_topic = get_error_event_topic(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: error_event_topic, + name: "OnRspError", + payload: rsp_info + }; + + debug!("td_api::EventHandler::OnRspError at cancel_order. push_event to DAPR, event: {:?}",event); + + let event_sinker = PushEventSinker::new(); + event_sinker.sink(event); + + }) +} + +fn on_rsp_order_action() -> bool { + + td_api::EventHandler::OnRspOrderAction(move |rsp_input_order_action, rsp_info, n_request_id, b_is_last| { + + error!("td_api::EventHandler::OnRspOrderAction, ErrorID: {},ErrorMsg: {}", rsp_info.ErrorID, rsp_info.ErrorMsg); + error!("td_api::EventHandler::OnRspOrderAction, rsp_input_order_action: {:?}, n_request_id:{},b_is_last:{}", + rsp_input_order_action,n_request_id,b_is_last); + + + let error_event_topic = get_error_event_topic(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: error_event_topic, + name: "OnRspError", + payload: rsp_info + }; + + debug!("td_api::EventHandler::OnRspOrderAction, covert to OnRspError. push event to DAPR, event: {:?}",event); + + let event_sinker = PushEventSinker::new(); + event_sinker.sink(event); + + }) +} + +fn on_err_rtn_order_action() -> bool { + + td_api::EventHandler::OnErrRtnOrderAction(move |rsp_order_action, rsp_info| { + + error!("td_api::EventHandler::OnErrRtnOrderAction, ErrorID: {},ErrorMsg: {}", rsp_info.ErrorID, rsp_info.ErrorMsg); + error!("td_api::EventHandler::OnErrRtnOrderAction, rsp_order_action: {:?}",rsp_order_action); + + + let error_event_topic = get_error_event_topic(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: error_event_topic, + name: "OnRspError", + payload: rsp_info + }; + + debug!("td_api::EventHandler::OnErrRtnOrderAction, covert to OnRspError. push event to DAPR, event: {:?}",event); + + let event_sinker = PushEventSinker::new(); + event_sinker.sink(event); + }) +} \ No newline at end of file diff --git a/src/ctp_client/mdcontroller.rs b/src/ctp_client/mdcontroller.rs index c2b99515c5d705cb30378a12eebfb00336d45bc7..9d9387678be705e71b75811e1ad99d351e34cc9c 100644 --- a/src/ctp_client/mdcontroller.rs +++ b/src/ctp_client/mdcontroller.rs @@ -1,348 +1,301 @@ -use hyper::{Body,header,Request, Response, StatusCode}; +use rust_ctp::md_api; -use super::super::request::{SubscribeRequest}; - -use super::super::result::RspResult; +use super::{PushEvent,PushEventSinker}; +use super::{CTP_CLIENT_ID, + CTP_MARKET_FRONT_ADDR, + CTP_USER_ID, + CTP_BROKER_ID, + CTP_USER_PASSWORD}; -use super::super::utils::ctperror::{GenericError}; +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; -/// like rust_ctp project test_ctp.rs file +use crate::utils::ctputil::{on_front_disconnected_reason,common_response,get_depth_market_data_topic}; -use rust_ctp::mdclient::mdapi; -use rust_ctp::mdclient::mdapi::CtpEventHandler; +////////CTP_Access Dependency//////// -use std::{env, thread}; -use once_cell::sync::OnceCell; -use std::time::Duration; -use std::collections::HashMap; -use crate::dapr::component_client::{pub_event_to_topic, fetch_state_from_statestore, save_state_to_statestore}; +use hyper::{Body,Request, Response}; -use crate::ctp_client::mdevent::Event; -use crate::utils::ctperror::CTPError; +use super::super::request::{SubscribeRequest}; -static CTP_CLIENT_ID: &str = "CTP"; +use super::super::result::RspResult; -static CTP_SUB_INSTRUMENTS_STATE_KEY:&str = "SUB_MAP"; -static CTP_EMPTY_SUB_INSTRUMENTS_STATE_VALUE:&str = "\"\""; +use std::thread; +use std::time::Duration; +use std::time::Instant; -static CTP_MARKET_DATA_FRONT_ADDR: OnceCell = OnceCell::new(); -static CTP_USER_ID: OnceCell = OnceCell::new(); -static CTP_USER_PASSWORD: OnceCell = OnceCell::new(); -static CTP_BROKER_ID: OnceCell = OnceCell::new(); -/// rust-ctp-mdclient main.rs fn main(){} +use std::collections::HashMap; +use tokio::sync::Mutex; +use once_cell::sync::Lazy; +//基于await的异步线程安全的初始化的全局可变量 +static CTP_MD_READY: Lazy> = Lazy::new(|| { + Mutex::new(false) +}); + +static CTP_SUB_INSTRUMENTS: Lazy>> = Lazy::new(|| { + Mutex::new(HashMap::new()) +}); + +/// ctp_access main.rs fn main(){} pub async fn init(){ - let res = save_state_to_statestore(CTP_SUB_INSTRUMENTS_STATE_KEY.to_string(),CTP_EMPTY_SUB_INSTRUMENTS_STATE_VALUE.to_string()).await; + info!("start; init CTP market client connection, market_front_addr: {}",CTP_MARKET_FRONT_ADDR.get().unwrap()); + md_connect(); + debug!("end; init CTP market client connection"); + info!("init CTP market client successfully!"); - match res { - Ok(_) => {}, - Err(e) => { - panic!("Init Sub Instrument List Error:{}",e); - } - } - - ready_for_ctp(); - register_md_event_handler(); + let version = md_api::get_version(); + info!("CTP market client version is {}", version); - mdapi::connect(CTP_MARKET_DATA_FRONT_ADDR.get().unwrap()); } -fn ready_for_ctp(){ - - CTP_MARKET_DATA_FRONT_ADDR.set(env::var("CTP_MARKET_DATA_FRONT_ADDR").expect("Ops,Error for Env Var CTP_MARKET_DATA_FRONT_ADDR")).unwrap(); - CTP_USER_ID.set(env::var("CTP_USER_ID").expect("Ops,Error for Env Var CTP_USER_ID")).unwrap(); - CTP_USER_PASSWORD.set(env::var("CTP_USER_PASSWORD").expect("Ops,Error for Env Var CTP_USER_PASSWORD")).unwrap(); - CTP_BROKER_ID.set(env::var("CTP_BROKER_ID").expect("Ops,Error for Env Var CTP_BROKER_ID")).unwrap(); - - let version = mdapi::get_version(); +fn md_connect(){ - println!("CTP is Ready, Version is {}", version); -} - -/// 必须主线程先完成注册CTP的事件处理器(闭包) -fn register_md_event_handler() { - - CtpEventHandler::OnRspError(|rsp_info, n_request_id, b_is_last| { - println!("Rust Ops,Error!!!"); - println!("===========Error=============="); - println!("Rust Error nRequestID: {}", n_request_id); - println!("Rust Error bIsLast: {}", b_is_last); - println!("Rust Error ErrorID: {}", rsp_info.ErrorID); - println!("Rust Error ErrorMsg: {}", rsp_info.ErrorMsg); - println!("===========Error=============="); + md_api::EventHandler::OnRspError(|rsp_info, n_request_id, b_is_last| { + error!("md_api::EventHandler::OnRspError, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); }); - CtpEventHandler::OnFrontConnected(|| { - println!("Rust Connected,then login"); + md_api::EventHandler::OnFrontConnected(|| { + info!("md_api::EventHandler::OnFrontConnected,then login"); - while mdapi::login(CTP_USER_ID.get().unwrap(), CTP_USER_PASSWORD.get().unwrap(), CTP_BROKER_ID.get().unwrap()) != 0 { - println!("Rust login failed, continue after 2s ..."); + while md_login() != 0 { + error!("send md_api::login req failed, re_login after 2s ..."); thread::sleep(Duration::from_millis(2 * 1000)) } - println!("Rust send login request successfully."); - + info!("md_api send login req successfully. User_ID:{}, Broker_ID:{}",CTP_USER_ID.get().unwrap(),CTP_BROKER_ID.get().unwrap()); }); - CtpEventHandler::OnFrontDisconnected(|n_reason| { - println!("Rust onFrontDisconnected, reason code: {}", n_reason); + md_api::EventHandler::OnFrontDisconnected(|n_reason| { + info!("md_api::EventHandler::OnFrontDisconnected, reason: {}", on_front_disconnected_reason(n_reason)); + + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut is_ready = rt.block_on(CTP_MD_READY.lock()); + *is_ready = false; + + debug!("md client OnFrontDisconnected, reset CTP_MD_READY status to false"); }); - CtpEventHandler::OnRspUserLogin(|rsp_user_login, rsp_info, n_request_id, b_is_last| { + md_api::connect(CTP_MARKET_FRONT_ADDR.get().unwrap()); +} + +fn md_login() ->i32 { + md_api::EventHandler::OnRspUserLogin(|rsp_user_login, rsp_info, n_request_id, b_is_last| { + let rt = tokio::runtime::Runtime::new().unwrap(); if rsp_info.ErrorID == 0 { - println!("Rust mdclient Login successfully."); - println!( - "Rust mdclient UserLogin UserID: {}, TradingDay: {}, LoginTime: {}", - rsp_user_login.UserID, rsp_user_login.TradingDay, rsp_user_login.LoginTime - ); - let rt = tokio::runtime::Runtime::new().unwrap(); - let _ = rt.block_on(resub_if_relogin()); - }else{ - println!("Rust Ops,UserLogin Error!!!"); - println!("===========Error=============="); - println!("Rust UserLogin Error nRequestID: {}", n_request_id); - println!("Rust UserLogin Error bIsLast: {}", b_is_last); - println!("Rust UserLogin Error ErrorID: {}", rsp_info.ErrorID); - println!("Rust UserLogin Error ErrorMsg: {}", rsp_info.ErrorMsg); - println!("===========Error=============="); - } + info!("md_api::EventHandler::OnRspUserLogin successfully. TradingDay: {}",rsp_user_login.TradingDay); - }); - /* - CtpEventHandler::OnRspUserLogout(|rsp_user_logout, rsp_info, n_request_id, b_is_last| { - println!("Rust mdclient UserLogout nRequestID: {}", n_request_id); - println!("Rust mdclient UserLogout bIsLast: {}", b_is_last); + let mut is_ready = rt.block_on(CTP_MD_READY.lock()); + *is_ready = true; - println!("Rust mdclient UserLogout UserID: {}",rsp_user_logout.UserID); - println!("Rust mdclient UserLogout BrokerID: {}",rsp_user_logout.BrokerID); - }); - */ - - CtpEventHandler::OnRtnDepthMarketData(|depth_market_data| { - - println!( - "Rust OnRtnDepthMarketData TradingDay: {}", - depth_market_data.TradingDay - ); - - println!( - "Rust OnRtnDepthMarketData UpdateTime: {}", - depth_market_data.UpdateTime - ); - - println!( - "Rust OnRtnDepthMarketData InstrumentID: {}", - depth_market_data.InstrumentID - ); - println!( - "Rust OnRtnDepthMarketData LastPrice: {}", - depth_market_data.LastPrice - ); - - let event_topic = get_depth_market_data_topic(CTP_CLIENT_ID,&depth_market_data.InstrumentID); - - let event = Event{ - client: String::from(CTP_CLIENT_ID), - payload: depth_market_data - }; + debug!("md client OnRspUserLogin OK,set CTP_MD_READY status to true"); - let json_str = serde_json::to_string(&event); + info!("start, check or resub last instrument_ids if CTP relogin"); + let _ = rt.block_on(resub_if_relogin()); + info!("end, check or resub"); + }else{ + error!("md_api::EventHandler::OnRspUserLogin Error, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); - match json_str { - Err(e) => { - eprintln!("Error serialize json depth market data!:{}",e) - }, - Ok(event_json) => { + let mut is_ready = rt.block_on(CTP_MD_READY.lock()); + *is_ready = false; - let rt = tokio::runtime::Runtime::new().unwrap(); - let _ = rt.block_on(pub_event_to_topic(event_topic,event_json)); - } + debug!("md client OnRspUserLogin Error,reset ctp_md_login status to false"); } - }); - /* - CtpEventHandler::OnRspUnSubMarketData(|rsp_specific_instrument,rsp_info,n_request_id,b_is_last| { - println!("Rust mdclient OnRspUnSubMarketData nRequestID: {}", n_request_id); - println!("Rust mdclient OnRspUnSubMarketData bIsLast: {}", b_is_last); - - println!("Rust mdclient OnRspUnSubMarketData InstrumentID: {}",rsp_specific_instrument.InstrumentID); - }); - - CtpEventHandler::OnRspSubForQuoteRsp(|rsp_specific_instrument,rsp_info,n_request_id,b_is_last| { - println!("Rust mdclient SubForQuote InstrumentInfo: {:?}",rsp_specific_instrument); }); - CtpEventHandler::OnRspUnSubForQuoteRsp(|rsp_specific_instrument,rsp_info,n_request_id,b_is_last| { - println!("Rust mdclient UnSubForQuote InstrumentInfo: {:?}",rsp_specific_instrument); - });*/ + md_api::login(CTP_USER_ID.get().unwrap(), + CTP_USER_PASSWORD.get().unwrap(), + CTP_BROKER_ID.get().unwrap()) } -pub async fn get_trading_day() -> Result,GenericError> { - let trading_day = mdapi::get_trading_day(); +pub async fn get_trading_day() -> CTPResult> { + let start = Instant::now(); + let is_ready = CTP_MD_READY.lock().await; - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(trading_day))?; + if *is_ready { + let trading_day = md_api::get_trading_day(); - Ok(response) -} + let rsp_result = RspResult{ + code: 0, + msg: "".to_owned(), + result: trading_day + }; -pub async fn subscribe(req: Request) -> Result,GenericError> { + let rsp_json = serde_json::to_string(&rsp_result)?; - let full_body = hyper::body::to_bytes(req.into_body()).await?; - let body_str = String::from_utf8(full_body.to_vec())?; + debug!("API get_trading_day cost: {:?} us", start.elapsed().as_micros()); - let sub_req: SubscribeRequest = serde_json::from_str(&body_str)?; + common_response(rsp_json).await + }else{ - save_sub_instrument_ids(&sub_req.instrument_ids).await?; + let ctp_error = CTPError::CTPClientNotReadyError(NewCTPError("MD Client Not Ready".to_owned())); + + debug!("API get_trading_day cost: {:?} us", start.elapsed().as_micros()); - let ret = mdapi::subscribe(&sub_req.instrument_ids); + Err(ctp_error) + } +} - let rsp_result = RspResult{ - code: 200, - msg: "".to_string(), - result: ret - }; +///登录成功才可以订阅 +pub async fn subscribe(req: Request) -> CTPResult> { - let json = serde_json::to_string(&rsp_result)?; + let is_ready = CTP_MD_READY.lock().await; - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(json))?; + debug!("Before subscribe,check md is_ready: {}",is_ready); - Ok(response) -} + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let sub_req: SubscribeRequest = serde_json::from_str(&body_str)?; -fn get_depth_market_data_topic(client_id:&str,instrument_id: &String) -> String { - format!("{}-{}",client_id,instrument_id) -} + debug!("send subscribe req, instrument_ids: {:?}",&sub_req.instrument_ids); -async fn save_sub_instrument_ids(instrument_ids:&Vec) -> Result,GenericError> { + if *is_ready { - let result = fetch_state_from_statestore(CTP_SUB_INSTRUMENTS_STATE_KEY.to_string()).await; + let mut ctp_instruments_map = CTP_SUB_INSTRUMENTS.lock().await; + for id in &sub_req.instrument_ids { + (*ctp_instruments_map).insert(String::from(id),String::from(id)); + } - match result { - Err(e) => { - eprintln!("Get CTP SUB_MAP Error:{}",e); - return Err(Box::new(CTPError{code:1001,msg: "get SUB_MAP Error".to_owned()})); - }, - Ok(res_body) => { - let full_body = hyper::body::to_bytes(res_body.into_body()).await?; - let last_sub_map_json_str = String::from_utf8(full_body.to_vec())?; + md_api::EventHandler::OnRspSubMarketData(|rsp_specific_instrument,rsp_info,n_request_id,b_is_last| { + debug!("md_api::EventHandler::OnRspSubMarketData, subscribe Instrument {} successfully! rsp_info:{:?}, nRequestID: {},bIsLast: {}", + rsp_specific_instrument.InstrumentID,rsp_info,n_request_id,b_is_last); + }); - println!("Get CTP Sub_Map json_str:{}",last_sub_map_json_str); + md_api::EventHandler::OnRtnDepthMarketData(|depth_market_data| { - let mut new_sub_map = HashMap::new(); + debug!("md_api::EventHandler::OnRtnDepthMarketData, TradingDay: {},InstrumentID: {}, LastPrice: {}", + depth_market_data.TradingDay,depth_market_data.InstrumentID,depth_market_data.LastPrice + ); - if last_sub_map_json_str != CTP_EMPTY_SUB_INSTRUMENTS_STATE_VALUE { - let last_sub_map: HashMap = serde_json::from_str(&last_sub_map_json_str)?; + let event_topic = get_depth_market_data_topic(); + + let event = PushEvent{ + client: CTP_CLIENT_ID, + name: "OnRtnDepthMarketData", + topic: event_topic, + payload: depth_market_data + }; + + let event_sinker = PushEventSinker::new(); + event_sinker.sink(event); + }); + + let ret = md_api::subscribe(&sub_req.instrument_ids); + + debug!("send subscribe req, return code: {}",ret); + + warn!("Only finish to send subscribe req, we have not receive OnRspSubMarketData event yet!"); + + let rsp_result = RspResult{ + code: 0, + msg: "".to_owned(), + result: ret + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await - for id in instrument_ids { - new_sub_map.insert(String::from(id),true); - } + }else { - for (id,_) in last_sub_map { - new_sub_map.insert(String::from(id),true); - } + warn!("Do not send subscribe req , because md is_ready: {}",is_ready); - }else{ + let ctp_error = CTPError::CTPClientNotReadyError(NewCTPError("MD Client Not Ready".to_owned())); - for id in instrument_ids { - new_sub_map.insert(String::from(id),true); - } - } + Err(ctp_error) + } +} - let new_sub_map_json_str = serde_json::to_string(&new_sub_map)?; +pub async fn unsubscribe(req: Request) -> CTPResult> { + let is_ready = CTP_MD_READY.lock().await; - println!("New CTP Sub_Map json_str:{}",new_sub_map_json_str); + debug!("Before unsubscribe,check md client is_ready: {}",is_ready); - let res = save_state_to_statestore(CTP_SUB_INSTRUMENTS_STATE_KEY.to_string(),new_sub_map_json_str).await; + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let sub_req: SubscribeRequest = serde_json::from_str(&body_str)?; - match res { - Err(e) => { - eprintln!("Save CTP SUB_MAP Error:{}",e); - return Err(Box::new(CTPError{code:1001,msg: "Save CTP SUB_MAP Error".to_owned()})); - }, - Ok(_) => { - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from("".to_string()))?; + debug!("send unsubscribe req, instrument_ids: {:?}",&sub_req.instrument_ids); - Ok(response) - } - } + if *is_ready { + + let mut ctp_instruments_map = CTP_SUB_INSTRUMENTS.lock().await; + for id in &sub_req.instrument_ids { + (*ctp_instruments_map).remove(&String::from(id)); } - } -} -pub async fn get_sub_instruments() -> Result,GenericError> { - let result = fetch_state_from_statestore(CTP_SUB_INSTRUMENTS_STATE_KEY.to_string()).await; + md_api::EventHandler::OnRspUnSubMarketData(|rsp_specific_instrument,rsp_info,n_request_id,b_is_last| { + debug!("md_api::EventHandler::OnRspUnSubMarketData, unsubscribe Instrument {} successfully! resp_info:{:?},nRequestID: {},bIsLast: {}", + rsp_specific_instrument.InstrumentID,rsp_info,n_request_id,b_is_last); + }); + + let ret = md_api::unsubscribe(&sub_req.instrument_ids); - match result { - Err(e) => { - eprintln!("Get CTP SUB_MAP Error:{}",e); - return Err(Box::new(CTPError{code:1001,msg: "get SUB_MAP Error".to_owned()})); - }, - Ok(resp) => { - let full_body = hyper::body::to_bytes(resp.into_body()).await?; + debug!("send unsubscribe req, return code: {}",ret); - let last_sub_map_json_str = String::from_utf8(full_body.to_vec())?; + warn!("Only finish to send unsubscribe req, we have not receive OnRspUnSubMarketData event yet!"); + + let rsp_result = RspResult{ + code: 0, + msg: "".to_owned(), + result: ret + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + }else { - //println!("Get CTP Sub_Map json str:{}",last_sub_map_json_str); + warn!("Do not send unsubscribe req, because md is_ready: {}",is_ready); - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(last_sub_map_json_str))?; + let ctp_error = CTPError::CTPClientNotReadyError(NewCTPError("MD Client Not Ready".to_owned())); - Ok(response) - } + Err(ctp_error) } } -pub async fn resub_if_relogin() -> Result,GenericError> { - - let result = fetch_state_from_statestore(CTP_SUB_INSTRUMENTS_STATE_KEY.to_string()).await; +pub async fn get_sub_instruments() -> CTPResult> { + + let ctp_instruments_map = CTP_SUB_INSTRUMENTS.lock().await; + let mut instruments:Vec = Vec::new(); + for (_key,id) in &(*ctp_instruments_map) { + instruments.push(String::from(id)); + } - match result { - Err(e) => { - eprintln!("resub_if_relogin: Get CTP SUB_MAP Error:{}",e); - return Err(Box::new(CTPError{code:1001,msg: "get SUB_MAP Error".to_owned()})); - }, - Ok(resp) => { - let full_body = hyper::body::to_bytes(resp.into_body()).await?; + let rsp_result = RspResult{ + code: 0, + msg: "".to_owned(), + result: instruments + }; - let last_sub_map_json_str = String::from_utf8(full_body.to_vec())?; + let rsp_json = serde_json::to_string(&rsp_result)?; - if last_sub_map_json_str != CTP_EMPTY_SUB_INSTRUMENTS_STATE_VALUE { - let last_sub_map: HashMap = serde_json::from_str(&last_sub_map_json_str)?; + common_response(rsp_json).await +} - println!("resub_if_relogin: GET Last Sub Map:{:?}", last_sub_map); - let mut instrument_ids = vec![]; - for (instrument_id,_) in last_sub_map{ - instrument_ids.push(instrument_id); - } +pub async fn resub_if_relogin() -> i32 { - let ret = mdapi::subscribe(&instrument_ids); + let ctp_instruments_map = CTP_SUB_INSTRUMENTS.lock().await; - if ret != 0 { - eprintln!("resub_if_relogin: Subscribe LAST SUB_MAP ErrorCode:{}",ret); - return Err(Box::new(CTPError{code:1001,msg: "Subscribe LAST SUB_MAP Error".to_owned()})); - } - } + let mut instruments:Vec = Vec::new(); + for (_key,id) in &(*ctp_instruments_map) { + instruments.push(String::from(id)); + } - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from("".to_owned()))?; + let ret = md_api::subscribe(&instruments); - Ok(response) - } + if ret != 0 { + error!("resub_if_relogin, Subscribe last sub instruments ErrorCode:{}",ret); + }else{ + debug!("resub_if_relogin, Subscribe last sub instruments: {:?} ok!",instruments); } + + ret } \ No newline at end of file diff --git a/src/ctp_client/mdevent.rs b/src/ctp_client/mdevent.rs deleted file mode 100644 index 8fe284920c1b51802ff429069b696b10f4ae6cc3..0000000000000000000000000000000000000000 --- a/src/ctp_client/mdevent.rs +++ /dev/null @@ -1,11 +0,0 @@ - -use serde::{Serialize}; - -#[derive(Serialize)] -pub struct Event{ - pub client: String, - pub payload: T -} - -#[derive(Serialize)] -pub struct EmptyEvent{} diff --git a/src/ctp_client/mod.rs b/src/ctp_client/mod.rs index e3e46f2552f05cbde4f6d93fc31b684da66e458b..1cdfac79e8bd48aa0f57e4dc5a67a450da18cb2a 100755 --- a/src/ctp_client/mod.rs +++ b/src/ctp_client/mod.rs @@ -1,2 +1,145 @@ pub mod mdcontroller; -pub mod mdevent; \ No newline at end of file +pub mod tdcontroller; +mod query_trading_account; +mod query_investor_position; +mod query_commission_rate; +mod query_settlement_info; +mod query_instruments; +mod send_market_order; +mod send_limit_order; +mod send_fill_and_kill_limit_order; +mod send_fill_or_kill_limit_order; +mod send_stop_limit_order; +mod send_market_if_touched_order; +mod cancel_order; + +use once_cell::sync::OnceCell; +use std::collections::HashMap; +use tokio::sync::Mutex; +use once_cell::sync::Lazy; + +use rust_ctp::api_struct::rsp_info_field::RspInfoField; +use rust_ctp::api_struct::rsp_instrument_commission_rate_field::RspInstrumentCommissionRateField; +use rust_ctp::api_struct::rsp_instrument_field::RspInstrumentField; +use rust_ctp::api_struct::rsp_investor_position_field::RspInvestorPositionField; +use rust_ctp::api_struct::rsp_settlement_info_field::RspSettlementInfoField; +use rust_ctp::api_struct::rsp_trading_account_field::RspTradingAccountField; + +use std::{env}; +use serde::{Serialize}; + +use crate::dapr::component_client::pub_event_to_topic; + +#[derive(Debug,Serialize)] +pub enum InnerEvent{ + OnRspError(RspInfoField), + OnRspQryInstrumentCommissionRate(bool,RspInstrumentCommissionRateField), + OnRspQryInstrument(bool,RspInstrumentField), + OnRspQryInvestorPosition(bool,RspInvestorPositionField), + OnRspQrySettlementInfo(bool,RspSettlementInfoField), + OnRspQryTradingAccount(bool,RspTradingAccountField), +} + +#[derive(Debug,Serialize)] +pub struct PushEvent { + pub client: &'static str, + pub name: &'static str, + pub topic: String, + pub payload: T, +} + +pub trait EventSender{ + fn send(&self,event_topic:String,event_json: String); +} + +pub struct InnerEventSender; +impl EventSender for InnerEventSender { + fn send(&self,event_topic:String,event_json: String){ + debug!("Push Event Topic:{}, JSON:{}", event_topic,event_json); + } +} + +pub struct DaprEventSender; +impl EventSender for DaprEventSender { + fn send(&self,event_topic:String,event_json: String){ + + debug!("Push Event to Dapr, Topic:{}, JSON:{}", event_topic,event_json); + + let rt = tokio::runtime::Runtime::new().unwrap(); + let _ = rt.block_on(pub_event_to_topic(event_topic,event_json)); + } +} + + +pub struct PushEventSinker{ + sinker: Box +} + +impl PushEventSinker { + pub fn new()-> Self { + + let sinker_config = CTP_EVENT_SINKER.get().unwrap(); + + match sinker_config.as_str() { + "DAPR" => { + PushEventSinker{ + sinker: Box::new(DaprEventSender{}) as Box + } + }, + _ => { + PushEventSinker{ + sinker: Box::new(InnerEventSender{}) as Box + } + } + } + } + + pub fn sink(&self,event: PushEvent){ + let event_json = serde_json::to_string(&event).unwrap(); + + self.sinker.send(event.topic,event_json); + } +} + +pub static CTP_CLIENT_ID: &str = "CTP"; + +static CTP_EVENT_SINKER: OnceCell = OnceCell::new(); + +static CTP_MARKET_FRONT_ADDR: OnceCell = OnceCell::new(); +static CTP_TRADE_FRONT_ADDR: OnceCell = OnceCell::new(); + +static CTP_USER_ID: OnceCell = OnceCell::new(); +static CTP_USER_PASSWORD: OnceCell = OnceCell::new(); +static CTP_BROKER_ID: OnceCell = OnceCell::new(); +static CTP_APP_ID: OnceCell = OnceCell::new(); +static CTP_AUTH_CODE: OnceCell = OnceCell::new(); +static CTP_USER_PRODUCT_INFO: OnceCell = OnceCell::new(); + +//基于await的异步线程安全的初始化的全局可变量 +static CTP_INSTRUMENT_HASHMAP: Lazy>> = Lazy::new(|| { + let m = HashMap::new(); + Mutex::new(m) +}); + +pub async fn init() { + + CTP_EVENT_SINKER.set(env::var("CTP_EVENT_SINKER").expect("Ops,Error for Env Var CTP_EVENT_SINKER")).unwrap(); + + CTP_MARKET_FRONT_ADDR.set(env::var("CTP_MARKET_FRONT_ADDR").expect("Ops,Error for Env Var CTP_MARKET_FRONT_ADDR")).unwrap(); + CTP_TRADE_FRONT_ADDR.set(env::var("CTP_TRADE_FRONT_ADDR").expect("Ops,Error for Env Var CTP_TRADE_FRONT_ADDR")).unwrap(); + + CTP_USER_ID.set(env::var("CTP_USER_ID").expect("Ops,Error for Env Var CTP_USER_ID")).unwrap(); + CTP_USER_PASSWORD.set(env::var("CTP_USER_PASSWORD").expect("Ops,Error for Env Var CTP_USER_PASSWORD")).unwrap(); + CTP_BROKER_ID.set(env::var("CTP_BROKER_ID").expect("Ops,Error for Env Var CTP_BROKER_ID")).unwrap(); + + CTP_APP_ID.set(env::var("CTP_APP_ID").expect("Ops,Error for Env Var CTP_APP_ID")).unwrap(); + CTP_AUTH_CODE.set(env::var("CTP_AUTH_CODE").expect("Ops,Error for Env Var CTP_AUTH_CODE")).unwrap(); + CTP_USER_PRODUCT_INFO.set(env::var("CTP_USER_PRODUCT_INFO").expect("Ops,Error for Env Var CTP_USER_PRODUCT_INFO")).unwrap(); + + mdcontroller::init().await; + + tdcontroller::init().await; + + info!("CTP market client Ready, market_front_addr: {}",CTP_MARKET_FRONT_ADDR.get().unwrap()); + info!("CTP trader client Ready, trade_front_addr: {}",CTP_TRADE_FRONT_ADDR.get().unwrap()); +} \ No newline at end of file diff --git a/src/ctp_client/query_commission_rate.rs b/src/ctp_client/query_commission_rate.rs new file mode 100644 index 0000000000000000000000000000000000000000..769162671cbf788a28fe68b0e5bda21956c9a764 --- /dev/null +++ b/src/ctp_client/query_commission_rate.rs @@ -0,0 +1,110 @@ +use rust_ctp::td_api; +use std::sync::mpsc; + +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; +use crate::result::RspResult; +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler}; + +use super::InnerEvent; + +pub fn query_commission_rate(user_id:&str,broker_id:&str,instrument_id:&str) -> CTPResult { + + let ctp_error; + + //--T1--->| |--->process T1 + // --->send same enum T instance---> + //--T2--->| |--->process T2 + let (tx, rx) = mpsc::channel(); + let tx_rsp_err = tx.clone(); + + let reg_err_ok = on_rsp_error(tx_rsp_err); + let reg_ok = on_rsp_qry_instrument_commission_rate(tx); + + if reg_err_ok && reg_ok { + + let ret = td_api::query_commission_rate(user_id,broker_id,instrument_id); + + if ret == 0 { + //Must received the same type + let received_event = rx.recv()?; + + match received_event{ + InnerEvent::OnRspError(event) => { + + ctp_error = CTPError::CTPOnRspError(NewCTPError( + format!("query_commission_rate error event: {:?}",event))); + }, + InnerEvent::OnRspQryInstrumentCommissionRate(_is_last,event) => { + + td_api::EventHandler::UnRspQryInstrumentCommissionRate(); + recover_on_error_push_event_handler(); + + let rsp_result = RspResult{ + code: ret, + msg: "".to_owned(), + result: event + }; + + return Ok(serde_json::to_string(&rsp_result)?); + }, + _ => { + error!("query_instruments received Unknow event: {:?}",received_event); + + ctp_error = CTPError::CTPOnRspError(NewCTPError( + format!("query_instruments received Unknow event: {:?}",received_event))); + } + } + }else{ + + error!("query_commission_rate error, ret:{}, msg: {}", ret, api_return_code(ret)); + + ctp_error = api_return_code(ret); + } + }else{ + warn!("please access for a while, query_commission_rate OnRspError:{}/OnRspQryInstrumentCommissionRate:{} event handler is busy.", + reg_err_ok,reg_ok); + + ctp_error = CTPError::CTPQueryFrequentError(NewCTPError( + "query_commission_rate too often,please access for a while".to_owned())); + } + + td_api::EventHandler::UnRspQryInstrumentCommissionRate(); + recover_on_error_push_event_handler(); + + Err(ctp_error) +} + +fn on_rsp_error(tx_rsp_err: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::UnRspError(); + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at query_commission_rate, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let event = InnerEvent::OnRspError(rsp_info); + + tx_rsp_err.send(event).unwrap(); + }) +} + +fn on_rsp_qry_instrument_commission_rate(tx: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::OnRspQryInstrumentCommissionRate(move |rsp_instrument_commission_rate_field, rsp_info, n_request_id, b_is_last| { + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspQryInstrumentCommissionRate, ErrorID: {},ErrorMsg: {}",rsp_info.ErrorID,rsp_info.ErrorMsg); + + let event = InnerEvent::OnRspError(rsp_info); + + tx.send(event).unwrap(); + + }else{ + debug!("td_api::EventHandler::OnRspQryInstrumentCommissionRate, rsp_instrument_commission_rate_field: {:?}, n_request_id:{},b_is_last:{}", + rsp_instrument_commission_rate_field,n_request_id,b_is_last); + + let event = InnerEvent::OnRspQryInstrumentCommissionRate(b_is_last,rsp_instrument_commission_rate_field); + tx.send(event).unwrap(); + } + }) +} \ No newline at end of file diff --git a/src/ctp_client/query_instruments.rs b/src/ctp_client/query_instruments.rs new file mode 100644 index 0000000000000000000000000000000000000000..1e03f733726b13c06fd870020d214a0628347b25 --- /dev/null +++ b/src/ctp_client/query_instruments.rs @@ -0,0 +1,105 @@ +use rust_ctp::td_api; + +use std::sync::mpsc; + +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler}; + +use super::InnerEvent; +use super::CTP_INSTRUMENT_HASHMAP; + +//查询所有交易标的 +pub async fn query_instruments() -> i32 { + + let mut ret; + + //--T1--->| |--->process T1 + // --->send same enum T instance---> + //--T2--->| |--->process T2 + let (tx, rx) = mpsc::channel(); + let tx_rsp_err = tx.clone(); + + let reg_err_ok = on_rsp_error(tx_rsp_err); + let reg_ok = on_rsp_qry_instrument(tx); + + if reg_err_ok && reg_ok { + ret = td_api::query_instruments(); + + if ret == 0 { + let mut instrument_list = Vec::new(); + loop { + let received_event = rx.recv().unwrap(); + + match received_event { + InnerEvent::OnRspError(event) => { + error!("query_instruments error event: {:?}",event); + ret = -1; + instrument_list.clear(); + break; + }, + InnerEvent::OnRspQryInstrument(is_last,event) => { + instrument_list.push(event); + + if is_last { + break; + } + }, + _ => { + error!("query_instruments received Unknow event: {:?}",received_event); + } + } + } + + //Process All Instruments + let mut instrument_hashmap = CTP_INSTRUMENT_HASHMAP.lock().await; + for instrument in &instrument_list{ + //let rsp_instrument:RspInstrumentField = serde_json::from_str(event_str).unwrap(); + instrument_hashmap.insert(instrument.InstrumentID.clone(),instrument.clone()); + } + + }else{ + error!("query_instruments error, ret:{}, msg: {}", ret, api_return_code(ret)); + } + }else{ + error!("query_instruments error about can not bind OnRspError event handler "); + ret = -1; + } + + td_api::EventHandler::UnRspQryInstrument(); + recover_on_error_push_event_handler(); + + ret +} + +fn on_rsp_error(tx_rsp_err: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::UnRspError(); + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at query_instruments, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let event = InnerEvent::OnRspError(rsp_info); + + tx_rsp_err.send(event).unwrap(); + }) +} + +fn on_rsp_qry_instrument(tx: std::sync::mpsc::Sender) -> bool { + td_api::EventHandler::OnRspQryInstrument(move |rsp_instrument_field, rsp_info, n_request_id, b_is_last| { + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspQryInstrument, ErrorID: {},ErrorMsg: {}",rsp_info.ErrorID,rsp_info.ErrorMsg); + + let event = InnerEvent::OnRspError(rsp_info); + + tx.send(event).unwrap(); + + }else{ + debug!("td_api::EventHandler::OnRspQryInstrument, rsp_instrument_field: {:?}, n_request_id:{},b_is_last:{}", + rsp_instrument_field, n_request_id, b_is_last); + + let event = InnerEvent::OnRspQryInstrument(b_is_last,rsp_instrument_field); + tx.send(event).unwrap(); + } + }) +} \ No newline at end of file diff --git a/src/ctp_client/query_investor_position.rs b/src/ctp_client/query_investor_position.rs new file mode 100644 index 0000000000000000000000000000000000000000..4c99a9b85a50ae2ef2843226dd36d42c8c9efcdb --- /dev/null +++ b/src/ctp_client/query_investor_position.rs @@ -0,0 +1,106 @@ +use rust_ctp::td_api; +use std::sync::mpsc; + +use crate::result::RspResult; +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler}; + +use super::InnerEvent; + +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; + +//sync func about thread-safely +pub fn query_investor_position(user_id:&str,broker_id:&str) -> CTPResult { + + let ctp_error; + + let (tx, rx) = mpsc::channel(); + let tx_rsp_err = tx.clone(); + + let reg_err_ok = on_rsp_error(tx_rsp_err); + let reg_ok = on_rsp_qry_investor_position(tx); + + if reg_err_ok && reg_ok{ + + let ret = td_api::query_investor_position(user_id,broker_id); + + if ret == 0 { + let received_event = rx.recv()?; + + match received_event{ + InnerEvent::OnRspError(event) => { + + ctp_error = CTPError::CTPOnRspError(NewCTPError( + format!("query_investor_position error event: {:?}",event))); + + }, + InnerEvent::OnRspQryInvestorPosition(_is_last,event) => { + + td_api::EventHandler::UnRspQryInvestorPosition(); + recover_on_error_push_event_handler(); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: event + }; + + return Ok(serde_json::to_string(&rsp_result)?); + }, + _ => { + error!("query_investor_position received Unknow event: {:?}",received_event); + + ctp_error = CTPError::CTPUnknowError(NewCTPError( + format!("query_investor_position received Unknow event: {:?}",received_event))); + } + } + + }else{ + error!("query_investor_position error, ret:{}, msg: {}", ret,api_return_code(ret)); + + ctp_error = api_return_code(ret); + } + }else{ + warn!("query_investor_position OnRspError event handler is busy,please access for a while"); + + ctp_error = CTPError::CTPQueryFrequentError(NewCTPError( + "query_investor_position too often,please access for a while".to_owned())); + } + + td_api::EventHandler::UnRspQryInvestorPosition(); + recover_on_error_push_event_handler(); + + Err(ctp_error) +} + +fn on_rsp_error(tx_rsp_err: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::UnRspError(); + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at query_investor_position, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let event = InnerEvent::OnRspError(rsp_info); + + tx_rsp_err.send(event).unwrap(); + }) +} + +fn on_rsp_qry_investor_position(tx: std::sync::mpsc::Sender) ->bool { + + td_api::EventHandler::OnRspQryInvestorPosition(move |rsp_investor_position_field, rsp_info, n_request_id, b_is_last| { + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspQryInvestorPosition, ErrorID: {},ErrorMsg: {}",rsp_info.ErrorID,rsp_info.ErrorMsg); + + let event = InnerEvent::OnRspError(rsp_info); + tx.send(event).unwrap(); + }else{ + debug!("td_api::EventHandler::OnRspQryInvestorPosition, investor_position_field: {:?}, n_request_id:{},b_is_last:{}", + rsp_investor_position_field,n_request_id,b_is_last); + + let event = InnerEvent::OnRspQryInvestorPosition(b_is_last,rsp_investor_position_field); + tx.send(event).unwrap(); + } + }) +} \ No newline at end of file diff --git a/src/ctp_client/query_settlement_info.rs b/src/ctp_client/query_settlement_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..88d1eabf409a74814cea2889446d8de08434b357 --- /dev/null +++ b/src/ctp_client/query_settlement_info.rs @@ -0,0 +1,149 @@ +use rust_ctp::td_api; +use std::sync::mpsc; + +use crate::result::RspResult; +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler}; + +use rust_ctp::api_struct::rsp_settlement_info_field::RspSettlementInfoField; + +use super::InnerEvent; + +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; + +//sync func about thread-safely +pub fn query_settlement_info(user_id:&str,broker_id:&str) -> CTPResult { + + let mut ctp_error; + + let (tx, rx) = mpsc::channel(); + let tx_rsp_err = tx.clone(); + + let reg_err_ok = on_rsp_error(tx_rsp_err); + + let reg_ok = on_rsp_qry_settlement_info(tx); + + if reg_err_ok && reg_ok { + + let ret = td_api::query_settlement_info(user_id,broker_id); + + if ret == 0 { + + let mut settlement_info_list = Vec::new(); + loop { + let received_event = rx.recv().unwrap(); + + match received_event { + InnerEvent::OnRspError(event) => { + + ctp_error = CTPError::CTPOnRspError(NewCTPError( + format!("query_settlement_info error event: {:?}",event))); + + settlement_info_list.clear(); + break; + + }, + InnerEvent::OnRspQrySettlementInfo(is_last,event) => { + // do not need to receive event + settlement_info_list.push(event); + + if is_last { + break; + } + }, + _ => { + error!("query_settlement_info received Unknow event: {:?}",received_event); + + ctp_error = CTPError::CTPUnknowError(NewCTPError( + format!("query_settlement_info received Unknow event: {:?}",received_event))); + } + } + } + + + + td_api::EventHandler::UnRspQrySettlementInfo(); + recover_on_error_push_event_handler(); + + let mut com_rsp_settlement_info = RspSettlementInfoField { + TradingDay: "".to_string(), + SettlementID: 0, + BrokerID: "".to_string(), + InvestorID: "".to_string(), + SequenceNo: 0, + Content: "".to_string(), + AccountID: "".to_string(), + CurrencyID: "".to_string() + }; + + //Process All settlement info + for settlement_info in &settlement_info_list { + if com_rsp_settlement_info.Content == "".to_owned() { + com_rsp_settlement_info = settlement_info.clone(); + }else{ + com_rsp_settlement_info.Content = com_rsp_settlement_info.Content+&settlement_info.Content; + } + } + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: com_rsp_settlement_info + }; + + return Ok(serde_json::to_string(&rsp_result)?); + + }else{ + error!("query_settlement_info error, ret:{}, msg: {}", ret,api_return_code(ret)); + + ctp_error = api_return_code(ret); + } + + }else{ + warn!("query_settlement_info OnRspError event handler is busy,please access for a while"); + + ctp_error = CTPError::CTPQueryFrequentError(NewCTPError( + "query_settlement_info too often,please access for a while".to_owned())); + + } + + td_api::EventHandler::UnRspQrySettlementInfo(); + recover_on_error_push_event_handler(); + + Err(ctp_error) +} + + +fn on_rsp_error(tx_rsp_err: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::UnRspError(); + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at query_settlement_info, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let event = InnerEvent::OnRspError(rsp_info); + + tx_rsp_err.send(event).unwrap(); + }) +} + +fn on_rsp_qry_settlement_info(tx: std::sync::mpsc::Sender)-> bool { + + td_api::EventHandler::OnRspQrySettlementInfo(move |rsp_settlement_info_field, rsp_info, n_request_id, b_is_last| { + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspQrySettlementInfo, ErrorID: {},ErrorMsg: {}",rsp_info.ErrorID,rsp_info.ErrorMsg); + + let event = InnerEvent::OnRspError(rsp_info); + + tx.send(event).unwrap(); + }else{ + debug!("td_api::EventHandler::OnRspQrySettlementInfo, rsp_settlement_info_field: {:?}, n_request_id:{},b_is_last:{}", + rsp_settlement_info_field,n_request_id,b_is_last); + + let event = InnerEvent::OnRspQrySettlementInfo(b_is_last,rsp_settlement_info_field); + + tx.send(event).unwrap(); + } + }) +} \ No newline at end of file diff --git a/src/ctp_client/query_trading_account.rs b/src/ctp_client/query_trading_account.rs new file mode 100644 index 0000000000000000000000000000000000000000..09e28484965836d96d8b9cf08b82363f7c08b481 --- /dev/null +++ b/src/ctp_client/query_trading_account.rs @@ -0,0 +1,115 @@ +use std::time::Instant; +use rust_ctp::td_api; +use std::sync::mpsc; + +use crate::result::RspResult; +use crate::utils::ctputil::{api_return_code,recover_on_error_push_event_handler}; + +use crate::ctperror::{NewCTPError,CTPError,CTPResult}; +use super::InnerEvent; + +//查询资金账户 +pub fn query_trading_account(user_id:&str,broker_id:&str) -> CTPResult { + let start = Instant::now(); + + let ctp_error; + + let (tx, rx) = mpsc::channel(); + let tx_rsp_err = tx.clone(); + + let reg_err_ok = on_rsp_error(tx_rsp_err); + debug!("binding OnRspError cost: {:?} us", start.elapsed().as_micros()); + let reg_ok = on_rsp_qry_trading_account(tx); + + if reg_err_ok && reg_ok { + + debug!("binding OnRspError & OnRspQryTradingAccount cost: {:?} us", start.elapsed().as_micros()); + + let ret = td_api::query_trading_account(user_id,broker_id); + + if ret == 0 { + let received_event = rx.recv().unwrap(); + + match received_event { + InnerEvent::OnRspError(event) => { + + ctp_error = CTPError::CTPOnRspError(NewCTPError( + format!("query_trading_account error event: {:?}",event))); + + }, + InnerEvent::OnRspQryTradingAccount(_is_last,event) => { + + td_api::EventHandler::UnRspQryTradingAccount(); + recover_on_error_push_event_handler(); + + let rsp_result = RspResult{ + code: ret, + msg: "".to_owned(), + result: event + }; + + return Ok(serde_json::to_string(&rsp_result)?); + }, + _ => { + error!("query_trading_account received Unknow event: {:?}",received_event); + + ctp_error = CTPError::CTPUnknowError(NewCTPError( + format!("query_trading_account received Unknow event: {:?}",received_event))); + } + } + }else{ + error!("query_trading_account error, ret:{}, msg: {}", ret,api_return_code(ret)); + + ctp_error = api_return_code(ret); + } + + }else{ + warn!("query_trading_account OnRspError event handler is busy,please access for a while"); + + ctp_error = CTPError::CTPQueryFrequentError(NewCTPError( + "query_trading_account too often,please access for a while".to_owned())); + } + + debug!("API query_trading_account cost: {:?} ms", start.elapsed().as_millis()); + + td_api::EventHandler::UnRspQryTradingAccount(); + recover_on_error_push_event_handler(); + + Err(ctp_error) +} + +fn on_rsp_error(tx_rsp_err: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::UnRspError(); + td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError at query_trading_account, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + let event = InnerEvent::OnRspError(rsp_info); + + tx_rsp_err.send(event).unwrap(); + }) +} + +fn on_rsp_qry_trading_account(tx: std::sync::mpsc::Sender) -> bool { + + td_api::EventHandler::OnRspQryTradingAccount(move |rsp_trading_account, rsp_info, n_request_id, b_is_last| { + + let event; + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspQryTradingAccount, ErrorID: {},ErrorMsg: {}",rsp_info.ErrorID,rsp_info.ErrorMsg); + + event = InnerEvent::OnRspError(rsp_info); + + }else{ + debug!("td_api::EventHandler::OnRspQryTradingAccount, TradingAccount: {:?}, n_request_id:{},b_is_last:{}", + rsp_trading_account,n_request_id,b_is_last); + + event = InnerEvent::OnRspQryTradingAccount(b_is_last,rsp_trading_account); + } + + tx.send(event).unwrap(); + }) +} \ No newline at end of file diff --git a/src/ctp_client/send_fill_and_kill_limit_order.rs b/src/ctp_client/send_fill_and_kill_limit_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d38f3172d6598c22822c0acaf51dd1d1a4408ab --- /dev/null +++ b/src/ctp_client/send_fill_and_kill_limit_order.rs @@ -0,0 +1,46 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///5.FAK单 +pub fn send_fill_and_kill_limit_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str, + volume:i32, + volume_condition:&str, + limit_price:f64) -> CTPResult { + + let ret = td_api::send_fill_and_kill_limit_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume, + volume_condition, + limit_price); + + if ret == order_def_id { + debug!("send_fill_and_kill_limit_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + }else{ + + error!("send_fill_and_kill_limit_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } + +} \ No newline at end of file diff --git a/src/ctp_client/send_fill_or_kill_limit_order.rs b/src/ctp_client/send_fill_or_kill_limit_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..d49357cd1cf18e23d46b7e832483e15883038a13 --- /dev/null +++ b/src/ctp_client/send_fill_or_kill_limit_order.rs @@ -0,0 +1,44 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///3.触价单 +pub fn send_fill_or_kill_limit_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str, + volume:i32, + limit_price:f64) -> CTPResult { + + let ret = td_api::send_fill_or_kill_limit_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume, + limit_price); + + if ret != order_def_id { + + debug!("send_fill_or_kill_limit_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + }else{ + + error!("send_fill_or_kill_limit_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } +} diff --git a/src/ctp_client/send_limit_order.rs b/src/ctp_client/send_limit_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed3bcf3a8633a8a1210d5ddafe0274f62556b2e3 --- /dev/null +++ b/src/ctp_client/send_limit_order.rs @@ -0,0 +1,44 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///2.限价单 +pub fn send_limit_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str, + volume:i32, + limit_price:f64) -> CTPResult { + + let ret = td_api::send_limit_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume, + limit_price); + + if ret == order_def_id { + + debug!("send_market_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + + }else{ + error!("send_limit_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } +} \ No newline at end of file diff --git a/src/ctp_client/send_market_if_touched_order.rs b/src/ctp_client/send_market_if_touched_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..bfa5d0271a8e052e0284354b886e818527914a70 --- /dev/null +++ b/src/ctp_client/send_market_if_touched_order.rs @@ -0,0 +1,44 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///3.触价单 +pub fn send_market_if_touched_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str,volume:i32, + stop_price_condition:&str, + stop_price:f64) -> CTPResult { + + let ret = td_api::send_market_if_touched_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume, + stop_price_condition, + stop_price); + + if ret == order_def_id { + debug!("send_market_if_touched_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + }else{ + error!("send_market_if_touched_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } + +} \ No newline at end of file diff --git a/src/ctp_client/send_market_order.rs b/src/ctp_client/send_market_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..d55590cf0bae317d1ae1491b7d1b70e2f65abd3d --- /dev/null +++ b/src/ctp_client/send_market_order.rs @@ -0,0 +1,44 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///1.市价单 +pub fn send_market_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str, + volume:i32) -> CTPResult { + + let ret = td_api::send_market_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume); + + if ret == order_def_id { + + debug!("send_market_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + + }else{ + error!("send_market_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } + + +} \ No newline at end of file diff --git a/src/ctp_client/send_stop_limit_order.rs b/src/ctp_client/send_stop_limit_order.rs new file mode 100644 index 0000000000000000000000000000000000000000..66ec9da1f755982cc5d8c2109ddae79b0cc6b1d6 --- /dev/null +++ b/src/ctp_client/send_stop_limit_order.rs @@ -0,0 +1,48 @@ +use rust_ctp::td_api; + +use crate::result::RspResult; +use crate::utils::ctputil::api_return_code; + +use crate::ctperror::CTPResult; + +///4.条件单 +pub fn send_stop_limit_order(order_def_id:i32, + user_id:&str, + broker_id:&str, + instrument_id:&str, + direction:&str, + open_close:&str, + volume:i32, + limit_price:f64, + stop_price_condition:&str, + stop_price:f64) -> CTPResult { + + let ret = td_api::send_stop_limit_order(order_def_id.to_string().as_str(), + user_id, + broker_id, + instrument_id, + direction, + open_close, + volume, + limit_price, + stop_price_condition, + stop_price); + + if ret == order_def_id { + + debug!("send_stop_limit_order req ok, return order_def_id:{}", ret); + + let rsp_result = RspResult { + code: ret, + msg: "".to_owned(), + result: "".to_owned() + }; + + Ok(serde_json::to_string(&rsp_result)?) + }else{ + + error!("send_stop_limit_order error, ret:{}, msg: {}", ret,api_return_code(ret)); + + Err(api_return_code(ret)) + } +} \ No newline at end of file diff --git a/src/ctp_client/tdcontroller.rs b/src/ctp_client/tdcontroller.rs new file mode 100644 index 0000000000000000000000000000000000000000..4a4ab576160568d9beba20eb1f6211fbe95f5c0e --- /dev/null +++ b/src/ctp_client/tdcontroller.rs @@ -0,0 +1,714 @@ +use rust_ctp::td_api; + +use super::query_settlement_info::*; +use super::query_instruments::*; +use super::query_trading_account::*; +use super::query_investor_position::*; +use super::query_commission_rate::*; +use super::send_market_order::*; +use super::send_limit_order::*; +use super::send_market_if_touched_order::*; +use super::send_stop_limit_order::*; +use super::send_fill_and_kill_limit_order::*; +use super::send_fill_or_kill_limit_order::*; +use super::cancel_order::*; + +use crate::result::RspResult; +use crate::ctperror::CTPResult; +use crate::utils::ctputil::{on_front_disconnected_reason,common_response}; +use super::super::request::{SendMarketOrderRequest, + SendLimitOrderRequest, + SendMarketIfTouchedOrderRequest, + SendStopLimitOrderRequest, + SendFillAndKillLimitOrderRequest, + SendFillOrKillLimitOrderRequest, + CancelOrderRequest}; + +use super::PushEvent; + +use super::{CTP_CLIENT_ID, + CTP_TRADE_FRONT_ADDR, + CTP_USER_ID, + CTP_BROKER_ID, + CTP_USER_PASSWORD, + CTP_APP_ID, + CTP_AUTH_CODE, + CTP_USER_PRODUCT_INFO, + CTP_INSTRUMENT_HASHMAP}; + +use crate::utils::http_url_params; + +////////CTP_Access Dependency//////// + +use hyper::{Body,Request, Response}; + +use std::thread; +use std::time::Duration; + +use tokio::sync::Mutex; +use once_cell::sync::Lazy; +//基于await的异步线程安全的初始化的全局可变量 +static CTP_TD_INIT: Lazy> = Lazy::new(|| { + Mutex::new(false) +}); + +static CTP_TD_READY: Lazy> = Lazy::new(|| { + Mutex::new(false) +}); + +static CTP_TD_ORDER_DEF_ID: Lazy> = Lazy::new(|| { + Mutex::new(0) +}); + +pub async fn init() { + info!("start; init push event_handler for CTP access trader"); + register_push_event_handler(); + info!("end; init push event_handler for CTP access trader"); + info!("start; init CTP trader client connection, trade_front_addr: {}",CTP_TRADE_FRONT_ADDR.get().unwrap()); + td_connect(); + debug!("end; init CTP trader client connection"); + debug!("wait for CTP trader client init..."); + + loop { + let is_init = CTP_TD_INIT.lock().await; + //when is_init == true + if *is_init { + break; + } + } + + let ret = query_instruments().await; + + if ret != 0 { + panic!("Error for init all instruments, please fix this problem!"); + }else{ + let mut is_ready = CTP_TD_READY.lock().await; + *is_ready = true; + + info!("init CTP trader completed, set CTP_TD_READY status to true"); + + let version = td_api::get_version(); + + info!("CTP trader client version is {}", version); + } +} + +fn register_push_event_handler(){ + + let _reg_err_ok = td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + //let event_str = serde_json::to_string(&rsp_info).unwrap(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: "todo".to_owned(), + name: "OnRspError", + payload: rsp_info + }; + + debug!("td_api::EventHandler::OnRspError: push_event {:?}", + event); + }); + + //OnRspOrderInsert是Order被交易所后台核心拒绝的响应,不算错误,只是订单的一个状态 + //交易所后台核心 —— 对收到的交易序列报文做合法性检查 + //检查出错误的交易申请报文后就会返回给 —— 交易所前置服务器1个包含错误信息的报单响应报文 + //交易所前置服务器 —— 立即将该报文信息转发给 —— CTP交易终端 + let _reg_order_insert_ok = td_api::EventHandler::OnRspOrderInsert(move |rsp_input_order, rsp_info| { + debug!("td_api::EventHandler::OnRspOrderInsert, rsp_input_order: {:?}", + rsp_input_order); + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnRspOrderInsert Error, ErrorID: {},ErrorMsg: {}", + rsp_info.ErrorID,rsp_info.ErrorMsg); + }else{ + //let event_str = serde_json::to_string(&rsp_input_order).unwrap(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + name: "OnRspOrderInsert", + topic: "todo".to_owned(), + payload: rsp_input_order + }; + + debug!("td_api::EventHandler::OnRspOrderInsert: push_event {:?}", + event); + } + + }); + + //报单录入错误响应 + //此接口仅在报单 —— 被CTP交易终端拒绝时 —— 被调用用来进行报错 + let _reg_err_order_insert_ok = td_api::EventHandler::OnErrRtnOrderInsert(move |rsp_input_order, rsp_info| { + debug!("td_api::EventHandler::OnErrRtnOrderInsert, rsp_input_order: {:?}", + rsp_input_order); + + if rsp_info.ErrorID !=0 { + error!("td_api::EventHandler::OnErrRtnOrderInsert Error, ErrorID: {},ErrorMsg: {}", + rsp_info.ErrorID,rsp_info.ErrorMsg); + }else{ + //let event_str = serde_json::to_string(&rsp_input_order).unwrap(); + + let event = PushEvent { + client: CTP_CLIENT_ID, + name: "OnErrRtnOrderInsert", + topic: "todo".to_owned(), + payload: rsp_input_order + }; + + debug!("td_api::EventHandler::OnErrRtnOrderInsert: push_event {:?}", + event); + } + }); + + let _reg_rsp_order_ok = td_api::EventHandler::OnRtnOrder(move |rsp_order| { + debug!("td_api::EventHandler::OnRtnOrder, rsp_order: {:?}", + rsp_order); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: "todo".to_owned(), + name: "OnRtnOrder", + payload: rsp_order + }; + + debug!("td_api::EventHandler::OnRtnOrder: push_event {:?}", + event); + }); + + let _reg_rsp_trade_ok = td_api::EventHandler::OnRtnTrade(move |rsp_trade| { + + debug!("td_api::EventHandler::OnRtnTrade, rsp_trade: {:?}",rsp_trade); + + let event = PushEvent { + client: CTP_CLIENT_ID, + topic: "todo".to_owned(), + name: "OnRtnTrade", + payload: rsp_trade + }; + + debug!("td_api::EventHandler::OnRtnTrade: push_event {:?}",event); + }); +} + +fn td_connect(){ + + td_api::EventHandler::OnFrontConnected(|| { + info!("td_api::EventHandler::OnFrontConnected,then authenticate"); + + while td_authenticate() != 0 { + error!("Rust authenticate failed, continue after 2s ..."); + thread::sleep(Duration::from_millis(2 * 1000)) + } + + debug!("Rust send authenticate req successfully."); + }); + + td_api::EventHandler::OnFrontDisconnected(|n_reason| { + info!("td_api::EventHandler::OnFrontDisconnected, reason: {}", + on_front_disconnected_reason(n_reason)); + + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut is_ready = rt.block_on(CTP_TD_READY.lock()); + *is_ready = false; + + warn!("Trader front disconnected, reset CTP_TD_READY status to false"); + }); + + td_api::connect(CTP_TRADE_FRONT_ADDR.get().unwrap()); +} + + +fn td_authenticate() ->i32 { + td_api::EventHandler::OnRspAuthenticate(|rsp_authenticate_field, rsp_info, n_request_id, b_is_last| { + + debug!("td_api::EventHandler::OnRspAuthenticate, UserID: {}, AppType: {},nRequestID: {},bIsLast: {}", + rsp_authenticate_field.UserID,rsp_authenticate_field.AppType,n_request_id,b_is_last); + + if rsp_info.ErrorID == 0 { + + while td_login() != 0 { + error!("td client send login req failed, continue after 2s ..."); + thread::sleep(Duration::from_millis(2 * 1000)) + } + + info!("td client send login req successfully. User_ID:{}, Broker_ID:{}", + CTP_USER_ID.get().unwrap(),CTP_BROKER_ID.get().unwrap()); + + }else{ + error!("td_api::EventHandler::OnRspAuthenticate, ErrorID: {},ErrorMsg: {}", + rsp_info.ErrorID,rsp_info.ErrorMsg); + } + }); + + td_api::authenticate(CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + CTP_APP_ID.get().unwrap(), + CTP_AUTH_CODE.get().unwrap(), + CTP_USER_PRODUCT_INFO.get().unwrap()) +} + +fn td_login() ->i32 { + td_api::EventHandler::OnRspUserLogin(|rsp_user_login, rsp_info, n_request_id, b_is_last| { + //登录完成,进行结算单确认 + if rsp_info.ErrorID == 0 { + info!("td_api::EventHandler::OnRspUserLogin successfully. TradingDay: {}, nRequestID{}, bIsLast {}", + rsp_user_login.TradingDay,n_request_id,b_is_last); + + while td_confirm_settlement_info() != 0 { + error!("td client send confirm_settlement_info req failed, continue after 2s ..."); + thread::sleep(Duration::from_millis(2 * 1000)) + } + + }else{ + error!("td_api::EventHandler::OnRspUserLogin Error, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + } + }); + + td_api::login(CTP_USER_ID.get().unwrap(), + CTP_USER_PASSWORD.get().unwrap(), + CTP_BROKER_ID.get().unwrap()) +} + +fn td_confirm_settlement_info() ->i32 { + + td_api::EventHandler::OnRspSettlementInfoConfirm(|rsp_settlement_info_confirm, rsp_info, n_request_id, b_is_last| { + + debug!("td_api::EventHandler::OnRspSettlementInfoConfirm, InvestorID: {}, ConfirmDate: {}, nRequestID: {},bIsLast: {}", + rsp_settlement_info_confirm.InvestorID, rsp_settlement_info_confirm.ConfirmDate, + n_request_id,b_is_last); + + if rsp_info.ErrorID != 0 { + error!("td client confirm_settlement_info Error, ErrorID: {},ErrorMsg: {}", + rsp_info.ErrorID, rsp_info.ErrorMsg); + }else{ + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut is_init = rt.block_on(CTP_TD_INIT.lock()); + *is_init = true; + info!("td client confirm settlement info successfully, set CTP_TD_INIT status to true, then init & query all instruments"); + } + }); + + td_api::confirm_settlement_info(CTP_USER_ID.get().unwrap(),CTP_BROKER_ID.get().unwrap()) +} + +pub async fn td_query_settlement_info(_req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + + let serde_result = query_settlement_info(CTP_USER_ID.get().unwrap(),CTP_BROKER_ID.get().unwrap()); + + let rsp_json = serde_result?; + + common_response(rsp_json).await + }else { + let rsp_result = RspResult{ + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} + +pub async fn td_query_instrument(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + let instrument_id = http_url_params(&req,"instrument_id"); + debug!("query_instrument instrument_id:{}",instrument_id); + + let instrument_map = CTP_INSTRUMENT_HASHMAP.lock().await; + let instrument_op = instrument_map.get(&instrument_id); + + let rsp_json = match instrument_op { + None => { + let rsp_result = RspResult{ + code: 1404, + msg: "NOT FOUNT".to_owned(), + result: "".to_string() + }; + + serde_json::to_string(&rsp_result)? + }, + Some(ref instrument)=>{ + + let rsp_result = RspResult{ + code: 1200, + msg: "".to_owned(), + result: instrument + }; + + serde_json::to_string(&rsp_result)? + } + }; + + common_response(rsp_json).await + }else{ + + let rsp_result = RspResult{ + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } + + +} + +pub async fn td_query_trading_account(_req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + let rsp_result = query_trading_account(CTP_USER_ID.get().unwrap(), CTP_BROKER_ID.get().unwrap()); + + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult{ + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } + + +} + +pub async fn td_query_investor_position(_req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + let rsp_result = query_investor_position(CTP_USER_ID.get().unwrap(), CTP_BROKER_ID.get().unwrap()); + + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult{ + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} + +pub async fn td_query_commission_rate(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + let instrument_id = http_url_params(&req,"instrument_id"); + debug!("query_commission_rate instrument_id:{}",instrument_id); + + let rsp_result = query_commission_rate(CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + instrument_id.as_str()); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} +///1.市价单 +pub async fn td_send_market_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendMarketOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_market_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_market_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} +///2.限价单 +pub async fn td_send_limit_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendLimitOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_limit_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_limit_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume, + req.limit_price); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} +///3.触价单 +pub async fn td_send_market_if_touched_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendMarketIfTouchedOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_market_if_touched_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_market_if_touched_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume, + req.stop_price_condition.as_str(), + req.stop_price); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + + common_response(rsp_json).await + } + +} + +///4.条件单 +pub async fn td_send_stop_limit_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendStopLimitOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_stop_limit_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_stop_limit_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume, + req.limit_price, + req.stop_price_condition.as_str(), + req.stop_price); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } + +} + +///5.FAK单 +pub async fn td_send_fill_and_kill_limit_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendFillAndKillLimitOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_fill_and_kill_limit_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_fill_and_kill_limit_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume, + req.volume_condition.as_str(), + req.limit_price); + let rsp_json = rsp_result?; + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} + +///6.FOK单 +pub async fn td_send_fill_or_kill_limit_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + let mut order_def_id = CTP_TD_ORDER_DEF_ID.lock().await; + *order_def_id = *order_def_id + 1; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: SendFillOrKillLimitOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_send_fill_or_kill_limit_order instrument_id:{}",req.instrument_id); + + let rsp_result = send_fill_or_kill_limit_order(*order_def_id, + CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.instrument_id.as_str(), + req.direction.as_str(), + req.open_close.as_str(), + req.volume, + req.limit_price); + let rsp_json = rsp_result?; + + common_response(rsp_json).await + + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} + +pub async fn td_cancel_order(req: Request) -> CTPResult>{ + + let is_ready = CTP_TD_READY.lock().await; + + if *is_ready { + + let full_body = hyper::body::to_bytes(req.into_body()).await?; + let body_str = String::from_utf8(full_body.to_vec())?; + let req: CancelOrderRequest = serde_json::from_str(&body_str)?; + debug!("td_cancel_order exchange_id:{},order_sys_id:{},cancel_action:{}", + req.exchange_id, + req.order_sys_id, + req.cancel_action); + + let rsp_result = cancel_order(CTP_USER_ID.get().unwrap(), + CTP_BROKER_ID.get().unwrap(), + req.exchange_id.as_str(), + req.order_sys_id.as_str(), + req.cancel_action.as_str()); + + let rsp_json = rsp_result?; + + common_response(rsp_json).await + }else{ + let rsp_result = RspResult { + code: 1100, + msg: "Trader Not Ready".to_owned(), + result: "".to_string() + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await + } +} \ No newline at end of file diff --git a/src/ctperror.rs b/src/ctperror.rs new file mode 100644 index 0000000000000000000000000000000000000000..d53d2a000047c662ff1d3a20fae2fc3b49815556 --- /dev/null +++ b/src/ctperror.rs @@ -0,0 +1,111 @@ +use serde::Serialize; + + +#[derive(Debug,Serialize)] +pub struct NewCTPError(pub String); + +///实现Display的trait,并实现fmt方法 +impl std::fmt::Display for NewCTPError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + //let err_json= serde_json::to_string(self).unwrap_or("{}".to_owned()); + write!(f, "{}",self.0) + } +} + +///实现Error的trait,因为没有子Error,不需要覆盖source()方法 +impl std::error::Error for NewCTPError {} + +pub type CTPResult = std::result::Result; + +//借助From Trait, 把T当初第三方函数Error,就可以统一映射到enum CTPError中 +//通过?返回第三方函数Error并且自动转换 +#[derive(Debug)] +pub enum CTPError { + SerdeJsonError(serde_json::error::Error), + HttpError(http::Error), + HyperError(hyper::Error), + FromUtf8Error(std::string::FromUtf8Error), + MpscRecvError(std::sync::mpsc::RecvError), + CTPOk(NewCTPError), + CTPConnectionError(NewCTPError), + CTPUnprocessedRequestError(NewCTPError), + CTPLimitRequestError(NewCTPError), + CTPQueryFrequentError(NewCTPError), + CTPUnknowError(NewCTPError), + CTPOnRspError(NewCTPError), + CTPClientNotReadyError(NewCTPError), +} + +impl std::error::Error for CTPError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self { + CTPError::SerdeJsonError(ref e) => Some(e), + CTPError::HttpError(ref e) => Some(e), + CTPError::HyperError(ref e)=>Some(e), + CTPError::FromUtf8Error(ref e)=>Some(e), + CTPError::MpscRecvError(ref e)=>Some(e), + + CTPError::CTPOk(ref e) =>Some(e), + CTPError::CTPConnectionError(ref e) =>Some(e), + CTPError::CTPUnprocessedRequestError(ref e) =>Some(e), + CTPError::CTPLimitRequestError(ref e)=>Some(e), + CTPError::CTPQueryFrequentError(ref e) => Some(e), + CTPError::CTPUnknowError(ref e)=>Some(e), + CTPError::CTPOnRspError(ref e)=>Some(e), + CTPError::CTPClientNotReadyError(ref e)=>Some(e), + } + } +} + +impl std::fmt::Display for CTPError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + + match &self { + CTPError::SerdeJsonError(ref e) => e.fmt(f), + CTPError::HttpError(ref e) => e.fmt(f), + CTPError::HyperError(ref e)=>e.fmt(f), + CTPError::FromUtf8Error(ref e)=>e.fmt(f), + CTPError::MpscRecvError(ref e)=>e.fmt(f), + CTPError::CTPOk(ref e) =>e.fmt(f), + CTPError::CTPConnectionError(ref e) =>e.fmt(f), + CTPError::CTPUnprocessedRequestError(ref e) =>e.fmt(f), + CTPError::CTPLimitRequestError(ref e)=>e.fmt(f), + CTPError::CTPQueryFrequentError(ref e) => e.fmt(f), + CTPError::CTPUnknowError(ref e)=>e.fmt(f), + CTPError::CTPOnRspError(ref e)=>e.fmt(f), + CTPError::CTPClientNotReadyError(ref e)=>e.fmt(f), + } + } +} + +impl From for CTPError { + fn from(e: serde_json::error::Error) -> Self { + CTPError::SerdeJsonError(e) + } +} + +impl From for CTPError { + fn from(e: http::Error) -> Self { + CTPError::HttpError(e) + } +} + +impl From for CTPError { + fn from(e: hyper::Error) -> Self { + CTPError::HyperError(e) + } +} + +impl From for CTPError { + fn from(e: std::string::FromUtf8Error) -> Self { + CTPError::FromUtf8Error(e) + } +} + +impl From for CTPError { + fn from(e: std::sync::mpsc::RecvError) -> Self { + CTPError::MpscRecvError(e) + } +} + + diff --git a/src/dapr/component_client.rs b/src/dapr/component_client.rs index 027525c251c2f291a96f08b858296e8db550de1c..84a0e2f26db86e6e4107736c67b845c253bbebbc 100644 --- a/src/dapr/component_client.rs +++ b/src/dapr/component_client.rs @@ -2,19 +2,19 @@ use hyper::Client; use hyper::{Body, Method, Request,Response}; use std::env; -use crate::utils::ctperror::{GenericError}; +use crate::ctperror::CTPResult; static DAPR_STATESTORE_NAME:&str = "statestore"; static DAPR_PUBSUB_NAME:&str = "pubsub"; -pub async fn fetch_state_from_statestore(state_key:String) -> Result,GenericError> { +pub async fn fetch_state_from_statestore(state_key:String) -> CTPResult> { let client = Client::new(); - let dapr_host = env::var("DARP_HTTP_HOST").unwrap_or("localhost".to_string()); + let dapr_host = env::var("DAPR_HTTP_HOST").unwrap_or("localhost".to_string()); let dapr_port = env::var("DAPR_HTTP_PORT").unwrap_or("3500".to_string()); let dapr_state_url = format!("http://{}:{}/v1.0/state/{}/{}",dapr_host,dapr_port,DAPR_STATESTORE_NAME,state_key); - //println!("dapr_state_url:{}",dapr_state_url); + debug!("fetch_state_from_statestore, url:{}",dapr_state_url); let req = Request::builder() .method(Method::GET) @@ -22,32 +22,23 @@ pub async fn fetch_state_from_statestore(state_key:String) -> Result { - return Ok(r); - }, - Err(e) =>{ - return Err(Box::new(e)); - } - } + Ok(client.request(req).await?) } /// 参数2: json_value 必须是json格式字符串 -pub async fn save_state_to_statestore(key: String,json_value:String) -> Result,GenericError> { +pub async fn save_state_to_statestore(key: String,json_value:String) -> CTPResult> { let client = Client::new(); - let dapr_host = env::var("DARP_HTTP_HOST").unwrap_or("localhost".to_string()); + let dapr_host = env::var("DAPR_HTTP_HOST").unwrap_or("localhost".to_string()); let dapr_port = env::var("DAPR_HTTP_PORT").unwrap_or("3500".to_string()); let dapr_state_url = format!("http://{}:{}/v1.0/state/{}",dapr_host,dapr_port,DAPR_STATESTORE_NAME); - //println!("dapr_state_url:{}",dapr_state_url); + debug!("save_state_to_statestore, url:{}",dapr_state_url); let dapr_state_json = format!("[{{\"key\":\"{}\",\"value\": {}}}]", key,json_value); - //println!("dapr_state_json:{}",dapr_state_json); + debug!("save_state_to_statestore, json:{}",dapr_state_json); let req = Request::builder() .method(Method::POST) @@ -55,26 +46,17 @@ pub async fn save_state_to_statestore(key: String,json_value:String) -> Result { - return Ok(r); - }, - Err(e) =>{ - return Err(Box::new(e)); - } - } + Ok(client.request(req).await?) } -pub async fn pub_event_to_topic(topic: String,event_json:String) -> Result,GenericError> { +pub async fn pub_event_to_topic(topic: String,event_json:String) -> CTPResult> { let client = Client::new(); - let dapr_host = env::var("DARP_HTTP_HOST").unwrap_or("localhost".to_string()); + let dapr_host = env::var("DAPR_HTTP_HOST").unwrap_or("localhost".to_string()); let dapr_port = env::var("DAPR_HTTP_PORT").unwrap_or("3500".to_string()); let dapr_pub_url = format!("http://{}:{}/v1.0/publish/{}/{}",dapr_host,dapr_port,DAPR_PUBSUB_NAME,topic); - //println!("dapr_pub_url:{}",dapr_pub_url); + debug!("pub_event_to_topic, url:{}",dapr_pub_url); let req = Request::builder() .method(Method::POST) @@ -82,18 +64,6 @@ pub async fn pub_event_to_topic(topic: String,event_json:String) -> Result { - return Ok(r); - }, - Err(e) =>{ - - eprintln!("pub_event_to_topic Error:{}",e); - - return Err(Box::new(e)); - } - } + Ok(client.request(req).await?) } diff --git a/src/main.rs b/src/main.rs index 5d6202aaac731f195512d796e750e84b3aa0a9d0..94861dbfb14d45ebe16c6e336335fbbc9febd8ff 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +#[macro_use] +extern crate log; + +mod ctperror; mod ctp_client; mod routes; mod request; @@ -5,7 +9,6 @@ mod result; mod utils; mod dapr; -use ctp_client::mdcontroller; use std::convert::Infallible; use std::net::SocketAddr; use hyper::{Server}; @@ -20,7 +23,11 @@ async fn shutdown_signal() { #[tokio::main] async fn main() { - mdcontroller::init().await; + env_logger::init(); + + debug!("start; init CTP"); + ctp_client::init().await; + debug!("end; init CTP"); // We'll bind to 127.0.0.1:5000 let addr_str = "0.0.0.0:5000"; @@ -36,13 +43,13 @@ async fn main() { let server = Server::bind(&addr).serve(make_svc); - println!("Listening on http://{}", addr); + info!("Listening on http://{}", addr); // And now add a graceful shutdown signal... let graceful = server.with_graceful_shutdown(shutdown_signal()); // Run this server for... forever! if let Err(e) = graceful.await { - eprintln!("server error: {}", e); + error!("server error: {}", e); } } diff --git a/src/request.rs b/src/request.rs index 45f08f42de9adca04e9ce6cd4bf996e57a58e7ad..5894bf5b3983f48f1c1e5b0b9ef92179f1cb27ed 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,3 +5,74 @@ pub struct SubscribeRequest { pub instrument_ids: Vec, } +#[derive(Serialize,Deserialize, Debug)] +pub struct SendMarketOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32 +} + +#[derive(Serialize,Deserialize, Debug)] +pub struct SendLimitOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32, + pub limit_price: f64, +} + +#[derive(Serialize,Deserialize, Debug)] +pub struct SendMarketIfTouchedOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32, + pub stop_price_condition:String, + pub stop_price: f64,//自动卖出单 +} + +#[derive(Serialize,Deserialize, Debug)] +pub struct SendStopLimitOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32, + pub limit_price: f64,//触发买入单 + pub stop_price_condition:String, + pub stop_price: f64,//自动卖出单 +} + +#[derive(Serialize,Deserialize, Debug)] +pub struct SendFillAndKillLimitOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32, + pub volume_condition: String, + pub limit_price: f64//触发买入单 +} + +#[derive(Serialize,Deserialize, Debug)] +pub struct SendFillOrKillLimitOrderRequest { + pub instrument_id: String, + pub direction: String, + pub open_close: String, + pub volume: i32, + pub limit_price: f64 +} +#[derive(Serialize,Deserialize, Debug)] +pub struct CancelOrderRequest { + pub exchange_id: String, + pub order_sys_id: String, + pub cancel_action: String +} +// pub struct CancelOrderRequest { +// pub instrument_id: String, +// pub front_id: i32, +// pub session_id: i32, +// pub order_def_id: String, +// pub exchange_id: String, +// pub order_sys_id: String, +// pub cancel_action: String +// } diff --git a/src/result.rs b/src/result.rs index fac4779cda3cb7d5cacec485de3ba683917375a2..5d3f08ae6fdff83b3dd66084e7b72d62e971f1d6 100644 --- a/src/result.rs +++ b/src/result.rs @@ -3,7 +3,7 @@ use serde::{Serialize}; #[derive(Serialize)] pub struct RspResult{ - pub code: i64, + pub code: i32, pub msg: String, pub result: T } diff --git a/src/routes.rs b/src/routes.rs index e7aefff7f1693524413da03574596e665a903c19..06887af9bdc49d146d1e852a05736a3ff159411f 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -3,6 +3,9 @@ use http::header::HeaderValue; use std::convert::Infallible; use crate::ctp_client::mdcontroller; +use crate::ctp_client::tdcontroller; + +use crate::ctperror::CTPError; pub async fn mdroutes(req: Request) -> Result, Infallible> { @@ -20,22 +23,62 @@ pub async fn mdroutes(req: Request) -> Result, Infallible> }, (&Method::PUT, "/api/v1/subscribe") => { mdcontroller::subscribe(req).await - } + }, (&Method::PUT, "/api/v1/unsubscribe") => { - Ok(Response::new(req.into_body())) - } + mdcontroller::unsubscribe(req).await + //Ok(Response::new(req.into_body())) + }, + (&Method::GET, "/api/v1/query_settlement_info") => { + tdcontroller::td_query_settlement_info(req).await + }, + (&Method::GET, "/api/v1/query_trading_account") => { + tdcontroller::td_query_trading_account(req).await + }, + (&Method::GET, "/api/v1/query_investor_position") => { + tdcontroller::td_query_investor_position(req).await + }, + (&Method::GET, "/api/v1/query_commission_rate") => { + tdcontroller::td_query_commission_rate(req).await + }, + (&Method::GET, "/api/v1/query_instrument") => { + tdcontroller::td_query_instrument(req).await + }, + (&Method::POST, "/api/v1/send_market_order") => { + tdcontroller::td_send_market_order(req).await + }, + (&Method::POST, "/api/v1/send_limit_order") => { + tdcontroller::td_send_limit_order(req).await + }, + (&Method::POST, "/api/v1/send_market_if_touched_order") => { + tdcontroller::td_send_market_if_touched_order(req).await + }, + (&Method::POST, "/api/v1/send_stop_limit_order") => { + tdcontroller::td_send_stop_limit_order(req).await + }, + (&Method::POST, "/api/v1/send_fill_and_kill_limit_order") => { + tdcontroller::td_send_fill_and_kill_limit_order(req).await + }, + (&Method::POST, "/api/v1/send_fill_or_kill_limit_order") => { + tdcontroller::td_send_fill_or_kill_limit_order(req).await + }, + (&Method::PUT, "/api/v1/cancel_order") => { + tdcontroller::td_cancel_order(req).await + }, // Return the 404 Not Found for other routes. _ => { - let mut not_found = Response::default(); - not_found.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); - - let error_str = String::from("{\"code\": 1404,\"message\": \"not found this route\",\"result\":{}"); - - *not_found.body_mut() = Body::from(error_str); + let error_json_str = r#" + { + "code": 1404, + "message": "not found this route", + "result": {} + }"#; + let mut not_found = Response::default(); + not_found.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); + *not_found.body_mut() = Body::from(error_json_str.to_string()); *not_found.status_mut() = StatusCode::NOT_FOUND; Ok(not_found) } @@ -44,19 +87,61 @@ pub async fn mdroutes(req: Request) -> Result, Infallible> //make sure routes return is Infallible match res_result { Ok(response) => Ok(response), - Err(error) => { - println!("ERROR: {:?}",error); + Err(ref ctp_error) => { + error!("CTP API ERROR: {}",ctp_error); + + let error_code; + + match ctp_error { + + CTPError::CTPOk(ref _e) =>{ + error_code = 0 + }, + CTPError::SerdeJsonError(ref _e) => { + error_code = 4000 + }, + CTPError::HttpError(ref _e) => { + error_code = 4001 + }, + CTPError::HyperError(ref _e)=>{ + error_code = 4002 + }, + CTPError::FromUtf8Error(ref _e)=>{ + error_code = 4003 + }, + CTPError::CTPUnknowError(ref _e)=>{ + error_code = 4004 + }, + CTPError::CTPConnectionError(ref _e) =>{ + error_code = 4005 + }, + CTPError::CTPUnprocessedRequestError(ref _e) =>{ + error_code = 4006 + }, + CTPError::CTPLimitRequestError(ref _e)=>{ + error_code = 4007 + }, + CTPError::CTPQueryFrequentError(ref _e)=>{ + error_code = 4008 + }, + CTPError::CTPOnRspError(ref _e) => { + error_code = 4009 + }, + CTPError::MpscRecvError(ref _e) => { + error_code = 4010 + }, + CTPError::CTPClientNotReadyError(ref _e)=>{ + error_code = 4011 + }, + } + + let error_json = format!(r#"{{"code": {}, "message": "{}","result": ""}}"#, error_code,ctp_error); let mut response = Response::new(Body::empty()); response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); - let error_str_start = String::from("{\"code\": 1500,\"message\""); - let error_str_empty_result = String::from("\"result\":{}"); - let error_str_end = String::from("}"); - let error_str = format!("{}: \"{}\",{}{}", error_str_start, error.to_string(), error_str_empty_result,error_str_end); - - *response.body_mut() = Body::from(error_str); + *response.body_mut() = Body::from(error_json); *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; diff --git a/src/rust_ctp_client/librustctpclient.so b/src/rust_ctp_client/librustctpclient.so deleted file mode 100755 index ec52e300f954013e191d1ba3713e644853633152..0000000000000000000000000000000000000000 Binary files a/src/rust_ctp_client/librustctpclient.so and /dev/null differ diff --git a/src/rust_ctp_client/librustmdclient.so b/src/rust_ctp_client/librustmdclient.so new file mode 100755 index 0000000000000000000000000000000000000000..38cd5f4ca2d15e8188f47bffb171ec3edc52f471 Binary files /dev/null and b/src/rust_ctp_client/librustmdclient.so differ diff --git a/src/rust_ctp_client/librusttdclient.so b/src/rust_ctp_client/librusttdclient.so new file mode 100755 index 0000000000000000000000000000000000000000..031c0873c6b2bac2dfe7be8ab6636d7382c6c45b Binary files /dev/null and b/src/rust_ctp_client/librusttdclient.so differ diff --git a/src/rust_ctp_client/libthosttraderapi_se.so b/src/rust_ctp_client/libthosttraderapi_se.so new file mode 100755 index 0000000000000000000000000000000000000000..e534853df81fb5cdd4b6f0fbf643b4be579c23fc Binary files /dev/null and b/src/rust_ctp_client/libthosttraderapi_se.so differ diff --git a/src/utils/ctperror.rs b/src/utils/ctperror.rs deleted file mode 100644 index 81fef43f81bd09c01144f306b901a95cad3d53ce..0000000000000000000000000000000000000000 --- a/src/utils/ctperror.rs +++ /dev/null @@ -1,46 +0,0 @@ -//------------------自定义CTPError----------- -use hyper::{header,Body, Response, StatusCode}; - -use super::super::result::RspResult; -use super::super::result::EmptyResult; - -use std::error::Error; -use serde::{Serialize}; - - -pub type GenericError = Box; - -#[derive(Debug,Serialize)] -pub struct CTPError{ - pub code: i32, - pub msg: String -} - -///实现Display的trait,并实现fmt方法 -impl std::fmt::Display for CTPError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let err_json= serde_json::to_string(self).unwrap_or("{}".to_owned()); - write!(f, "{}",err_json) - } -} - -///实现Error的trait,因为没有子Error,不需要覆盖source()方法 -impl Error for CTPError {} - -//not adapt Struct defined in request -pub async fn unprocessable_request() -> Result,GenericError> { - let rsp_result = RspResult{ - code: 1422, - msg: "Request Error: Unprocessable entity, please check.".to_owned(), - result: EmptyResult{} - }; - - let json = serde_json::to_string(&rsp_result)?; - - let response = Response::builder() - .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(json))?; - - Ok(response) -} \ No newline at end of file diff --git a/src/utils/ctputil.rs b/src/utils/ctputil.rs new file mode 100644 index 0000000000000000000000000000000000000000..cefab67e30c9a2db88f27d1fed9c26235613667d --- /dev/null +++ b/src/utils/ctputil.rs @@ -0,0 +1,104 @@ +//------------------自定义CTPError----------- +use hyper::{header,Body, Response, StatusCode}; + +use rust_ctp::td_api; +use crate::ctp_client::CTP_CLIENT_ID; +use crate::ctperror::CTPResult; +use super::super::result::RspResult; +use super::super::result::EmptyResult; + +use std::{env}; + +use crate::ctp_client::{PushEvent,PushEventSinker}; +use crate::ctperror::{NewCTPError,CTPError}; + +//not adapt Struct defined in request +pub async fn unprocessable_request() -> CTPResult> { + let rsp_result = RspResult{ + code: 1422, + msg: "Request Error: Unprocessable entity, please check.".to_owned(), + result: EmptyResult{} + }; + + let rsp_json = serde_json::to_string(&rsp_result)?; + + common_response(rsp_json).await +} + +pub async fn common_response(rsp_json: String) -> CTPResult> { + let response = Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from(rsp_json))?; + + Ok(response) +} + +///@param nReason 错误原因 +///#4097 网络读失败 +///#4098 网络写失败 +///#8193 接收心跳超时 +///#8194 发送心跳失败 +///#8195 收到错误报文 +pub fn on_front_disconnected_reason(n_reason: i32)-> String { + match n_reason { + 4097 => "网络读失败".to_string(), + 4098 => "网络写失败".to_string(), + 8193 => "接收心跳超时".to_string(), + 8194 => "发送心跳失败".to_string(), + 8195 => "收到错误报文".to_string(), + _ => "未知错误码".to_string(), + } +} + +///#0,代表成功 +///#-1,表示网络连接失败 +///#-2,表示未处理请求超过许可数 +///#-3,表示每秒发送请求数超过许可数 +pub fn api_return_code(ret: i32)-> CTPError { + match ret { + 0 => CTPError::CTPOk(NewCTPError("成功".to_owned())),//"成功".to_string(), + -1 => CTPError::CTPConnectionError(NewCTPError("网络连接失败".to_owned())),//"网络连接失败".to_string(), + -2 => CTPError::CTPUnprocessedRequestError(NewCTPError("未处理请求超过许可数".to_owned())),//"未处理请求超过许可数".to_string(), + -3 => CTPError::CTPLimitRequestError(NewCTPError("每秒发送请求数超过许可数".to_owned())),//"每秒发送请求数超过许可数".to_string(), + _ => CTPError::CTPUnknowError(NewCTPError("未知错误码".to_owned())),//"未知错误码".to_string(), + } +} + +pub fn recover_on_error_push_event_handler() { + td_api::EventHandler::UnRspError(); + + let _reg_err_ok = td_api::EventHandler::OnRspError(move |rsp_info, n_request_id, b_is_last| { + error!("td_api::EventHandler::OnRspError, ErrorMsg: {}, ErrorID: {}, nRequestID: {}, bIsLast: {}", + rsp_info.ErrorMsg,rsp_info.ErrorID, + n_request_id,b_is_last); + + //let event_str = serde_json::to_string(&rsp_info).unwrap(); + + + let error_event_topic = get_error_event_topic(); + + let error_event = PushEvent { + client: CTP_CLIENT_ID, + topic: error_event_topic, + name: "OnRspError", + payload: rsp_info + }; + + debug!("td_api::EventHandler::OnRspError: push error event {:?}",error_event); + + let event_sinker = PushEventSinker::new(); + event_sinker.sink(error_event); + + }); +} + +pub fn get_error_event_topic() -> String { + let nq_bot_id = env::var("NQ_BOT_ID").expect("Ops,Error for Env Var NQ_BOT_ID"); + format!("CTP_ERROR-{}",nq_bot_id) +} + +pub fn get_depth_market_data_topic() -> String { + let nq_bot_id = env::var("NQ_BOT_ID").expect("Ops,Error for Env Var NQ_BOT_ID"); + format!("CTP-MD-{}",nq_bot_id) +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4a6b460c3c7591e252ac48d39065ece898a4ff3c..902f7d84b7e09e27d2ceaf868cd9284770a2fd03 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,20 @@ -pub mod util; -pub mod ctperror; \ No newline at end of file +pub mod ctputil; + +use std::collections::HashMap; +use hyper::{Body,Request}; + +pub fn http_url_params(req: &Request,key:&str) -> String { + let params: HashMap = req + .uri() + .query() + .map(|v| { + url::form_urlencoded::parse(v.as_bytes()) + .into_owned() + .collect() + }) + .unwrap_or_else(HashMap::new); + + let empty_value = "".to_string(); + let value = params.get(key).unwrap_or(&empty_value); + return value.to_string(); +} \ No newline at end of file diff --git a/src/utils/util.rs b/src/utils/util.rs deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000