├── README.md ├── ctp.json ├── daemonvt.sh ├── ftsMain.py ├── ftsVob ├── __init__.py ├── errorHandler │ ├── __init__.py │ └── default_error_handler.py ├── logHandler │ ├── __init__.py │ └── default_handler.py ├── quantAlgo │ ├── __init__.py │ └── quant_algo.py ├── quantEngine │ ├── __init__.py │ ├── event_engine.py │ ├── main_engine.py │ └── push_engine │ │ ├── __init__.py │ │ ├── account_info_engine.py │ │ ├── base_engine.py │ │ ├── clock_engine.py │ │ └── quotation_engine.py ├── quantGateway │ ├── __init__.py │ ├── api.py │ ├── ctpGateway │ │ ├── __init__.py │ │ ├── ctpDataType.py │ │ └── ctpGateway.py │ ├── quant_constant.py │ └── quant_gateway.py └── quantStrategy │ ├── __init__.py │ └── strategyTemplate.py └── strategies ├── __init__.py └── demo.py /README.md: -------------------------------------------------------------------------------- 1 | Daemon program of ftsVob (Future Trade System Vob) 2 | Author: Occ 3 | Guide: 4 | * Server trade type system type, has no graphic user interface 5 | * Platform is build on ubuntu14.0+ and python2.7+ 6 | * python ftsMain.py is will starting whole trade system, and will configure ctp.json under current directory 7 | * Need combile vnctpmd.so,vnctptd.so on your own platform and install in ftsVob/quantGateway/ctpGateway directory 8 | 9 | ### Program whole picture 10 | 11 | * [strategies] 12 | * [demo.py] 13 | * [__init__.py] 14 | * [daemonvt.sh] 15 | * [ctp.json] 16 | * [ftsVob] 17 | * [quantEngine] 18 | * [event_engine.py] 19 | * [push_engine] 20 | * [account_info_engine.py] 21 | * [base_engine.py] 22 | * [clock_engine.py] 23 | * [__init__.py] 24 | * [quotation_engine.py] 25 | * [main_engine.py] 26 | * [__init__.py] 27 | * [quantGateway] 28 | * [ctpGateway] 29 | * [ctpGateway.py] 30 | * [vnctpmd.so] 31 | * [vnctptd.so] 32 | * [ctpDataType.py] 33 | * [__init__.py] 34 | * [quant_gateway.py] 35 | * [api.py] 36 | * [__init__.py] 37 | * [quant_constant.py] 38 | * [logHandler] 39 | * [default_handler.py] 40 | * [__init__.py] 41 | * [quantStrategy] 42 | * [__init__.py] 43 | * [strategyTemplate.py] 44 | * [databaseSys] 45 | * [__init__.py] 46 | * [ftsMain.py] 47 | 48 | ### Develop plan 49 | * Improve log handler system need fix some bugs of log system 50 | * Adding database api, such as mysql, mongodb, redis 51 | * Adding trading interface which is needed by concrete strategy 52 | 53 | -------------------------------------------------------------------------------- /ctp.json: -------------------------------------------------------------------------------- 1 | { 2 | "brokerID": 期货公司代码, 3 | "tdAddress": "tcp://tdIP+port", 4 | "password": 密码, 5 | "mdAddress": "tcp://mdIP+port", 6 | "userID": 客户号 7 | } 8 | 9 | -------------------------------------------------------------------------------- /daemonvt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPTNAME=daemonvt.sh 3 | PIDFILE=fts.pid 4 | PYTHONCMD=/usr/bin/python 5 | do_start() { 6 | $PYTHONCMD ftsMain.py 7 | } 8 | do_stop() { 9 | kill `cat $PIDFILE` || echo -n "fts not running" 10 | } 11 | case "$1" in 12 | start) 13 | do_start 14 | ;; 15 | stop) 16 | do_stop 17 | ;; 18 | restart) 19 | do_stop 20 | do_start 21 | ;; 22 | *) 23 | echo "Usage: $SCRIPTNAME {start|stop||restart}" >&2 24 | exit 3 25 | ;; 26 | esac 27 | exit 28 | -------------------------------------------------------------------------------- /ftsMain.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | import os 4 | import ftsVob 5 | 6 | def main(): 7 | """期货交易系统的主程序入口""" 8 | """系统平台为Ubuntu 16.0+,python2.7+""" 9 | """后期可能需要迁移到python3.0+""" 10 | 11 | #暂时使用写定的gateway接口之后可能需要配置文件 12 | gatewayname = 'CTP' 13 | gatewayconfig = 'ctp.json' 14 | 15 | #输出程序pid 16 | with open('fts.pid', 'w') as f: 17 | f.write(str(os.getpid())) 18 | m = ftsVob.MainEngine(gatewayname, gatewayconfig) 19 | 20 | #加载策略 21 | m.load_strategy() 22 | 23 | #启动主引擎 24 | m.start() 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /ftsVob/__init__.py: -------------------------------------------------------------------------------- 1 | from .quantEngine import MainEngine 2 | from .quantStrategy import StrategyTemplate 3 | from .logHandler import DefaultLogHandler 4 | from .quantAlgo import AlgoTrade 5 | -------------------------------------------------------------------------------- /ftsVob/errorHandler/__init__.py: -------------------------------------------------------------------------------- 1 | from .default_error_handler import ErrorHandler 2 | -------------------------------------------------------------------------------- /ftsVob/errorHandler/default_error_handler.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | """ 4 | ftsVob的错误处理类 5 | 根据错误代码提供封装的回调函数 6 | """ 7 | 8 | class ErrorHandler(object): 9 | """错误处理句柄""" 10 | def __init__(self, log=None): 11 | self.log = log 12 | self.value_call = { 13 | 31: self.err_lack_capital, 14 | 15: self.err_field, 15 | 90: self.err_search_wait 16 | } 17 | 18 | def process_error(self, event): 19 | """注册函数""" 20 | err = event.data 21 | self.value_call.get(err.errorID)(event) 22 | 23 | def err_lack_capital(self, event): 24 | self.log.info(event.data.errorMsg) 25 | 26 | def err_field(self, event): 27 | self.log.info(event.data.errorMsg) 28 | 29 | def err_search_wait(self, event): 30 | self.log.info(event.data.errorMsg) 31 | 32 | 33 | -------------------------------------------------------------------------------- /ftsVob/logHandler/__init__.py: -------------------------------------------------------------------------------- 1 | from .default_handler import DefaultLogHandler 2 | -------------------------------------------------------------------------------- /ftsVob/logHandler/default_handler.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import logging 3 | 4 | class DefaultLogHandler(object): 5 | """默认的Log类基础系统支持类""" 6 | def __init__(self, name='mylog', filepath='default.log', log_type='stdout', log_level='DEBUG'): 7 | self.logger = logging.getLogger(name) 8 | self.loglevel = {'CRITICAL': logging.CRITICAL, 9 | 'ERROR': logging.ERROR, 10 | 'WARNING': logging.WARNING, 11 | 'INFO': logging.INFO, 12 | 'DEBUG': logging.DEBUG} 13 | 14 | self.logger.setLevel(self.loglevel[log_level]) 15 | fmt = logging.Formatter(fmt='%(asctime)s: %(levelname)s %(message)s', 16 | datefmt='%Y-%m-%d %H:%M:%S') 17 | 18 | if log_type == 'stdout': 19 | # 创建一个handler,用于标准输出 20 | ch = logging.StreamHandler() 21 | ch.setFormatter(fmt) 22 | self.logger.addHandler(ch) 23 | if log_type == 'file': 24 | # 创建一个handler,用于输出日志文件 25 | fh = logging.FileHandler(filepath) 26 | fh.setFormatter(fmt) 27 | self.logger.addHandler(fh) 28 | 29 | def info(self, *args, **kwargs): 30 | self.logger.info(*args, **kwargs) 31 | 32 | def debug(self, *args, **kwargs): 33 | self.logger.debug(*args, **kwargs) 34 | 35 | def warn(self, *args, **kwargs): 36 | self.logger.warning(*args, **kwargs) 37 | 38 | def critical(self, *args, **kwargs): 39 | self.logger.critical(*args, **kwargs) 40 | 41 | def error(self, *args, **kwargs): 42 | self.logger.error(*args, **kwargs) 43 | 44 | -------------------------------------------------------------------------------- /ftsVob/quantAlgo/__init__.py: -------------------------------------------------------------------------------- 1 | from .quant_algo import AlgoTrade 2 | -------------------------------------------------------------------------------- /ftsVob/quantAlgo/quant_algo.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import datetime 3 | import time 4 | from threading import Thread 5 | from ..quantGateway.quant_constant import * 6 | from ..quantGateway.quant_gateway import * 7 | from ..logHandler import DefaultLogHandler 8 | from ..errorHandler import ErrorHandler 9 | 10 | """ 11 | GateWay的上层封装 12 | 这里实现了一些算法交易的类 13 | Engine层和Strategy层只和算法交易层交互 14 | """ 15 | 16 | class AlgoTrade(object): 17 | """算法交易接口""" 18 | def __init__(self, gateWay, eventEngine): 19 | """Constructor""" 20 | self.gateway = gateWay 21 | self.eventengine = eventEngine 22 | self.log = self.log_handler() 23 | 24 | #错误处理的log可以选择gateway的log,默认选择Algo的log系统 25 | self.err = ErrorHandler(log=self.log) 26 | 27 | #处理多合约这里设计为一个二级字典 28 | #{'symbol':{'orderID': orderObj}} 29 | self.orderinfo = {} 30 | self.register() 31 | 32 | def twap(self, size, reqobj, price=0, interval=1, maxtimeout=60): 33 | self.twap_thread = Thread(target=self.twap_callback, args=(size, reqobj, price, interval, maxtimeout)) 34 | self.twap_thread.start() 35 | 36 | def vwap(self, size, reqobj, price=0, interval=1, maxtimeout=60): 37 | self.vwap_thread = Thread(target=self.vwap_callback, args=(size, reqobj, price, interval, maxtimeout)) 38 | self.vwap_thread.start() 39 | 40 | def twap_callback(self, size, reqobj, price, interval, maxtimeout): 41 | """Time Weighted Average Price 42 | 每次以线程模式调用 43 | @size: 小单规模 44 | @reqobj: 发单请求 45 | @price: 下单价格,默认为0表示按照bid1下单 46 | @interval: 时间结果,每个size的时间间隔 47 | @maxtimeout: 最长等待时间,超时则按照ask1扫单 48 | """ 49 | volume = reqobj.volume 50 | starttime = datetime.datetime.now() 51 | status_send_order = {'timeout':False, 'success':False} 52 | 53 | while(True): 54 | if volume % size > 0: 55 | count = volume // size + 1 56 | else: 57 | count = volume // size 58 | 59 | #获取合约的即时价格 60 | if reqobj.symbol in self.gateway.tickdata: 61 | rb_data = self.gateway.tickdata[reqobj.symbol].tolist()[-1] 62 | price = rb_data.bidPrice1 63 | 64 | for i in range(count): 65 | if i == count-1: 66 | reqobj.volume = (volume - (i+1)*size) 67 | reqobj.price = price 68 | self.gateway.sendOrder(reqobj) 69 | else: 70 | reqobj.volume = size 71 | reqobj.price = price 72 | self.gateway.sendOrder(reqobj) 73 | time.sleep(interval) 74 | 75 | #检查Order信息表,准备撤单 76 | remain_volume = 0 77 | 78 | #获取合约订单 79 | try: 80 | contract = self.orderinfo[reqobj.symbol] 81 | except KeyError: 82 | self.log.error(u'未获取合约交易信息请检查日志TWAP线程终止') 83 | return 84 | 85 | for elt in contrace: 86 | #遍历订单 87 | of = contract[elt] 88 | if of.status != STATUS_ALLTRADED: 89 | cancel_obj = VtCancelOrderReq() 90 | cancel_obj.symbol = of.symbol 91 | cancel_obj.exchange = of.exchange 92 | cancel_obj.orderID = of.orderID 93 | cancel_obj.frontID = of.frontID 94 | cancel_obj.sessionID = of.sessionID 95 | self.gateway.cancelOrder(cancel_obj) 96 | remain_volume += (of.totalVolume - of.tradedVolume) 97 | 98 | if remain_volume == 0: 99 | status_send_order['success'] = True 100 | status_send_order['timeout'] = False 101 | break 102 | 103 | #记录剩余合约量,准备下一轮下单 104 | volume = remain_volume 105 | endtime = datetime.datetime.now() 106 | if (endtime - starttime).seconds > maxtimeout: 107 | status_send_order['timeout'] = True 108 | status_send_order['success'] = False 109 | break 110 | 111 | #剩余单数以对手价格下单 112 | if status_send_order['timeout'] == True and status_send_order['success'] == False: 113 | rb_data = self.gateway.tickdata[reqobj.symbol].tolist()[-1] 114 | price = rb_data.AskPrice1 115 | reqobj.price = price 116 | reqobj.volume = volume 117 | self.gateway.sendOrder(reqobj) 118 | 119 | def get_order_info_callback(self, event): 120 | if event.data.symbol in orderinfo: 121 | orderinfo[event.data.symbol][event.data.orderID] = event.data 122 | else: 123 | orderinfo[event.data.symbol] = dict() 124 | orderinfo[event.data.symbol][event.data.orderID] = event.data 125 | 126 | def get_trade_info_callback(self, event): 127 | tradeinfo = event.data 128 | self.orderinfo[tradeinfo.symbol][tradeinfo.orderID].status = STATUS_ALLTRADED 129 | 130 | def register(self): 131 | self.eventengine.register(EVENT_TRADE, self.get_trade_info_callback) 132 | self.eventengine.register(EVENT_ORDER, self.get_order_info_callback) 133 | self.eventengine.register(EVENT_ERROR, self.err.process_error) 134 | 135 | def log_handler(self): 136 | return DefaultLogHandler(name=__name__) 137 | 138 | def vwap_callback(self, size, reqobj, price, interval, maxtimeout): 139 | pass 140 | 141 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/__init__.py: -------------------------------------------------------------------------------- 1 | from .push_engine.base_engine import BaseEngine as PushBaseEngine 2 | from .push_engine.quotation_engine import DefaultQuotationEngine 3 | from .main_engine import MainEngine 4 | from .event_engine import * 5 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/event_engine.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from collections import defaultdict 3 | from Queue import Queue, Empty 4 | from threading import Thread 5 | 6 | #帐户相关常量事件 7 | EVENT_POSITION = 'ePosition' 8 | EVENT_ACCOUNT = 'eAccount' 9 | EVENT_TICKET = 'eTicket' #行情推送事件 10 | EVENT_ERROR = 'eError' #错误处理事件将统一交给error_handler处理 11 | EVENT_TRADE = 'eTrade' #处理交易成功回报 12 | EVENT_ORDER = 'eOrder' #处理交易下单回报 13 | 14 | class Event: 15 | """事件对象""" 16 | 17 | def __init__(self, event_type, data=None): 18 | self.event_type = event_type 19 | self.data = data 20 | 21 | class EventEngine: 22 | """事件驱动引擎""" 23 | 24 | def __init__(self): 25 | """初始化事件引擎""" 26 | # 事件队列 27 | self.__queue = Queue() 28 | 29 | # 事件引擎开关 30 | self.__active = False 31 | 32 | # 事件引擎处理线程 33 | self.__thread = Thread(target=self.__run) 34 | 35 | # 事件字典,key 为时间, value 为对应监听事件函数的列表 36 | self.__handlers = defaultdict(list) 37 | 38 | def __run(self): 39 | """启动引擎""" 40 | while self.__active: 41 | try: 42 | event = self.__queue.get(block=True, timeout=1) 43 | handle_thread = Thread(target=self.__process, args=(event,)) 44 | handle_thread.start() 45 | except Empty: 46 | pass 47 | 48 | def __process(self, event): 49 | """事件处理""" 50 | # 检查该事件是否有对应的处理函数 51 | if event.event_type in self.__handlers: 52 | # 若存在,则按顺序将时间传递给处理函数执行 53 | for handler in self.__handlers[event.event_type]: 54 | handler(event) 55 | 56 | def start(self): 57 | """引擎启动""" 58 | self.__active = True 59 | self.__thread.start() 60 | 61 | def stop(self): 62 | """停止引擎""" 63 | self.__active = False 64 | self.__thread.join() 65 | 66 | def register(self, event_type, handler): 67 | """注册事件处理函数监听""" 68 | if handler not in self.__handlers[event_type]: 69 | self.__handlers[event_type].append(handler) 70 | 71 | def unregister(self, event_type, handler): 72 | """注销事件处理函数""" 73 | handler_list = self.__handlers.get(event_type) 74 | if handler_list is None: 75 | return 76 | if handler in handler_list: 77 | handler_list.remove(handler) 78 | if len(handler_list) == 0: 79 | self.__handlers.pop(event_type) 80 | 81 | def put(self, event): 82 | self.__queue.put(event) 83 | 84 | @property 85 | def queue_size(self): 86 | return self.__queue.qsize() 87 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/main_engine.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import os 3 | import sys 4 | import time 5 | import importlib 6 | 7 | from collections import OrderedDict 8 | from .event_engine import EventEngine 9 | from .push_engine import DefaultQuotationEngine 10 | from .push_engine import AccountInfoEngine 11 | 12 | #引用系统支持类 13 | from ..logHandler import DefaultLogHandler 14 | from ..quantGateway import * 15 | 16 | class MainEngine: 17 | """主引擎,负责行情 / 事件驱动引擎 / 交易""" 18 | 19 | def __init__(self, gateway_name, gateway_config, log_handler=DefaultLogHandler(name=__name__), quotation_engines=None): 20 | """初始化事件引擎 / 连接gateway""" 21 | self.event_engine = EventEngine() 22 | self.gateway = Use(gateway_name, gatewayConf=gateway_config, eventEngine=self.event_engine, log=log_handler) 23 | self.log = log_handler 24 | if os.path.exists(gateway_config): 25 | self.gateway.connect() 26 | else: 27 | self.log.warn(u"配置文件不存在 %s" % gateway_config) 28 | 29 | quotation_engines = quotation_engines or [AccountInfoEngine] 30 | 31 | if type(quotation_engines) != list: 32 | quotation_engines = [quotation_engines] 33 | self.quotation_engines = [] 34 | for quotation_engine in quotation_engines: 35 | self.quotation_engines.append(quotation_engine(self.event_engine, self.gateway)) 36 | 37 | #保存读取的策略类 38 | self.strategies = OrderedDict() 39 | self.strategy_list = list() 40 | 41 | self.log.info(u'启动主引擎') 42 | 43 | def start(self): 44 | """启动主引擎""" 45 | self.event_engine.start() 46 | #策略引擎延时启动,保证交易所服务器连接成功 47 | time.sleep(5) 48 | for quotation_engine in self.quotation_engines: 49 | quotation_engine.start() 50 | 51 | def load_strategy(self, names=None): 52 | """动态加载策略 53 | 暂时不提供reload策略的功能 54 | @param names: 策略名列表""" 55 | 56 | s_folder = 'strategies' 57 | strategies = os.listdir(s_folder) 58 | strategies = filter(lambda file: file.endswith('.py') and file != '__init__.py', strategies) 59 | importlib.import_module(s_folder) 60 | for strategy_file in strategies: 61 | strategy_module_name = os.path.basename(strategy_file)[:-3] 62 | strategy_module = importlib.import_module('.' + strategy_module_name, 'strategies') 63 | strategy_class = getattr(strategy_module, 'Strategy') 64 | if names is None or strategy_class.name in names: 65 | self.strategies[strategy_module_name] = strategy_class 66 | self.strategy_list.append(strategy_class(self.gateway, log_handler=self.log, main_engine=self)) 67 | self.log.info(u'加载策略: %s' % strategy_module_name) 68 | 69 | for strategy in self.strategy_list: 70 | for quotation_engine in self.quotation_engines: 71 | self.event_engine.register(quotation_engine.EventType, strategy.run) 72 | self.log.info(u'加载策略完毕') 73 | 74 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/push_engine/__init__.py: -------------------------------------------------------------------------------- 1 | from .quotation_engine import DefaultQuotationEngine 2 | from .account_info_engine import AccountInfoEngine 3 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/push_engine/account_info_engine.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | import json 4 | import time 5 | from .base_engine import BaseEngine 6 | from ..event_engine import * 7 | 8 | class AccountInfoEngine(BaseEngine): 9 | """处理帐户信息引擎将帐户所有信息打包发给策略""" 10 | EventType = 'account' 11 | PushInterval = 10 12 | 13 | def init(self): 14 | #暂时作为测试数据 15 | self.source = {} 16 | self.register() 17 | 18 | def register(self): 19 | #注册需要的数据推送事件 20 | self.event_engine.register(EVENT_POSITION, self.get_position) 21 | self.event_engine.register(EVENT_ACCOUNT, self.get_account) 22 | self.event_engine.register(EVENT_TICKET, self.get_quotation) 23 | 24 | def get_position(self, event): 25 | self.source['position'] = event.data 26 | 27 | def get_account(self, event): 28 | self.source['account'] = json.dumps(event.data.__dict__) 29 | 30 | def get_quotation(self, event): 31 | self.source['quotation'] = event.data 32 | 33 | def fetch_quotation(self): 34 | #self.gateway.qryAccount() 35 | #这里需要延时处理,否则回报会丢弃,建议使用多引擎的方式 36 | #self.gateway.qryPosition() 37 | self.gateway.subscribe('IF1609') 38 | return self.source 39 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/push_engine/base_engine.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from threading import Thread 3 | 4 | import time 5 | from ..event_engine import Event 6 | 7 | 8 | class BaseEngine: 9 | """行情推送引擎基类""" 10 | EventType = 'base' 11 | PushInterval = 1 12 | 13 | def __init__(self, event_engine, gateway): 14 | self.event_engine = event_engine 15 | self.is_active = True 16 | self.quotation_thread = Thread(target=self.push_quotation) 17 | self.quotation_thread.setDaemon(False) 18 | self.gateway = gateway 19 | self.init() 20 | 21 | def start(self): 22 | self.quotation_thread.start() 23 | 24 | def stop(self): 25 | self.is_active = False 26 | 27 | def push_quotation(self): 28 | while self.is_active: 29 | try: 30 | response_data = self.fetch_quotation() 31 | except: 32 | time.sleep(self.PushInterval) 33 | continue 34 | event = Event(event_type=self.EventType, data=response_data) 35 | self.event_engine.put(event) 36 | time.sleep(self.PushInterval) 37 | 38 | def fetch_quotation(self): 39 | # return your quotation 40 | return None 41 | 42 | def init(self): 43 | # do something init 44 | pass 45 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/push_engine/clock_engine.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import datetime 3 | from threading import Thread 4 | 5 | import time 6 | from ..easydealutils import time as etime 7 | from ..event_engine import Event 8 | 9 | 10 | class Clock: 11 | def __init__(self, trading_time, clock_event): 12 | self.trading_state = trading_time 13 | self.clock_event = clock_event 14 | 15 | 16 | class ClockEngine: 17 | """时间推送引擎""" 18 | EventType = 'clock_tick' 19 | 20 | def __init__(self, event_engine): 21 | self.start_time = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) 22 | self.event_engine = event_engine 23 | self.is_active = True 24 | self.clock_engine_thread = Thread(target=self.clocktick) 25 | self.sleep_time = 1 26 | self.trading_state = True if etime.is_tradetime(datetime.datetime.now()) else False 27 | 28 | def start(self): 29 | self.clock_engine_thread.start() 30 | 31 | def clocktick(self): 32 | while self.is_active: 33 | now_time = datetime.datetime.now() 34 | self.tock(now_time) 35 | time.sleep(self.sleep_time) 36 | 37 | def tock(self, now_time): 38 | """ 39 | :param now_time: datetime.datetime() 40 | :return: 41 | """ 42 | min_seconds = 60 43 | time_delta = now_time - self.start_time 44 | seconds_delta = int(time_delta.total_seconds()) 45 | 46 | if etime.is_holiday(now_time): 47 | pass # 假日暂停时钟引擎 48 | else: 49 | # 工作日,干活了 50 | if etime.is_tradetime(now_time): 51 | # 交易时间段 52 | if self.trading_state is True: 53 | 54 | if etime.is_closing(now_time): 55 | self.push_event_type('closing') 56 | 57 | for delta in [0.5, 1, 5, 15, 30, 60]: 58 | if seconds_delta % (min_seconds * delta) == 0: 59 | self.push_event_type(delta) 60 | 61 | else: 62 | self.trading_state = True 63 | self.push_event_type('open') 64 | 65 | elif etime.is_pause(now_time): 66 | self.push_event_type('pause') 67 | while etime.is_pause(now_time): 68 | time.sleep(self.sleep_time) 69 | 70 | elif etime.is_continue(now_time): 71 | self.push_event_type('continue') 72 | while etime.is_continue(now_time): 73 | time.sleep(self.sleep_time) 74 | 75 | elif self.trading_state is True: 76 | self.trading_state = False 77 | self.push_event_type('close') 78 | 79 | def push_event_type(self, etype): 80 | event = Event(event_type=self.EventType, data=Clock(self.trading_state, etype)) 81 | self.event_engine.put(event) 82 | 83 | def stop(self): 84 | self.is_active = False 85 | -------------------------------------------------------------------------------- /ftsVob/quantEngine/push_engine/quotation_engine.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .base_engine import BaseEngine 4 | 5 | class DefaultQuotationEngine(BaseEngine): 6 | """行情推送引擎""" 7 | EventType = 'quotation' 8 | 9 | def init(self): 10 | 11 | #暂时作为测试数据 12 | self.source = {} 13 | 14 | def fetch_quotation(self): 15 | return self.source 16 | -------------------------------------------------------------------------------- /ftsVob/quantGateway/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from .api import * 3 | from .ctpGateway import CtpGateway 4 | -------------------------------------------------------------------------------- /ftsVob/quantGateway/api.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from .ctpGateway import CtpGateway 3 | 4 | def Use(gatewayname, **kwargs): 5 | """用于生成特定的交易接口模块 6 | :param gatewayname: 期货交易接口支持['ctp', 'lts'] 7 | :return 对应的交易接口类 8 | """ 9 | if gatewayname.lower() in ['ctp', 'CTP']: 10 | return CtpGateway(**kwargs) 11 | -------------------------------------------------------------------------------- /ftsVob/quantGateway/ctpGateway/__init__.py: -------------------------------------------------------------------------------- 1 | from .ctpGateway import CtpGateway 2 | -------------------------------------------------------------------------------- /ftsVob/quantGateway/ctpGateway/ctpGateway.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | ''' 4 | vn.ctp的gateway接入 5 | 6 | 考虑到现阶段大部分CTP中的ExchangeID字段返回的都是空值 7 | vtSymbol直接使用symbol 8 | ''' 9 | 10 | 11 | import os 12 | import json 13 | from copy import copy 14 | 15 | from vnctpmd import MdApi 16 | from vnctptd import TdApi 17 | from ctpDataType import * 18 | from ..quant_gateway import * 19 | from ..quant_constant import * 20 | 21 | 22 | # 以下为一些VT类型和CTP类型的映射字典 23 | # 价格类型映射 24 | priceTypeMap = {} 25 | priceTypeMap[PRICETYPE_LIMITPRICE] = defineDict["THOST_FTDC_OPT_LimitPrice"] 26 | priceTypeMap[PRICETYPE_MARKETPRICE] = defineDict["THOST_FTDC_OPT_AnyPrice"] 27 | priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} 28 | 29 | # 方向类型映射 30 | directionMap = {} 31 | directionMap[DIRECTION_LONG] = defineDict['THOST_FTDC_D_Buy'] 32 | directionMap[DIRECTION_SHORT] = defineDict['THOST_FTDC_D_Sell'] 33 | directionMapReverse = {v: k for k, v in directionMap.items()} 34 | 35 | # 开平类型映射 36 | offsetMap = {} 37 | offsetMap[OFFSET_OPEN] = defineDict['THOST_FTDC_OF_Open'] 38 | offsetMap[OFFSET_CLOSE] = defineDict['THOST_FTDC_OF_Close'] 39 | offsetMap[OFFSET_CLOSETODAY] = defineDict['THOST_FTDC_OF_CloseToday'] 40 | offsetMap[OFFSET_CLOSEYESTERDAY] = defineDict['THOST_FTDC_OF_CloseYesterday'] 41 | offsetMapReverse = {v:k for k,v in offsetMap.items()} 42 | 43 | # 交易所类型映射 44 | exchangeMap = {} 45 | #exchangeMap[EXCHANGE_CFFEX] = defineDict['THOST_FTDC_EIDT_CFFEX'] 46 | #exchangeMap[EXCHANGE_SHFE] = defineDict['THOST_FTDC_EIDT_SHFE'] 47 | #exchangeMap[EXCHANGE_CZCE] = defineDict['THOST_FTDC_EIDT_CZCE'] 48 | #exchangeMap[EXCHANGE_DCE] = defineDict['THOST_FTDC_EIDT_DCE'] 49 | exchangeMap[EXCHANGE_CFFEX] = 'CFFEX' 50 | exchangeMap[EXCHANGE_SHFE] = 'SHFE' 51 | exchangeMap[EXCHANGE_CZCE] = 'CZCE' 52 | exchangeMap[EXCHANGE_DCE] = 'DCE' 53 | exchangeMap[EXCHANGE_UNKNOWN] = '' 54 | exchangeMapReverse = {v:k for k,v in exchangeMap.items()} 55 | 56 | # 持仓类型映射 57 | posiDirectionMap = {} 58 | posiDirectionMap[DIRECTION_NET] = defineDict["THOST_FTDC_PD_Net"] 59 | posiDirectionMap[DIRECTION_LONG] = defineDict["THOST_FTDC_PD_Long"] 60 | posiDirectionMap[DIRECTION_SHORT] = defineDict["THOST_FTDC_PD_Short"] 61 | posiDirectionMapReverse = {v:k for k,v in posiDirectionMap.items()} 62 | 63 | 64 | ######################################################################## 65 | class CtpGateway(VtGateway): 66 | """CTP接口""" 67 | 68 | #---------------------------------------------------------------------- 69 | def __init__(self, gatewayName='CTP', gatewayConf=None, eventEngine=None, log=None): 70 | """Constructor""" 71 | super(CtpGateway, self).__init__(gatewayName, eventEngine=eventEngine, log=log) 72 | 73 | self.mdApi = CtpMdApi(self, self.log) # 行情API 74 | self.tdApi = CtpTdApi(self, self.log) # 交易API 75 | 76 | self.mdConnected = False # 行情API连接状态,登录完成后为True 77 | self.tdConnected = False # 交易API连接状态 78 | 79 | self.gatewayConf = gatewayConf 80 | 81 | #---------------------------------------------------------------------- 82 | def connect(self): 83 | """连接""" 84 | # 载入json文件 85 | fileName = self.gatewayConf 86 | 87 | try: 88 | f = file(fileName) 89 | except IOError: 90 | self.log.info(u'读取连接配置出错,请检查') 91 | return 92 | 93 | # 解析json文件 94 | setting = json.load(f) 95 | try: 96 | userID = str(setting['userID']) 97 | password = str(setting['password']) 98 | brokerID = str(setting['brokerID']) 99 | tdAddress = str(setting['tdAddress']) 100 | mdAddress = str(setting['mdAddress']) 101 | except KeyError: 102 | self.log.info(u'连接配置缺少字段,请检查') 103 | return 104 | 105 | # 创建行情和交易接口对象 106 | self.mdApi.connect(userID, password, brokerID, mdAddress) 107 | self.tdApi.connect(userID, password, brokerID, tdAddress) 108 | 109 | #---------------------------------------------------------------------- 110 | def subscribe(self, contract_name): 111 | """订阅行情""" 112 | reqobj = VtSubscribeReq() 113 | reqobj.symbol = contract_name 114 | self.mdApi.subscribe(reqobj) 115 | 116 | #---------------------------------------------------------------------- 117 | def sendOrder(self, orderReq): 118 | """发单""" 119 | return self.tdApi.sendOrder(orderReq) 120 | 121 | #---------------------------------------------------------------------- 122 | def cancelOrder(self, cancelOrderReq): 123 | """撤单""" 124 | self.tdApi.cancelOrder(cancelOrderReq) 125 | 126 | #---------------------------------------------------------------------- 127 | def qryAccount(self): 128 | """查询账户资金""" 129 | self.tdApi.qryAccount() 130 | 131 | #---------------------------------------------------------------------- 132 | def qryPosition(self): 133 | """查询持仓""" 134 | self.tdApi.qryPosition() 135 | 136 | #---------------------------------------------------------------------- 137 | def close(self): 138 | """关闭""" 139 | if self.mdConnected: 140 | self.mdApi.close() 141 | if self.tdConnected: 142 | self.tdApi.close() 143 | 144 | ######################################################################## 145 | class CtpMdApi(MdApi): 146 | """CTP行情API实现""" 147 | 148 | #---------------------------------------------------------------------- 149 | def __init__(self, gateway, log): 150 | """Constructor 151 | @gateway: 交易接口对象 152 | @log: 日志句柄继承自交易接口 153 | """ 154 | super(CtpMdApi, self).__init__() 155 | 156 | self.gateway = gateway # gateway对象 157 | self.gatewayName = gateway.gatewayName # gateway对象名称 158 | 159 | self.reqID = EMPTY_INT # 操作请求编号 160 | 161 | self.connectionStatus = False # 连接状态 162 | self.loginStatus = False # 登录状态 163 | 164 | self.subscribedSymbols = set() # 已订阅合约代码 165 | 166 | self.userID = EMPTY_STRING # 账号 167 | self.password = EMPTY_STRING # 密码 168 | self.brokerID = EMPTY_STRING # 经纪商代码 169 | self.address = EMPTY_STRING # 服务器地址 170 | self.log = log # 日志句柄 171 | 172 | #---------------------------------------------------------------------- 173 | def onFrontConnected(self): 174 | """服务器连接""" 175 | self.connectionStatus = True 176 | self.log.info(u'行情服务器连接成功') 177 | self.login() 178 | 179 | #---------------------------------------------------------------------- 180 | def onFrontDisconnected(self, n): 181 | """服务器断开""" 182 | self.connectionStatus = False 183 | self.loginStatus = False 184 | self.gateway.mdConnected = False 185 | 186 | #---------------------------------------------------------------------- 187 | def onHeartBeatWarning(self, n): 188 | """心跳报警""" 189 | # 因为API的心跳报警比较常被触发,且与API工作关系不大,因此选择忽略 190 | pass 191 | 192 | #---------------------------------------------------------------------- 193 | def onRspError(self, error, n, last): 194 | """错误回报""" 195 | err = VtErrorData() 196 | err.errorID = error['ErrorID'] 197 | err.errorMsg = error['ErrorMsg'].decode('gbk') 198 | self.gateway.onError(err) 199 | 200 | #---------------------------------------------------------------------- 201 | def onRspUserLogin(self, data, error, n, last): 202 | """登陆回报""" 203 | # 如果登录成功,推送日志信息 204 | if error['ErrorID'] == 0: 205 | self.loginStatus = True 206 | self.gateway.mdConnected = True 207 | 208 | # 重新订阅之前订阅的合约 209 | for subscribeReq in self.subscribedSymbols: 210 | self.subscribe(subscribeReq) 211 | 212 | # 否则,推送错误信息 213 | else: 214 | self.log.info(error['ErrorID']) 215 | self.log.info(error['ErrorMsg'].decode('gbk')) 216 | 217 | #---------------------------------------------------------------------- 218 | def onRspUserLogout(self, data, error, n, last): 219 | """登出回报""" 220 | # 如果登出成功,推送日志信息 221 | if error['ErrorID'] == 0: 222 | self.loginStatus = False 223 | self.gateway.tdConnected = False 224 | 225 | # 否则,推送错误信息 226 | else: 227 | self.log.info(error['ErrorID']) 228 | self.log.info(error['ErrorMsg'].decode('gbk')) 229 | 230 | #---------------------------------------------------------------------- 231 | def onRspSubMarketData(self, data, error, n, last): 232 | """订阅合约回报""" 233 | # 通常不在乎订阅错误,选择忽略 234 | self.log.info(error['ErrorID']) 235 | 236 | #---------------------------------------------------------------------- 237 | def onRspUnSubMarketData(self, data, error, n, last): 238 | """退订合约回报""" 239 | # 同上 240 | pass 241 | 242 | #---------------------------------------------------------------------- 243 | def onRtnDepthMarketData(self, data): 244 | """行情推送""" 245 | tick = VtTickData() 246 | tick.gatewayName = self.gatewayName 247 | 248 | tick.symbol = data['InstrumentID'] 249 | tick.exchange = exchangeMapReverse.get(data['ExchangeID'], u'未知') 250 | tick.vtSymbol = tick.symbol #'.'.join([tick.symbol, EXCHANGE_UNKNOWN]) 251 | 252 | tick.lastPrice = data['LastPrice'] 253 | tick.volume = data['Volume'] 254 | tick.openInterest = data['OpenInterest'] 255 | tick.time = '.'.join([data['UpdateTime'], str(data['UpdateMillisec']/100)]) 256 | tick.date = data['TradingDay'] 257 | 258 | tick.openPrice = data['OpenPrice'] 259 | tick.highPrice = data['HighestPrice'] 260 | tick.lowPrice = data['LowestPrice'] 261 | tick.preClosePrice = data['PreClosePrice'] 262 | 263 | tick.upperLimit = data['UpperLimitPrice'] 264 | tick.lowerLimit = data['LowerLimitPrice'] 265 | 266 | # CTP只有一档行情 267 | tick.bidPrice1 = data['BidPrice1'] 268 | tick.bidVolume1 = data['BidVolume1'] 269 | tick.askPrice1 = data['AskPrice1'] 270 | tick.askVolume1 = data['AskVolume1'] 271 | self.gateway.onTick(tick) 272 | 273 | #---------------------------------------------------------------------- 274 | def onRspSubForQuoteRsp(self, data, error, n, last): 275 | """订阅期权询价""" 276 | pass 277 | 278 | #---------------------------------------------------------------------- 279 | def onRspUnSubForQuoteRsp(self, data, error, n, last): 280 | """退订期权询价""" 281 | pass 282 | 283 | #---------------------------------------------------------------------- 284 | def onRtnForQuoteRsp(self, data): 285 | """期权询价推送""" 286 | pass 287 | 288 | #---------------------------------------------------------------------- 289 | def connect(self, userID, password, brokerID, address): 290 | """初始化连接""" 291 | self.userID = userID # 账号 292 | self.password = password # 密码 293 | self.brokerID = brokerID # 经纪商代码 294 | self.address = address # 服务器地址 295 | 296 | # 如果尚未建立服务器连接,则进行连接 297 | if not self.connectionStatus: 298 | # 创建C++环境中的API对象,这里传入的参数是需要用来保存.con文件的文件夹路径 299 | path = os.getcwd() + '/temp/' + self.gatewayName + '/' 300 | if not os.path.exists(path): 301 | os.makedirs(path) 302 | self.createFtdcMdApi(path) 303 | 304 | # 注册服务器地址 305 | self.registerFront(self.address) 306 | 307 | # 初始化连接,成功会调用onFrontConnected 308 | self.init() 309 | 310 | # 若已经连接但尚未登录,则进行登录 311 | else: 312 | if not self.loginStatus: 313 | self.login() 314 | 315 | #---------------------------------------------------------------------- 316 | def subscribe(self, subscribeReq): 317 | """订阅合约""" 318 | # 这里的设计是,如果尚未登录就调用了订阅方法 319 | # 则先保存订阅请求,登录完成后会自动订阅 320 | if self.loginStatus: 321 | self.subscribeMarketData(str(subscribeReq.symbol)) 322 | self.subscribedSymbols.add(subscribeReq) 323 | 324 | #---------------------------------------------------------------------- 325 | def login(self): 326 | """登录""" 327 | # 如果填入了用户名密码等,则登录 328 | if self.userID and self.password and self.brokerID: 329 | req = {} 330 | req['UserID'] = self.userID 331 | req['Password'] = self.password 332 | req['BrokerID'] = self.brokerID 333 | self.reqID += 1 334 | self.reqUserLogin(req, self.reqID) 335 | 336 | #---------------------------------------------------------------------- 337 | def close(self): 338 | """关闭""" 339 | self.exit() 340 | 341 | 342 | ######################################################################## 343 | class CtpTdApi(TdApi): 344 | """CTP交易API实现""" 345 | 346 | #---------------------------------------------------------------------- 347 | def __init__(self, gateway, log): 348 | """API对象的初始化函数 349 | @gateway: 交易接口 350 | @log: 日志句柄继承自交易接口 351 | """ 352 | 353 | super(CtpTdApi, self).__init__() 354 | 355 | self.gateway = gateway # gateway对象 356 | self.gatewayName = gateway.gatewayName # gateway对象名称 357 | 358 | self.reqID = EMPTY_INT # 操作请求编号 359 | self.orderRef = EMPTY_INT # 订单编号 360 | 361 | self.connectionStatus = False # 连接状态 362 | self.loginStatus = False # 登录状态 363 | 364 | self.userID = EMPTY_STRING # 账号 365 | self.password = EMPTY_STRING # 密码 366 | self.brokerID = EMPTY_STRING # 经纪商代码 367 | self.address = EMPTY_STRING # 服务器地址 368 | 369 | self.frontID = EMPTY_INT # 前置机编号 370 | self.sessionID = EMPTY_INT # 会话编号 371 | 372 | self.posBufferDict = {} # 缓存持仓数据的字典 373 | self.symbolExchangeDict = {} # 保存合约代码和交易所的印射关系 374 | self.symbolSizeDict = {} # 保存合约代码和合约大小的印射关系 375 | self.log = log # 日志句柄 376 | self.pos = list() # 持仓列表 377 | 378 | #---------------------------------------------------------------------- 379 | def onFrontConnected(self): 380 | """服务器连接""" 381 | self.connectionStatus = True 382 | self.login() 383 | 384 | #---------------------------------------------------------------------- 385 | def onFrontDisconnected(self, n): 386 | """服务器断开""" 387 | self.connectionStatus = False 388 | self.loginStatus = False 389 | self.gateway.tdConnected = False 390 | 391 | #---------------------------------------------------------------------- 392 | def onHeartBeatWarning(self, n): 393 | """""" 394 | pass 395 | 396 | #---------------------------------------------------------------------- 397 | def onRspAuthenticate(self, data, error, n, last): 398 | """""" 399 | pass 400 | 401 | #---------------------------------------------------------------------- 402 | def onRspUserLogin(self, data, error, n, last): 403 | """登陆回报""" 404 | # 如果登录成功,推送日志信息 405 | if error['ErrorID'] == 0: 406 | self.frontID = str(data['FrontID']) 407 | self.sessionID = str(data['SessionID']) 408 | self.loginStatus = True 409 | self.gateway.mdConnected = True 410 | 411 | self.log.info(u'交易服务器登录完成') 412 | 413 | # 确认结算信息 414 | req = {} 415 | req['BrokerID'] = self.brokerID 416 | req['InvestorID'] = self.userID 417 | self.reqID += 1 418 | self.reqSettlementInfoConfirm(req, self.reqID) 419 | 420 | # 否则,推送错误信息 421 | else: 422 | self.log.info(error['ErrorID']) 423 | self.log.info(error['ErrorMsg'].decode('gbk')) 424 | 425 | #---------------------------------------------------------------------- 426 | def onRspUserLogout(self, data, error, n, last): 427 | """登出回报""" 428 | # 如果登出成功,推送日志信息 429 | if error['ErrorID'] == 0: 430 | self.loginStatus = False 431 | self.gateway.tdConnected = False 432 | self.log.info(u'交易服务器登出完成') 433 | 434 | # 否则,推送错误信息 435 | else: 436 | self.log.info(error['ErrorID']) 437 | self.log.info(error['ErrorMsg'].decode('gbk')) 438 | 439 | #---------------------------------------------------------------------- 440 | def onRspUserPasswordUpdate(self, data, error, n, last): 441 | """""" 442 | pass 443 | 444 | #---------------------------------------------------------------------- 445 | def onRspTradingAccountPasswordUpdate(self, data, error, n, last): 446 | """""" 447 | pass 448 | 449 | #---------------------------------------------------------------------- 450 | def onRspOrderInsert(self, data, error, n, last): 451 | """发单错误(柜台)""" 452 | """本地消息必须处理""" 453 | err = VtErrorData() 454 | err.errorID = error['ErrorID'] 455 | err.errorMsg = error['ErrorMsg'].decode('gbk') 456 | self.gateway.onError(err) 457 | 458 | #---------------------------------------------------------------------- 459 | def onRspParkedOrderInsert(self, data, error, n, last): 460 | """""" 461 | pass 462 | 463 | #---------------------------------------------------------------------- 464 | def onRspParkedOrderAction(self, data, error, n, last): 465 | """""" 466 | pass 467 | 468 | #---------------------------------------------------------------------- 469 | def onRspOrderAction(self, data, error, n, last): 470 | """撤单错误(柜台)""" 471 | """本地消息必须处理""" 472 | err = VtErrorData() 473 | err.errorID = error['ErrorID'] 474 | err.errorMsg = error['ErrorMsg'].decode('gbk') 475 | self.gateway.onError(err) 476 | 477 | #---------------------------------------------------------------------- 478 | def onRspQueryMaxOrderVolume(self, data, error, n, last): 479 | """""" 480 | pass 481 | 482 | #---------------------------------------------------------------------- 483 | def onRspSettlementInfoConfirm(self, data, error, n, last): 484 | """确认结算信息回报""" 485 | 486 | # 查询合约代码 487 | self.reqID += 1 488 | self.reqQryInstrument({}, self.reqID) 489 | 490 | #---------------------------------------------------------------------- 491 | def onRspRemoveParkedOrder(self, data, error, n, last): 492 | """""" 493 | pass 494 | 495 | #---------------------------------------------------------------------- 496 | def onRspRemoveParkedOrderAction(self, data, error, n, last): 497 | """""" 498 | pass 499 | 500 | #---------------------------------------------------------------------- 501 | def onRspExecOrderInsert(self, data, error, n, last): 502 | """""" 503 | pass 504 | 505 | #---------------------------------------------------------------------- 506 | def onRspExecOrderAction(self, data, error, n, last): 507 | """""" 508 | pass 509 | 510 | #---------------------------------------------------------------------- 511 | def onRspForQuoteInsert(self, data, error, n, last): 512 | """""" 513 | pass 514 | 515 | #---------------------------------------------------------------------- 516 | def onRspQuoteInsert(self, data, error, n, last): 517 | """""" 518 | pass 519 | 520 | #---------------------------------------------------------------------- 521 | def onRspQuoteAction(self, data, error, n, last): 522 | """""" 523 | pass 524 | 525 | #---------------------------------------------------------------------- 526 | def onRspQryOrder(self, data, error, n, last): 527 | """""" 528 | pass 529 | 530 | #---------------------------------------------------------------------- 531 | def onRspQryTrade(self, data, error, n, last): 532 | """""" 533 | pass 534 | 535 | #---------------------------------------------------------------------- 536 | def onRspQryInvestorPosition(self, data, error, n, last): 537 | """持仓查询回报""" 538 | if not last: 539 | self.pos.append(data) 540 | else: 541 | self.pos.append(data) 542 | self.gateway.onPosition(self.pos) 543 | self.pos = [] 544 | 545 | #---------------------------------------------------------------------- 546 | def onRspQryTradingAccount(self, data, error, n, last): 547 | """资金账户查询回报""" 548 | account = VtAccountData() 549 | account.gatewayName = self.gatewayName 550 | 551 | # 账户代码 552 | account.accountID = data['AccountID'] 553 | account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) 554 | 555 | # 数值相关 556 | account.preBalance = data['PreBalance'] 557 | account.available = data['Available'] 558 | account.commission = data['Commission'] 559 | account.margin = data['CurrMargin'] 560 | account.closeProfit = data['CloseProfit'] 561 | account.positionProfit = data['PositionProfit'] 562 | 563 | # 这里的balance和快期中的账户不确定是否一样,需要测试 564 | account.balance = (data['PreBalance'] - data['PreCredit'] - data['PreMortgage'] + 565 | data['Mortgage'] - data['Withdraw'] + data['Deposit'] + 566 | data['CloseProfit'] + data['PositionProfit'] + data['CashIn'] - 567 | data['Commission']) 568 | 569 | # 推送 570 | self.gateway.onAccount(account) 571 | 572 | #---------------------------------------------------------------------- 573 | def onRspQryInvestor(self, data, error, n, last): 574 | """投资者查询回报""" 575 | pass 576 | 577 | #---------------------------------------------------------------------- 578 | def onRspQryTradingCode(self, data, error, n, last): 579 | """""" 580 | pass 581 | 582 | #---------------------------------------------------------------------- 583 | def onRspQryInstrumentMarginRate(self, data, error, n, last): 584 | """""" 585 | pass 586 | 587 | #---------------------------------------------------------------------- 588 | def onRspQryInstrumentCommissionRate(self, data, error, n, last): 589 | """""" 590 | pass 591 | 592 | #---------------------------------------------------------------------- 593 | def onRspQryExchange(self, data, error, n, last): 594 | """""" 595 | pass 596 | 597 | #---------------------------------------------------------------------- 598 | def onRspQryProduct(self, data, error, n, last): 599 | """""" 600 | pass 601 | 602 | #---------------------------------------------------------------------- 603 | def onRspQryInstrument(self, data, error, n, last): 604 | """合约查询回报""" 605 | contract = VtContractData() 606 | contract.gatewayName = self.gatewayName 607 | 608 | contract.symbol = data['InstrumentID'] 609 | contract.exchange = exchangeMapReverse[data['ExchangeID']] 610 | contract.vtSymbol = contract.symbol #'.'.join([contract.symbol, contract.exchange]) 611 | contract.name = data['InstrumentName'].decode('GBK') 612 | 613 | # 合约数值 614 | contract.size = data['VolumeMultiple'] 615 | contract.priceTick = data['PriceTick'] 616 | contract.strikePrice = data['StrikePrice'] 617 | contract.underlyingSymbol = data['UnderlyingInstrID'] 618 | 619 | # 合约类型 620 | if data['ProductClass'] == '1': 621 | contract.productClass = PRODUCT_FUTURES 622 | elif data['ProductClass'] == '2': 623 | contract.productClass = PRODUCT_OPTION 624 | elif data['ProductClass'] == '3': 625 | contract.productClass = PRODUCT_COMBINATION 626 | else: 627 | contract.productClass = PRODUCT_UNKNOWN 628 | 629 | # 期权类型 630 | if data['OptionsType'] == '1': 631 | contract.optionType = OPTION_CALL 632 | elif data['OptionsType'] == '2': 633 | contract.optionType = OPTION_PUT 634 | 635 | # 缓存代码和交易所的印射关系 636 | self.symbolExchangeDict[contract.symbol] = contract.exchange 637 | self.symbolSizeDict[contract.symbol] = contract.size 638 | 639 | # 推送 640 | self.gateway.onContract(contract) 641 | 642 | if last: 643 | self.log.info(u'交易合约信息获取完成') 644 | 645 | #---------------------------------------------------------------------- 646 | def onRspQryDepthMarketData(self, data, error, n, last): 647 | """""" 648 | pass 649 | 650 | #---------------------------------------------------------------------- 651 | def onRspQrySettlementInfo(self, data, error, n, last): 652 | """查询结算信息回报""" 653 | pass 654 | 655 | #---------------------------------------------------------------------- 656 | def onRspQryTransferBank(self, data, error, n, last): 657 | """""" 658 | pass 659 | 660 | #---------------------------------------------------------------------- 661 | def onRspQryInvestorPositionDetail(self, data, error, n, last): 662 | """""" 663 | pass 664 | 665 | #---------------------------------------------------------------------- 666 | def onRspQryNotice(self, data, error, n, last): 667 | """""" 668 | pass 669 | 670 | #---------------------------------------------------------------------- 671 | def onRspQrySettlementInfoConfirm(self, data, error, n, last): 672 | """""" 673 | pass 674 | 675 | #---------------------------------------------------------------------- 676 | def onRspQryInvestorPositionCombineDetail(self, data, error, n, last): 677 | """""" 678 | pass 679 | 680 | #---------------------------------------------------------------------- 681 | def onRspQryCFMMCTradingAccountKey(self, data, error, n, last): 682 | """""" 683 | pass 684 | 685 | #---------------------------------------------------------------------- 686 | def onRspQryEWarrantOffset(self, data, error, n, last): 687 | """""" 688 | pass 689 | 690 | #---------------------------------------------------------------------- 691 | def onRspQryInvestorProductGroupMargin(self, data, error, n, last): 692 | """""" 693 | pass 694 | 695 | #---------------------------------------------------------------------- 696 | def onRspQryExchangeMarginRate(self, data, error, n, last): 697 | """""" 698 | pass 699 | 700 | #---------------------------------------------------------------------- 701 | def onRspQryExchangeMarginRateAdjust(self, data, error, n, last): 702 | """""" 703 | pass 704 | 705 | #---------------------------------------------------------------------- 706 | def onRspQryExchangeRate(self, data, error, n, last): 707 | """""" 708 | pass 709 | 710 | #---------------------------------------------------------------------- 711 | def onRspQrySecAgentACIDMap(self, data, error, n, last): 712 | """""" 713 | pass 714 | 715 | #---------------------------------------------------------------------- 716 | def onRspQryOptionInstrTradeCost(self, data, error, n, last): 717 | """""" 718 | pass 719 | 720 | #---------------------------------------------------------------------- 721 | def onRspQryOptionInstrCommRate(self, data, error, n, last): 722 | """""" 723 | pass 724 | 725 | #---------------------------------------------------------------------- 726 | def onRspQryExecOrder(self, data, error, n, last): 727 | """""" 728 | pass 729 | 730 | #---------------------------------------------------------------------- 731 | def onRspQryForQuote(self, data, error, n, last): 732 | """""" 733 | pass 734 | 735 | #---------------------------------------------------------------------- 736 | def onRspQryQuote(self, data, error, n, last): 737 | """""" 738 | pass 739 | 740 | #---------------------------------------------------------------------- 741 | def onRspQryTransferSerial(self, data, error, n, last): 742 | """""" 743 | pass 744 | 745 | #---------------------------------------------------------------------- 746 | def onRspQryAccountregister(self, data, error, n, last): 747 | """""" 748 | pass 749 | 750 | #---------------------------------------------------------------------- 751 | def onRspError(self, error, n, last): 752 | """错误回报""" 753 | self.log.info(error['ErrorID']) 754 | self.log.info(error['ErrorMsg'].decode('gbk')) 755 | 756 | #---------------------------------------------------------------------- 757 | def onRtnOrder(self, data): 758 | """报单回报""" 759 | # 更新最大报单编号 760 | newref = data['OrderRef'] 761 | self.orderRef = max(self.orderRef, int(newref)) 762 | 763 | # 创建报单数据对象 764 | order = VtOrderData() 765 | order.gatewayName = self.gatewayName 766 | 767 | # 保存代码和报单号 768 | order.symbol = data['InstrumentID'] 769 | order.exchange = exchangeMapReverse[data['ExchangeID']] 770 | order.vtSymbol = order.symbol #'.'.join([order.symbol, order.exchange]) 771 | 772 | order.orderID = data['OrderRef'] 773 | 774 | # 方向 775 | if data['Direction'] == '0': 776 | order.direction = DIRECTION_LONG 777 | elif data['Direction'] == '1': 778 | order.direction = DIRECTION_SHORT 779 | else: 780 | order.direction = DIRECTION_UNKNOWN 781 | 782 | # 开平 783 | if data['CombOffsetFlag'] == '0': 784 | order.offset = OFFSET_OPEN 785 | elif data['CombOffsetFlag'] == '1': 786 | order.offset = OFFSET_CLOSE 787 | else: 788 | order.offset = OFFSET_UNKNOWN 789 | 790 | # 状态 791 | if data['OrderStatus'] == '0': 792 | order.status = STATUS_ALLTRADED 793 | elif data['OrderStatus'] == '1': 794 | order.status = STATUS_PARTTRADED 795 | elif data['OrderStatus'] == '3': 796 | order.status = STATUS_NOTTRADED 797 | elif data['OrderStatus'] == '5': 798 | order.status = STATUS_CANCELLED 799 | else: 800 | order.status = STATUS_UNKNOWN 801 | 802 | # 价格、报单量等数值 803 | order.price = data['LimitPrice'] 804 | order.totalVolume = data['VolumeTotalOriginal'] 805 | order.tradedVolume = data['VolumeTraded'] 806 | order.orderTime = data['InsertTime'] 807 | order.cancelTime = data['CancelTime'] 808 | order.frontID = data['FrontID'] 809 | order.sessionID = data['SessionID'] 810 | 811 | # CTP的报单号一致性维护需要基于frontID, sessionID, orderID三个字段 812 | # 但在本接口设计中,已经考虑了CTP的OrderRef的自增性,避免重复 813 | # 唯一可能出现OrderRef重复的情况是多处登录并在非常接近的时间内(几乎同时发单) 814 | # 考虑到VtTrader的应用场景,认为以上情况不会构成问题 815 | order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) 816 | 817 | # 推送 818 | self.gateway.onOrder(order) 819 | 820 | #---------------------------------------------------------------------- 821 | def onRtnTrade(self, data): 822 | """成交回报""" 823 | # 创建报单数据对象 824 | trade = VtTradeData() 825 | trade.gatewayName = self.gatewayName 826 | 827 | # 保存代码和报单号 828 | trade.symbol = data['InstrumentID'] 829 | trade.exchange = exchangeMapReverse[data['ExchangeID']] 830 | trade.vtSymbol = trade.symbol #'.'.join([trade.symbol, trade.exchange]) 831 | 832 | trade.tradeID = data['TradeID'] 833 | trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) 834 | 835 | trade.orderID = data['OrderRef'] 836 | trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) 837 | 838 | # 方向 839 | trade.direction = directionMapReverse.get(data['Direction'], '') 840 | 841 | # 开平 842 | trade.offset = offsetMapReverse.get(data['OffsetFlag'], '') 843 | 844 | # 价格、报单量等数值 845 | trade.price = data['Price'] 846 | trade.volume = data['Volume'] 847 | trade.tradeTime = data['TradeTime'] 848 | 849 | # 推送 850 | self.gateway.onTrade(trade) 851 | 852 | #---------------------------------------------------------------------- 853 | def onErrRtnOrderInsert(self, data, error): 854 | """发单错误回报(交易所)""" 855 | """分布式处理时需要关注的回报,目前暂时忽略""" 856 | pass 857 | #self.log.info(error['ErrorID']) 858 | #self.log.info(error['ErrorMsg'].decode('gbk')) 859 | 860 | #---------------------------------------------------------------------- 861 | def onErrRtnOrderAction(self, data, error): 862 | """撤单错误回报(交易所)""" 863 | """分布式处理时需要关注的回报,目前暂时忽略""" 864 | pass 865 | #self.log.info(error['ErrorID']) 866 | #self.log.info(error['ErrorMsg'].decode('gbk')) 867 | 868 | #---------------------------------------------------------------------- 869 | def onRtnInstrumentStatus(self, data): 870 | """""" 871 | pass 872 | 873 | #---------------------------------------------------------------------- 874 | def onRtnTradingNotice(self, data): 875 | """""" 876 | pass 877 | 878 | #---------------------------------------------------------------------- 879 | def onRtnErrorConditionalOrder(self, data): 880 | """""" 881 | pass 882 | 883 | #---------------------------------------------------------------------- 884 | def onRtnExecOrder(self, data): 885 | """""" 886 | pass 887 | 888 | #---------------------------------------------------------------------- 889 | def onErrRtnExecOrderInsert(self, data, error): 890 | """""" 891 | pass 892 | 893 | #---------------------------------------------------------------------- 894 | def onErrRtnExecOrderAction(self, data, error): 895 | """""" 896 | pass 897 | 898 | #---------------------------------------------------------------------- 899 | def onErrRtnForQuoteInsert(self, data, error): 900 | """""" 901 | pass 902 | 903 | #---------------------------------------------------------------------- 904 | def onRtnQuote(self, data): 905 | """""" 906 | pass 907 | 908 | #---------------------------------------------------------------------- 909 | def onErrRtnQuoteInsert(self, data, error): 910 | """""" 911 | pass 912 | 913 | #---------------------------------------------------------------------- 914 | def onErrRtnQuoteAction(self, data, error): 915 | """""" 916 | pass 917 | 918 | #---------------------------------------------------------------------- 919 | def onRtnForQuoteRsp(self, data): 920 | """""" 921 | pass 922 | 923 | #---------------------------------------------------------------------- 924 | def onRspQryContractBank(self, data, error, n, last): 925 | """""" 926 | pass 927 | 928 | #---------------------------------------------------------------------- 929 | def onRspQryParkedOrder(self, data, error, n, last): 930 | """""" 931 | pass 932 | 933 | #---------------------------------------------------------------------- 934 | def onRspQryParkedOrderAction(self, data, error, n, last): 935 | """""" 936 | pass 937 | 938 | #---------------------------------------------------------------------- 939 | def onRspQryTradingNotice(self, data, error, n, last): 940 | """""" 941 | pass 942 | 943 | #---------------------------------------------------------------------- 944 | def onRspQryBrokerTradingParams(self, data, error, n, last): 945 | """""" 946 | pass 947 | 948 | #---------------------------------------------------------------------- 949 | def onRspQryBrokerTradingAlgos(self, data, error, n, last): 950 | """""" 951 | pass 952 | 953 | #---------------------------------------------------------------------- 954 | def onRtnFromBankToFutureByBank(self, data): 955 | """""" 956 | pass 957 | 958 | #---------------------------------------------------------------------- 959 | def onRtnFromFutureToBankByBank(self, data): 960 | """""" 961 | pass 962 | 963 | #---------------------------------------------------------------------- 964 | def onRtnRepealFromBankToFutureByBank(self, data): 965 | """""" 966 | pass 967 | 968 | #---------------------------------------------------------------------- 969 | def onRtnRepealFromFutureToBankByBank(self, data): 970 | """""" 971 | pass 972 | 973 | #---------------------------------------------------------------------- 974 | def onRtnFromBankToFutureByFuture(self, data): 975 | """""" 976 | pass 977 | 978 | #---------------------------------------------------------------------- 979 | def onRtnFromFutureToBankByFuture(self, data): 980 | """""" 981 | pass 982 | 983 | #---------------------------------------------------------------------- 984 | def onRtnRepealFromBankToFutureByFutureManual(self, data): 985 | """""" 986 | pass 987 | 988 | #---------------------------------------------------------------------- 989 | def onRtnRepealFromFutureToBankByFutureManual(self, data): 990 | """""" 991 | pass 992 | 993 | #---------------------------------------------------------------------- 994 | def onRtnQueryBankBalanceByFuture(self, data): 995 | """""" 996 | pass 997 | 998 | #---------------------------------------------------------------------- 999 | def onErrRtnBankToFutureByFuture(self, data, error): 1000 | """""" 1001 | pass 1002 | 1003 | #---------------------------------------------------------------------- 1004 | def onErrRtnFutureToBankByFuture(self, data, error): 1005 | """""" 1006 | pass 1007 | 1008 | #---------------------------------------------------------------------- 1009 | def onErrRtnRepealBankToFutureByFutureManual(self, data, error): 1010 | """""" 1011 | pass 1012 | 1013 | #---------------------------------------------------------------------- 1014 | def onErrRtnRepealFutureToBankByFutureManual(self, data, error): 1015 | """""" 1016 | pass 1017 | 1018 | #---------------------------------------------------------------------- 1019 | def onErrRtnQueryBankBalanceByFuture(self, data, error): 1020 | """""" 1021 | pass 1022 | 1023 | #---------------------------------------------------------------------- 1024 | def onRtnRepealFromBankToFutureByFuture(self, data): 1025 | """""" 1026 | pass 1027 | 1028 | #---------------------------------------------------------------------- 1029 | def onRtnRepealFromFutureToBankByFuture(self, data): 1030 | """""" 1031 | pass 1032 | 1033 | #---------------------------------------------------------------------- 1034 | def onRspFromBankToFutureByFuture(self, data, error, n, last): 1035 | """""" 1036 | pass 1037 | 1038 | #---------------------------------------------------------------------- 1039 | def onRspFromFutureToBankByFuture(self, data, error, n, last): 1040 | """""" 1041 | pass 1042 | 1043 | #---------------------------------------------------------------------- 1044 | def onRspQueryBankAccountMoneyByFuture(self, data, error, n, last): 1045 | """""" 1046 | pass 1047 | 1048 | #---------------------------------------------------------------------- 1049 | def onRtnOpenAccountByBank(self, data): 1050 | """""" 1051 | pass 1052 | 1053 | #---------------------------------------------------------------------- 1054 | def onRtnCancelAccountByBank(self, data): 1055 | """""" 1056 | pass 1057 | 1058 | #---------------------------------------------------------------------- 1059 | def onRtnChangeAccountByBank(self, data): 1060 | """""" 1061 | pass 1062 | 1063 | #---------------------------------------------------------------------- 1064 | def connect(self, userID, password, brokerID, address): 1065 | """初始化连接""" 1066 | self.userID = userID # 账号 1067 | self.password = password # 密码 1068 | self.brokerID = brokerID # 经纪商代码 1069 | self.address = address # 服务器地址 1070 | 1071 | # 如果尚未建立服务器连接,则进行连接 1072 | if not self.connectionStatus: 1073 | # 创建C++环境中的API对象,这里传入的参数是需要用来保存.con文件的文件夹路径 1074 | path = os.getcwd() + '/temp/' + self.gatewayName + '/' 1075 | if not os.path.exists(path): 1076 | os.makedirs(path) 1077 | self.createFtdcTraderApi(path) 1078 | 1079 | # 注册服务器地址 1080 | self.registerFront(self.address) 1081 | 1082 | # 初始化连接,成功会调用onFrontConnected 1083 | self.init() 1084 | 1085 | # 若已经连接但尚未登录,则进行登录 1086 | else: 1087 | if not self.loginStatus: 1088 | self.login() 1089 | 1090 | #---------------------------------------------------------------------- 1091 | def login(self): 1092 | """连接服务器""" 1093 | # 如果填入了用户名密码等,则登录 1094 | if self.userID and self.password and self.brokerID: 1095 | req = {} 1096 | req['UserID'] = self.userID 1097 | req['Password'] = self.password 1098 | req['BrokerID'] = self.brokerID 1099 | self.reqID += 1 1100 | self.reqUserLogin(req, self.reqID) 1101 | 1102 | #---------------------------------------------------------------------- 1103 | def qryAccount(self): 1104 | """查询账户""" 1105 | self.reqID += 1 1106 | self.reqQryTradingAccount({}, self.reqID) 1107 | 1108 | #---------------------------------------------------------------------- 1109 | def qryPosition(self): 1110 | """查询持仓""" 1111 | self.reqID += 1 1112 | req = {} 1113 | req['BrokerID'] = self.brokerID 1114 | req['InvestorID'] = self.userID 1115 | self.reqQryInvestorPosition(req, self.reqID) 1116 | 1117 | #---------------------------------------------------------------------- 1118 | def sendOrder(self, orderReq): 1119 | """发单""" 1120 | self.reqID += 1 1121 | self.orderRef += 1 1122 | 1123 | req = {} 1124 | 1125 | req['InstrumentID'] = orderReq.symbol 1126 | req['LimitPrice'] = orderReq.price 1127 | req['VolumeTotalOriginal'] = orderReq.volume 1128 | 1129 | # 下面如果由于传入的类型本接口不支持,则会返回空字符串 1130 | req['OrderPriceType'] = priceTypeMap.get(orderReq.priceType, '') 1131 | req['Direction'] = directionMap.get(orderReq.direction, '') 1132 | req['CombOffsetFlag'] = offsetMap.get(orderReq.offset, '') 1133 | 1134 | req['OrderRef'] = str(self.orderRef) 1135 | req['InvestorID'] = self.userID 1136 | req['UserID'] = self.userID 1137 | req['BrokerID'] = self.brokerID 1138 | 1139 | req['CombHedgeFlag'] = defineDict['THOST_FTDC_HF_Speculation'] # 投机单 1140 | req['ContingentCondition'] = defineDict['THOST_FTDC_CC_Immediately'] # 立即发单 1141 | req['ForceCloseReason'] = defineDict['THOST_FTDC_FCC_NotForceClose'] # 非强平 1142 | req['IsAutoSuspend'] = 0 # 非自动挂起 1143 | req['TimeCondition'] = defineDict['THOST_FTDC_TC_GFD'] # 今日有效 1144 | req['VolumeCondition'] = defineDict['THOST_FTDC_VC_AV'] # 任意成交量 1145 | req['MinVolume'] = 1 # 最小成交量为1 1146 | 1147 | # 判断FAK和FOK 1148 | if orderReq.priceType == PRICETYPE_FAK: 1149 | req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"] 1150 | req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC'] 1151 | req['VolumeCondition'] = defineDict['THOST_FTDC_VC_AV'] 1152 | if orderReq.priceType == PRICETYPE_FOK: 1153 | req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"] 1154 | req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC'] 1155 | req['VolumeCondition'] = defineDict['THOST_FTDC_VC_CV'] 1156 | 1157 | self.reqOrderInsert(req, self.reqID) 1158 | 1159 | # 返回订单号(字符串),便于某些算法进行动态管理 1160 | vtOrderID = '.'.join([self.gatewayName, str(self.orderRef)]) 1161 | return vtOrderID 1162 | 1163 | #---------------------------------------------------------------------- 1164 | def cancelOrder(self, cancelOrderReq): 1165 | """撤单""" 1166 | self.reqID += 1 1167 | 1168 | req = {} 1169 | 1170 | req['InstrumentID'] = cancelOrderReq.symbol 1171 | req['ExchangeID'] = cancelOrderReq.exchange 1172 | req['OrderRef'] = cancelOrderReq.orderID 1173 | req['FrontID'] = cancelOrderReq.frontID 1174 | req['SessionID'] = cancelOrderReq.sessionID 1175 | 1176 | req['ActionFlag'] = defineDict['THOST_FTDC_AF_Delete'] 1177 | req['BrokerID'] = self.brokerID 1178 | req['InvestorID'] = self.userID 1179 | 1180 | self.reqOrderAction(req, self.reqID) 1181 | 1182 | #---------------------------------------------------------------------- 1183 | def close(self): 1184 | """关闭""" 1185 | self.exit() 1186 | 1187 | 1188 | ######################################################################## 1189 | class PositionBuffer(object): 1190 | """用来缓存持仓的数据,处理上期所的数据返回分今昨的问题""" 1191 | 1192 | #---------------------------------------------------------------------- 1193 | def __init__(self, data, gatewayName): 1194 | """Constructor""" 1195 | self.symbol = data['InstrumentID'] 1196 | self.direction = posiDirectionMapReverse.get(data['PosiDirection'], '') 1197 | 1198 | self.todayPosition = EMPTY_INT 1199 | self.ydPosition = EMPTY_INT 1200 | self.todayPositionCost = EMPTY_FLOAT 1201 | self.ydPositionCost = EMPTY_FLOAT 1202 | 1203 | # 通过提前创建持仓数据对象并重复使用的方式来降低开销 1204 | pos = VtPositionData() 1205 | pos.symbol = self.symbol 1206 | pos.vtSymbol = self.symbol 1207 | pos.gatewayName = gatewayName 1208 | pos.direction = self.direction 1209 | pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) 1210 | self.pos = pos 1211 | 1212 | def updateShfeBuffer(self, data, size): 1213 | """更新上期所缓存,返回更新后的持仓数据""" 1214 | # 昨仓和今仓的数据更新是分在两条记录里的,因此需要判断检查该条记录对应仓位 1215 | # 因为今仓字段TodayPosition可能变为0(被全部平仓),因此分辨今昨仓需要用YdPosition字段 1216 | if data['YdPosition']: 1217 | self.ydPosition = data['Position'] 1218 | self.ydPositionCost = data['PositionCost'] 1219 | else: 1220 | self.todayPosition = data['Position'] 1221 | self.todayPositionCost = data['PositionCost'] 1222 | 1223 | # 持仓的昨仓和今仓相加后为总持仓 1224 | self.pos.position = self.todayPosition + self.ydPosition 1225 | self.pos.ydPosition = self.ydPosition 1226 | 1227 | # 如果手头还有持仓,则通过加权平均方式计算持仓均价 1228 | if self.todayPosition or self.ydPosition: 1229 | self.pos.price = ((self.todayPositionCost + self.ydPositionCost)/ 1230 | ((self.todayPosition + self.ydPosition) * size)) 1231 | # 否则价格为0 1232 | else: 1233 | self.pos.price = 0 1234 | 1235 | return copy(self.pos) 1236 | 1237 | def updateBuffer(self, data, size): 1238 | """更新其他交易所的缓存,返回更新后的持仓数据""" 1239 | # 其他交易所并不区分今昨,因此只关心总仓位,昨仓设为0 1240 | self.pos.position = data['Position'] 1241 | self.pos.ydPosition = 0 1242 | 1243 | if data['Position']: 1244 | self.pos.price = data['PositionCost'] / (data['Position'] * size) 1245 | else: 1246 | self.pos.price = 0 1247 | 1248 | return copy(self.pos) 1249 | 1250 | -------------------------------------------------------------------------------- /ftsVob/quantGateway/quant_constant.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 默认空值 4 | EMPTY_STRING = '' 5 | EMPTY_UNICODE = u'' 6 | EMPTY_INT = 0 7 | EMPTY_FLOAT = 0.0 8 | 9 | # 方向常量 10 | DIRECTION_NONE = u'无方向' 11 | DIRECTION_LONG = u'多' 12 | DIRECTION_SHORT = u'空' 13 | DIRECTION_UNKNOWN = u'未知' 14 | DIRECTION_NET = u'净' 15 | DIRECTION_SELL = u'卖出' # IB接口 16 | 17 | # 开平常量 18 | OFFSET_NONE = u'无开平' 19 | OFFSET_OPEN = u'开仓' 20 | OFFSET_CLOSE = u'平仓' 21 | OFFSET_CLOSETODAY = u'平今' 22 | OFFSET_CLOSEYESTERDAY = u'平昨' 23 | OFFSET_UNKNOWN = u'未知' 24 | 25 | # 状态常量 26 | STATUS_NOTTRADED = u'未成交' 27 | STATUS_PARTTRADED = u'部分成交' 28 | STATUS_ALLTRADED = u'全部成交' 29 | STATUS_CANCELLED = u'已撤销' 30 | STATUS_UNKNOWN = u'未知' 31 | 32 | # 合约类型常量 33 | PRODUCT_EQUITY = u'股票' 34 | PRODUCT_FUTURES = u'期货' 35 | PRODUCT_OPTION = u'期权' 36 | PRODUCT_INDEX = u'指数' 37 | PRODUCT_COMBINATION = u'组合' 38 | PRODUCT_FOREX = u'外汇' 39 | PRODUCT_UNKNOWN = u'未知' 40 | PRODUCT_SPOT = u'现货' 41 | PRODUCT_DEFER = u'延期' 42 | PRODUCT_NONE = '' 43 | 44 | # 价格类型常量 45 | PRICETYPE_LIMITPRICE = u'限价' 46 | PRICETYPE_MARKETPRICE = u'市价' 47 | PRICETYPE_FAK = u'FAK' 48 | PRICETYPE_FOK = u'FOK' 49 | 50 | # 期权类型 51 | OPTION_CALL = u'看涨期权' 52 | OPTION_PUT = u'看跌期权' 53 | 54 | # 交易所类型 55 | EXCHANGE_SSE = 'SSE' # 上交所 56 | EXCHANGE_SZSE = 'SZSE' # 深交所 57 | EXCHANGE_CFFEX = 'CFFEX' # 中金所 58 | EXCHANGE_SHFE = 'SHFE' # 上期所 59 | EXCHANGE_CZCE = 'CZCE' # 郑商所 60 | EXCHANGE_DCE = 'DCE' # 大商所 61 | EXCHANGE_SGE = 'SGE' # 上金所 62 | EXCHANGE_UNKNOWN = 'UNKNOWN'# 未知交易所 63 | EXCHANGE_NONE = '' # 空交易所 64 | EXCHANGE_HKEX = 'HKEX' # 港交所 65 | 66 | EXCHANGE_SMART = 'SMART' # IB智能路由(股票、期权) 67 | EXCHANGE_NYMEX = 'NYMEX' # IB 期货 68 | EXCHANGE_GLOBEX = 'GLOBEX' # CME电子交易平台 69 | EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN 70 | 71 | EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 72 | 73 | # 货币类型 74 | CURRENCY_USD = 'USD' # 美元 75 | CURRENCY_CNY = 'CNY' # 人民币 76 | CURRENCY_UNKNOWN = 'UNKNOWN' # 未知货币 77 | CURRENCY_NONE = '' # 空货币 -------------------------------------------------------------------------------- /ftsVob/quantGateway/quant_gateway.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | import time 4 | 5 | from quant_constant import * 6 | from ..logHandler import DefaultLogHandler 7 | from ..quantEngine.event_engine import * 8 | 9 | 10 | ######################################################################## 11 | class VtGateway(object): 12 | """交易接口""" 13 | 14 | #---------------------------------------------------------------------- 15 | def __init__(self, gatewayName, eventEngine=None, log=None): 16 | """Constructor""" 17 | self.gatewayName = gatewayName 18 | self.eventEngine = eventEngine 19 | self.log = log 20 | self.tickdata = {} 21 | #---------------------------------------------------------------------- 22 | def onTick(self, tick): 23 | """市场行情推送""" 24 | # 通用事件 25 | # event = Event(event_type=EVENT_TICKET) 26 | # event.data = tick 27 | # self.eventEngine.put(event) 28 | 29 | # 用RingBuffer将行情数据缓存以便于将来可以合并 30 | if tick.symbol in self.tickdata: 31 | self.tickdata[tick.symbol].append(tick) 32 | else: 33 | self.tickdata[tick.symbol] = RingBuffer(size_max = 900) 34 | self.tickdata[tick.symbol].append(tick) 35 | 36 | #---------------------------------------------------------------------- 37 | def onTrade(self, trade): 38 | """成交信息推送""" 39 | # 通用事件 40 | # 特定合约的成交事件 41 | pass 42 | 43 | #---------------------------------------------------------------------- 44 | def onOrder(self, order): 45 | """订单变化推送""" 46 | # 通用事件 47 | # 特定订单编号的事件 48 | pass 49 | 50 | #---------------------------------------------------------------------- 51 | def onPosition(self, position): 52 | """持仓信息推送""" 53 | # 通用事件 54 | event = Event(event_type=EVENT_POSITION) 55 | event.data = position 56 | self.eventEngine.put(event) 57 | 58 | #---------------------------------------------------------------------- 59 | def onAccount(self, account): 60 | """账户信息推送""" 61 | # 通用事件 62 | event = Event(event_type=EVENT_ACCOUNT) 63 | event.data = account 64 | self.eventEngine.put(event) 65 | 66 | #---------------------------------------------------------------------- 67 | def onError(self, error): 68 | """错误信息推送""" 69 | # 通用事件 70 | event = Event(event_type=EVENT_ERROR) 71 | event.data = error 72 | self.eventEngine.put(event) 73 | 74 | #---------------------------------------------------------------------- 75 | def onLog(self, log): 76 | """日志推送""" 77 | # 通用事件 78 | pass 79 | 80 | #---------------------------------------------------------------------- 81 | def onContract(self, contract): 82 | """合约基础信息推送""" 83 | # 通用事件 84 | pass 85 | 86 | #---------------------------------------------------------------------- 87 | def connect(self): 88 | """连接""" 89 | pass 90 | 91 | #---------------------------------------------------------------------- 92 | def subscribe(self, subscribeReq): 93 | """订阅行情""" 94 | pass 95 | 96 | #---------------------------------------------------------------------- 97 | def sendOrder(self, orderReq): 98 | """发单""" 99 | pass 100 | 101 | #---------------------------------------------------------------------- 102 | def cancelOrder(self, cancelOrderReq): 103 | """撤单""" 104 | pass 105 | 106 | #---------------------------------------------------------------------- 107 | def qryAccount(self): 108 | """查询账户资金""" 109 | pass 110 | 111 | #---------------------------------------------------------------------- 112 | def qryPosition(self): 113 | """查询持仓""" 114 | pass 115 | 116 | #---------------------------------------------------------------------- 117 | def close(self): 118 | """关闭""" 119 | pass 120 | 121 | class VtBaseData(object): 122 | """回调函数推送数据的基础类,其他数据类继承于此""" 123 | 124 | #---------------------------------------------------------------------- 125 | def __init__(self): 126 | """Constructor""" 127 | self.gatewayName = EMPTY_STRING # Gateway名称 128 | self.rawData = None # 原始数据 129 | 130 | class VtContractData(VtBaseData): 131 | """合约详细信息类""" 132 | 133 | #---------------------------------------------------------------------- 134 | def __init__(self): 135 | """Constructor""" 136 | super(VtContractData, self).__init__() 137 | 138 | self.symbol = EMPTY_STRING # 代码 139 | self.exchange = EMPTY_STRING # 交易所代码 140 | self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码,通常是 合约代码.交易所代码 141 | self.name = EMPTY_UNICODE # 合约中文名 142 | 143 | self.productClass = EMPTY_UNICODE # 合约类型 144 | self.size = EMPTY_INT # 合约大小 145 | self.priceTick = EMPTY_FLOAT # 合约最小价格TICK 146 | 147 | # 期权相关 148 | self.strikePrice = EMPTY_FLOAT # 期权行权价 149 | self.underlyingSymbol = EMPTY_STRING # 标的物合约代码 150 | self.optionType = EMPTY_UNICODE # 期权类型 151 | 152 | class VtTickData(VtBaseData): 153 | """Tick行情数据类""" 154 | 155 | #---------------------------------------------------------------------- 156 | def __init__(self): 157 | """Constructor""" 158 | super(VtTickData, self).__init__() 159 | 160 | # 代码相关 161 | self.symbol = EMPTY_STRING # 合约代码 162 | self.exchange = EMPTY_STRING # 交易所代码 163 | self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码,通常是 合约代码.交易所代码 164 | 165 | # 成交数据 166 | self.lastPrice = EMPTY_FLOAT # 最新成交价 167 | self.lastVolume = EMPTY_INT # 最新成交量 168 | self.volume = EMPTY_INT # 今天总成交量 169 | self.openInterest = EMPTY_INT # 持仓量 170 | self.time = EMPTY_STRING # 时间 11:20:56.5 171 | self.date = EMPTY_STRING # 日期 20151009 172 | 173 | # 常规行情 174 | self.openPrice = EMPTY_FLOAT # 今日开盘价 175 | self.highPrice = EMPTY_FLOAT # 今日最高价 176 | self.lowPrice = EMPTY_FLOAT # 今日最低价 177 | self.preClosePrice = EMPTY_FLOAT 178 | 179 | self.upperLimit = EMPTY_FLOAT # 涨停价 180 | self.lowerLimit = EMPTY_FLOAT # 跌停价 181 | 182 | # 五档行情 183 | self.bidPrice1 = EMPTY_FLOAT 184 | self.bidPrice2 = EMPTY_FLOAT 185 | self.bidPrice3 = EMPTY_FLOAT 186 | self.bidPrice4 = EMPTY_FLOAT 187 | self.bidPrice5 = EMPTY_FLOAT 188 | 189 | self.askPrice1 = EMPTY_FLOAT 190 | self.askPrice2 = EMPTY_FLOAT 191 | self.askPrice3 = EMPTY_FLOAT 192 | self.askPrice4 = EMPTY_FLOAT 193 | self.askPrice5 = EMPTY_FLOAT 194 | 195 | self.bidVolume1 = EMPTY_INT 196 | self.bidVolume2 = EMPTY_INT 197 | self.bidVolume3 = EMPTY_INT 198 | self.bidVolume4 = EMPTY_INT 199 | self.bidVolume5 = EMPTY_INT 200 | 201 | self.askVolume1 = EMPTY_INT 202 | self.askVolume2 = EMPTY_INT 203 | self.askVolume3 = EMPTY_INT 204 | self.askVolume4 = EMPTY_INT 205 | self.askVolume5 = EMPTY_INT 206 | 207 | class VtAccountData(VtBaseData): 208 | """账户数据类""" 209 | 210 | def __init__(self): 211 | """Constructor""" 212 | super(VtAccountData, self).__init__() 213 | 214 | # 账号代码相关 215 | self.accountID = EMPTY_STRING # 账户代码 216 | self.vtAccountID = EMPTY_STRING # 账户在vt中的唯一代码,通常是 Gateway名.账户代码 217 | 218 | # 数值相关 219 | self.preBalance = EMPTY_FLOAT # 昨日账户结算净值 220 | self.balance = EMPTY_FLOAT # 账户净值 221 | self.available = EMPTY_FLOAT # 可用资金 222 | self.commission = EMPTY_FLOAT # 今日手续费 223 | self.margin = EMPTY_FLOAT # 保证金占用 224 | self.closeProfit = EMPTY_FLOAT # 平仓盈亏 225 | self.positionProfit = EMPTY_FLOAT # 持仓盈亏 226 | 227 | class VtOrderData(VtBaseData): 228 | """订单数据类""" 229 | 230 | #---------------------------------------------------------------------- 231 | def __init__(self): 232 | """Constructor""" 233 | super(VtOrderData, self).__init__() 234 | 235 | # 代码编号相关 236 | self.symbol = EMPTY_STRING # 合约代码 237 | self.exchange = EMPTY_STRING # 交易所代码 238 | self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码,通常是 合约代码.交易所代码 239 | 240 | self.orderID = EMPTY_STRING # 订单编号 241 | self.vtOrderID = EMPTY_STRING # 订单在vt系统中的唯一编号,通常是 Gateway名.订单编号 242 | 243 | # 报单相关 244 | self.direction = EMPTY_UNICODE # 报单方向 245 | self.offset = EMPTY_UNICODE # 报单开平仓 246 | self.price = EMPTY_FLOAT # 报单价格 247 | self.totalVolume = EMPTY_INT # 报单总数量 248 | self.tradedVolume = EMPTY_INT # 报单成交数量 249 | self.status = EMPTY_UNICODE # 报单状态 250 | 251 | self.orderTime = EMPTY_STRING # 发单时间 252 | self.cancelTime = EMPTY_STRING # 撤单时间 253 | 254 | # CTP/LTS相关 255 | self.frontID = EMPTY_INT # 前置机编号 256 | self.sessionID = EMPTY_INT # 连接编号 257 | 258 | class VtTradeData(VtBaseData): 259 | """成交数据类""" 260 | 261 | #---------------------------------------------------------------------- 262 | def __init__(self): 263 | """Constructor""" 264 | super(VtTradeData, self).__init__() 265 | 266 | # 代码编号相关 267 | self.symbol = EMPTY_STRING # 合约代码 268 | self.exchange = EMPTY_STRING # 交易所代码 269 | self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码,通常是 合约代码.交易所代码 270 | 271 | self.tradeID = EMPTY_STRING # 成交编号 272 | self.vtTradeID = EMPTY_STRING # 成交在vt系统中的唯一编号,通常是 Gateway名.成交编号 273 | 274 | self.orderID = EMPTY_STRING # 订单编号 275 | self.vtOrderID = EMPTY_STRING # 订单在vt系统中的唯一编号,通常是 Gateway名.订单编号 276 | 277 | # 成交相关 278 | self.direction = EMPTY_UNICODE # 成交方向 279 | self.offset = EMPTY_UNICODE # 成交开平仓 280 | self.price = EMPTY_FLOAT # 成交价格 281 | self.volume = EMPTY_INT # 成交数量 282 | self.tradeTime = EMPTY_STRING # 成交时间 283 | 284 | class VtPositionData(VtBaseData): 285 | """持仓数据类""" 286 | 287 | #---------------------------------------------------------------------- 288 | def __init__(self): 289 | """Constructor""" 290 | super(VtPositionData, self).__init__() 291 | 292 | # 代码编号相关 293 | self.symbol = EMPTY_STRING # 合约代码 294 | self.exchange = EMPTY_STRING # 交易所代码 295 | self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码,合约代码.交易所代码 296 | 297 | # 持仓相关 298 | self.direction = EMPTY_STRING # 持仓方向 299 | self.position = EMPTY_INT # 持仓量 300 | self.frozen = EMPTY_INT # 冻结数量 301 | self.price = EMPTY_FLOAT # 持仓均价 302 | self.vtPositionName = EMPTY_STRING # 持仓在vt系统中的唯一代码,通常是vtSymbol.方向 303 | 304 | # 20151020添加 305 | self.ydPosition = EMPTY_INT # 昨持仓 306 | 307 | class VtErrorData(VtBaseData): 308 | """错误数据类""" 309 | 310 | #---------------------------------------------------------------------- 311 | def __init__(self): 312 | """Constructor""" 313 | super(VtErrorData, self).__init__() 314 | 315 | self.errorID = EMPTY_STRING # 错误代码 316 | self.errorMsg = EMPTY_UNICODE # 错误信息 317 | self.additionalInfo = EMPTY_UNICODE # 补充信息 318 | 319 | self.errorTime = time.strftime('%X', time.localtime()) # 错误生成时间 320 | 321 | class VtSubscribeReq(object): 322 | """订阅行情时传入的对象类""" 323 | 324 | #---------------------------------------------------------------------- 325 | def __init__(self): 326 | """Constructor""" 327 | self.symbol = EMPTY_STRING # 代码 328 | self.exchange = EMPTY_STRING # 交易所 329 | 330 | # 以下为IB相关 331 | self.productClass = EMPTY_UNICODE # 合约类型 332 | self.currency = EMPTY_STRING # 合约货币 333 | self.expiry = EMPTY_STRING # 到期日 334 | self.strikePrice = EMPTY_FLOAT # 行权价 335 | self.optionType = EMPTY_UNICODE # 期权类型 336 | 337 | class VtOrderReq(object): 338 | """发单时传入的对象类""" 339 | 340 | #---------------------------------------------------------------------- 341 | def __init__(self): 342 | """Constructor""" 343 | self.symbol = EMPTY_STRING # 代码 344 | self.exchange = EMPTY_STRING # 交易所 345 | self.price = EMPTY_FLOAT # 价格 346 | self.volume = EMPTY_INT # 数量 347 | 348 | self.priceType = EMPTY_STRING # 价格类型 349 | self.direction = EMPTY_STRING # 买卖 350 | self.offset = EMPTY_STRING # 开平 351 | 352 | # 以下为IB相关 353 | self.productClass = EMPTY_UNICODE # 合约类型 354 | self.currency = EMPTY_STRING # 合约货币 355 | self.expiry = EMPTY_STRING # 到期日 356 | self.strikePrice = EMPTY_FLOAT # 行权价 357 | self.optionType = EMPTY_UNICODE # 期权类型 358 | 359 | class VtCancelOrderReq(object): 360 | """撤单时传入的对象类""" 361 | 362 | #---------------------------------------------------------------------- 363 | def __init__(self): 364 | """Constructor""" 365 | self.symbol = EMPTY_STRING # 代码 366 | self.exchange = EMPTY_STRING # 交易所 367 | 368 | # 以下字段主要和CTP、LTS类接口相关 369 | self.orderID = EMPTY_STRING # 报单号 370 | self.frontID = EMPTY_STRING # 前置机号 371 | self.sessionID = EMPTY_STRING # 会话号 372 | 373 | class RingBuffer(object): 374 | """ class that implements a not-yet-full buffer """ 375 | def __init__(self, size_max = 60): 376 | self.max = size_max 377 | self.data = [] 378 | 379 | class __Full(object): 380 | """ class that implements a full buffer """ 381 | def append(self, x): 382 | """ Append an element overwriting the oldest one. """ 383 | self.data[self.cur] = x 384 | self.cur = (self.cur+1) % self.max 385 | def tolist(self): 386 | """ return list of elements in correct order. """ 387 | return self.data[self.cur:] + self.data[:self.cur] 388 | 389 | def append(self, x): 390 | """ append an element at the end of the buffer. """ 391 | self.data.append(x) 392 | if len(self.data) == self.max: 393 | self.cur = 0 394 | #当队列满时将self的类从非满改为满 395 | self.__class__ = self.__Full 396 | def tolist(self): 397 | """ Return a list of elements from the oldest to the newest. """ 398 | return self.data 399 | -------------------------------------------------------------------------------- /ftsVob/quantStrategy/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategyTemplate import StrategyTemplate 2 | -------------------------------------------------------------------------------- /ftsVob/quantStrategy/strategyTemplate.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import sys 3 | import traceback 4 | 5 | from ..quantAlgo import AlgoTrade 6 | 7 | class StrategyTemplate: 8 | name = 'DefaultStrategyTemplate' 9 | 10 | def __init__(self, gateway, log_handler, main_engine): 11 | self.gateway = gateway 12 | self.main_engine = main_engine 13 | 14 | # 优先使用自定义 log 句柄, 否则使用主引擎日志句柄 15 | self.log = self.log_handler() or log_handler 16 | 17 | # 每个策略对应一个Algo类 18 | self.algo = AlgoTrade(gateway, main_engine.event_engine) 19 | 20 | self.init() 21 | 22 | def init(self): 23 | # 进行相关的初始化操作 24 | pass 25 | 26 | def strategy(self, event): 27 | pass 28 | 29 | def run(self, event): 30 | try: 31 | self.strategy(event) 32 | except: 33 | exc_type, exc_value, exc_traceback = sys.exc_info() 34 | self.log.error(repr(traceback.format_exception(exc_type, 35 | exc_value, 36 | exc_traceback))) 37 | 38 | def clock(self, event): 39 | pass 40 | 41 | def log_handler(self): 42 | """ 43 | 优先使用在此自定义 log 句柄, 否则返回None, 并使用主引擎日志句柄 44 | :return: log_handler or None 45 | """ 46 | return None 47 | -------------------------------------------------------------------------------- /strategies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EGQM/ftsVob/0ed69bbe6bdf4767252b66865647679a50ac9368/strategies/__init__.py -------------------------------------------------------------------------------- /strategies/demo.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from ftsVob.quantGateway.quant_constant import * 3 | from ftsVob.quantGateway.quant_gateway import * 4 | 5 | from ftsVob import StrategyTemplate 6 | from ftsVob import DefaultLogHandler 7 | from ftsVob import AlgoTrade 8 | 9 | class Strategy(StrategyTemplate): 10 | name = 'Cta strategy' 11 | 12 | def strategy(self, event): 13 | self.log.info(u'Cta 策略触发') 14 | self.log.info(event.data) 15 | self.log.info('\n') 16 | 17 | def log_handler(self): 18 | return DefaultLogHandler(name=__name__, log_type='file') 19 | 20 | def run(self, event): 21 | self.log.info(event.data) 22 | orderreq = VtOrderReq() 23 | orderreq.symbol = 'IF1609' 24 | orderreq.volume = 5 25 | orderreq.priceType = PRICETYPE_FOK 26 | orderreq.direction = DIRECTION_SHORT 27 | orderreq.offset = OFFSET_OPEN 28 | self.algo.twap(1, orderreq) 29 | """ 30 | if 'position' in event.data: 31 | poslist = event.data['position'] 32 | for elt in poslist: 33 | if elt['PosiDirection'] == '3': 34 | direct = DIRECTION_LONG 35 | if elt['PosiDirection'] == '2': 36 | direct = DIRECTION_SHORT 37 | offset = OFFSET_CLOSE 38 | if elt['Position'] > 0: 39 | req = ReqObj(elt['InstrumentID'], 40 | elt['SettlementPrice'], 41 | elt['Position'], 42 | PRICETYPE_FOK, 43 | direct, 44 | offset) 45 | self.gateway.sendOrder(req) 46 | else: 47 | self.log.info('Position volume is zero') 48 | """ 49 | --------------------------------------------------------------------------------