class DemoCallback: def __init__(self): self.api = TqApi() self.quote = self.api.get_quote("SHFE.cu1805") def on_data_update(self): if self.quote.get("last_price", 0) > 1000: self.api.insert_order(symbol="SHFE.cu1805", direction="BUY", offset="OPEN", volume=1, limit_price=30000) def run(self): self.api.run(self.on_data_update)
class DemoTask: def __init__(self): self.api = TqApi() self.tm = TaskManager(self.api) def task_main(self): print("start") quote = self.api.get_quote("SHFE.cu1805") while True: wait_result = yield { "QUOTE_CHANGED": lambda: self.api.is_changing(quote), "TIMEOUT": 0.2, } if wait_result["QUOTE_CHANGED"]: print("Quote", quote) if wait_result["TIMEOUT"]: print("Timeout") def run(self): self.tm.start_task(self.task_main()) self.api.run()
class DemoSpread: def __init__(self): self.api = TqApi() self.tm = TaskManager(self.api) def task_main(self): print ("start") symbol_a = "SHFE.cu1805" symbol_b = "SHFE.cu1806" quote_a = self.api.get_quote(symbol_a) quote_b = self.api.get_quote(symbol_b) buy_open_spread = 50000 sell_close_spread = -70000 max_volume = 5 long_volume = 0 while True: wait_result = yield { "BUY_OPEN": lambda: long_volume == 0 and quote_a["ask_price1"] - quote_b["bid_price1"] < buy_open_spread, "SELL_CLOSE": lambda: long_volume > 0 and quote_a["bid_price1"] - quote_b["ask_price1"] > sell_close_spread, } if wait_result["BUY_OPEN"]: task_a = self.tm.start_task(make_order_until_all_matched(self.api, symbol=symbol_a, direction="BUY", offset="OPEN", volume=max_volume)) task_b = self.tm.start_task(make_order_until_all_matched(self.api, symbol=symbol_b, direction="SELL", offset="OPEN", volume=max_volume)) long_volume = max_volume if wait_result["SELL_CLOSE"]: task_a = self.tm.start_task(make_order_until_all_matched(self.api, symbol=symbol_a, direction="SELL", offset="CLOSE", volume=max_volume)) task_b = self.tm.start_task(make_order_until_all_matched(self.api, symbol=symbol_b, direction="BUY", offset="CLOSE", volume=max_volume)) long_volume = 0 wait_subtask_finish = yield { "ANY_TASK_ERROR": lambda: self.tm.get_error(task_a) or self.tm.get_error(task_b), "BOTH_TASK_FINSISH": lambda: self.tm.is_finish(task_a) and self.tm.is_finish(task_b), } if wait_subtask_finish["ANY_TASK_ERROR"]: break print ("finish") def run(self): self.tm.start_task(self.task_main()) self.api.run()
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'chengzhi' from tqsdk.api import TqApi from tqsdk.lib import TargetPosTask ''' 价差回归 当近月-远月的价差大于200时做空近月,做多远月 当价差小于150时平仓 ''' api = TqApi("SIM") quote_near = api.get_quote("SHFE.rb1810") quote_deferred = api.get_quote("SHFE.rb1901") # 创建 rb1810 的目标持仓 task,该 task 负责调整 rb1810 的仓位到指定的目标仓位 target_pos_near = TargetPosTask(api, "SHFE.rb1810") # 创建 rb1901 的目标持仓 task,该 task 负责调整 rb1901 的仓位到指定的目标仓位 target_pos_deferred = TargetPosTask(api, "SHFE.rb1901") while True: api.wait_update() if api.is_changing(quote_near) or api.is_changing(quote_deferred): spread = quote_near["last_price"] - quote_deferred["last_price"] print("当前价差:", spread) if spread > 200: print("目标持仓: 空近月,多远月") # 设置目标持仓为正数表示多头,负数表示空头,0表示空仓 target_pos_near.set_target_volume(-1) target_pos_deferred.set_target_volume(1) elif spread < 150: print("目标持仓: 空仓")
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'chengzhi' from tqsdk.api import TqApi api = TqApi("SIM") quote = api.get_quote("SHFE.cu1812") while True: api.wait_update() # 如果 cu1812 的任何字段有变化,is_changing就会返回 True if api.is_changing(quote): print(quote) # 只有当 cu1812 的最新价有变化,is_changing才会返回 True if api.is_changing(quote, "last_price"): print("最新价变化", quote["last_price"]) # 当 cu1812 的买1价/买1量/卖1价/卖1量中任何一个有变化,is_changing都会返回 True if api.is_changing( quote, ["ask_price1", "ask_volume1", "bid_price1", "bid_volume1"]): print("盘口变化", quote["ask_price1"], quote["ask_volume1"], quote["bid_price1"], quote["bid_volume1"])
def dual_thrust(quote, klines): current_open = quote["open"] HH = max(klines.high[-Nday - 1:-1]) # N日最高价的最高价 HC = max(klines.close[-Nday - 1:-1]) # N日收盘价的最高价 LC = min(klines.close[-Nday - 1:-1]) # N日收盘价的最低价 LL = min(klines.low[-Nday - 1:-1]) # N日最低价的最低价 range = max(HH - LC, HC - LL) buy_line = current_open + range * K1 # 上轨 sell_line = current_open - range * K2 # 下轨 print("当前开盘价:", current_open, "\n上轨:", buy_line, "\n下轨:", sell_line) return buy_line, sell_line api = TqApi("SIM") quote = api.get_quote(symbol) klines = api.get_kline_serial(symbol, 24 * 60 * 60) # 86400使用日线 target_pos = TargetPosTask(api, symbol) buy_line, sell_line = dual_thrust(quote, klines) # 获取上下轨 while True: api.wait_update() if api.is_changing(klines[-1], "datetime") or api.is_changing( quote, "open"): # 新产生一根日线或开盘价发生变化: 重新计算上下轨 buy_line, sell_line = dual_thrust(quote, klines) if api.is_changing(quote, "last_price"): print("最新价变化", quote["last_price"], end=':') if quote["last_price"] > buy_line: # 高于上轨 print("高于上轨,目标持仓 多头3手") target_pos.set_target_volume(3) # 交易
def vwap_table(api: TqApi, symbol: str, target_pos: int, duration: float, account: Optional[Union[TqAccount, TqKq, TqSim]] = None): """ 返回基于 vwap 策略的计划任务时间表。下单需要配合 TargetPosScheduler 使用。 调用 vwap_table 函数,根据以下逻辑生成 time_table: 1. 根据 target_pos - 当前合约的净持仓,得到总的需要调整手数 2. 请求 symbol 合约的 ``1min`` K 线 3. 采样取用最近 10 日内,以合约当前行情时间的下一分钟为起点,每日 duration / 60 根 K 线, \ 例如当前合约时间为 14:35:35,那么采样是会使用 14:36:00 开始的分钟线 K 线 4. 按日期分组,分别计算交易日内,每根 K 线成交量占总成交量的比例 5. 计算最近 10 日内相同分钟内的成交量占比的算术平均数,将第 1 步得到的总调整手数按照得到的比例分配 6. 每一分钟,前 58s 以追加价格下单,后 2s 以对价价格下单 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 拟下单的合约 symbol, 格式为 交易所代码.合约代码, 例如 "SHFE.cu2201" target_pos (int): 目标持仓手数 duration (int): 算法执行的时长,以秒为单位,必须是 60 的整数倍,时长可以跨非交易时间段,但是不可以跨交易日 * 设置为 60*10, 可以是 10:10~10:15 + 10:30~10:35 account (TqAccount/TqKq/TqSim): [可选]指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 Returns: pandas.DataFrame: 本函数返回一个 pandas.DataFrame 实例. 表示一份计划任务时间表。每一行表示一项目标持仓任务,包含以下列: + interval: 当前这项任务的持续时间长度,单位为秒 + target_pos: 当前这项任务的目标持仓 + price: 当前这项任务的下单价格模式,支持 PASSIVE(排队价),ACTIVE(对价),None(不下单,表示暂停一段时间) Example1:: from tqsdk import TqApi, TargetPosScheduler from tqsdk.algorithm import vwap_table api = TqApi(auth="信易账户,用户密码") quote = api.get_quote("CZCE.MA109") # 设置 vwap 任务参数 time_table = vwap_table(api, "CZCE.MA109", -100, 600) # 目标持仓 -100 手,600s 内完成 print(time_table.to_string()) target_pos_sch = TargetPosScheduler(api, "CZCE.MA109", time_table) # 启动循环 while not target_pos_sch.is_finished(): api.wait_update() api.close() """ account = api._account._check_valid(account) if account is None: raise Exception(f"多账户模式下, 需要指定账户实例 account") TIME_CELL = 60 # 等时长下单的时间单元, 单位: 秒 HISTORY_DAY_LENGTH = 10 # 使用多少天的历史数据用来计算每个时间单元的下单手数 if duration % TIME_CELL or duration < 60: raise Exception(f"duration {duration} 参数应该为 {TIME_CELL} 的整数倍") pos = account.get_position(symbol) target_pos = int(target_pos) delta_pos = target_pos - pos.pos target_volume = abs(delta_pos) # 总的下单手数 if target_volume == 0: return DataFrame(columns=['interval', 'target_pos', 'price']) # 获取 Kline klines = api.get_kline_serial(symbol, TIME_CELL, data_length=int(10 * 60 * 60 / TIME_CELL * HISTORY_DAY_LENGTH)) klines["time"] = klines.datetime.apply( lambda x: datetime.fromtimestamp(x // 1000000000).time()) # k线时间 klines["date"] = klines.datetime.apply(lambda x: datetime.fromtimestamp( _get_trading_day_from_timestamp(x) // 1000000000).date()) # k线交易日 quote = api.get_quote(symbol) # 当前交易日完整的交易时间段 trading_timestamp = _get_trading_timestamp(quote, quote.datetime) trading_timestamp_nano_range = trading_timestamp[ 'night'] + trading_timestamp['day'] # 当前交易日完整的交易时间段 # 当前时间 行情时间 current_timestamp_nano = _get_trade_timestamp(quote.datetime, float('nan')) if not trading_timestamp_nano_range[0][ 0] <= current_timestamp_nano < trading_timestamp_nano_range[-1][1]: raise Exception("当前时间不在指定的交易时间段内") current_datetime = datetime.fromtimestamp(current_timestamp_nano // 1000000000) # 下一分钟的开始时间 next_datetime = current_datetime.replace(second=0) + timedelta(minutes=1) start_datetime_nano = int(next_datetime.timestamp()) * 1000000000 r = _rangeset_head( _rangeset_slice(trading_timestamp_nano_range, start_datetime_nano), int(duration * 1e9)) if not (r and trading_timestamp_nano_range[0][0] <= r[-1][-1] < trading_timestamp_nano_range[-1][1]): raise Exception("指定时间段超出当前交易日") start_datetime = datetime.fromtimestamp(start_datetime_nano // 1000000000) end_datetime = datetime.fromtimestamp((r[-1][-1] - 1) // 1000000000) time_slot_start = time(start_datetime.hour, start_datetime.minute) # 计划交易时段起始时间点 time_slot_end = time(end_datetime.hour, end_datetime.minute) # 计划交易时段终点时间点 if time_slot_end > time_slot_start: # 判断是否类似 23:00:00 开始, 01:00:00 结束这样跨天的情况 klines = klines[(klines["time"] >= time_slot_start) & (klines["time"] <= time_slot_end)] else: klines = klines[(klines["time"] >= time_slot_start) | (klines["time"] <= time_slot_end)] # 获取在预设交易时间段内的所有K线, 即时间位于 time_slot_start 到 time_slot_end 之间的数据 need_date = klines['date'].drop_duplicates()[-HISTORY_DAY_LENGTH:] klines = klines[klines['date'].isin(need_date)] grouped_datetime = klines.groupby(['date', 'time'])['volume'].sum() # 计算每个交易日内的预设交易时间段内的成交量总和(level=0: 表示按第一级索引"data"来分组)后,将每根k线的成交量除以所在交易日内的总成交量,计算其所占比例 volume_percent = grouped_datetime / grouped_datetime.groupby(level=0).sum() predicted_percent = volume_percent.groupby( level=1).mean() # 将历史上相同时间单元的成交量占比使用算数平均计算出预测值 # 计算每个时间单元的成交量预测值 time_table = DataFrame(columns=['interval', 'volume', 'price']) volume_left = target_volume # 剩余手数 percent_left = 1 # 剩余百分比 for index, value in predicted_percent.items(): volume = round(target_volume * (value / percent_left)) volume_left -= volume percent_left -= value append_time_table = pd.DataFrame([{ "interval": TIME_CELL - 2, "volume": volume, "price": "PASSIVE" }, { "interval": 2, "volume": 0, "price": "ACTIVE" }]) time_table = pd.concat([time_table, append_time_table], ignore_index=True) time_table['volume'] = time_table['volume'].mul(np.sign(delta_pos)) time_table['target_pos'] = time_table['volume'].cumsum() time_table['target_pos'] = time_table['target_pos'].add(pos.pos) time_table.drop(columns=['volume'], inplace=True) time_table = time_table.astype({ 'target_pos': 'int64', 'interval': 'float64' }) return time_table