#encoding=utf-8
# 本策略仅为TFast策略云服务的策略技术开发示例,不视为投资建议
"""
开盘追涨的策略逻辑:
(1)买入信号:开盘高开8%以上、买一量 > 1000000的股票,以涨停价买入
(2)卖出信号:到达撤单时间后,撤掉所有未成交委托
"""
import time, os, signal
from datetime import datetime
from kungfu.wingchun.constants import *
SSE, SZE, BUY, LIMIT, OPEN = Exchange.SSE, Exchange.SZE, Side.Buy, PriceType.Limit, Offset.Open
MD_SOURCE, TD_SOURCE, ACCOUNT = 'sim', 'sim', '123123123**'
# 简化策略逻辑:只买入固定数量的VOLUME,以涨停板价格追入
VOLUME = 4000
# 开盘价比昨收价,高开8个点
HIGHER_PERCENT = 1 + (8 / 100)
# 开盘时,买一位置上的封单数量(1000股)
BID1_VOL = 1000
now = datetime.now()
# 选股开始时间
calc_tm = datetime.strptime(f'{now.year}-{now.month}-{now.day} 09:25:00', '%Y-%m-%d %H:%M:%S').timestamp() # 开盘时间9:25 开始,选强势股
# 撤单开始时间
stop_tm = datetime.strptime(f'{now.year}-{now.month}-{now.day} 09:32:00', '%Y-%m-%d %H:%M:%S').timestamp() # 当日 未成交的 追涨停买单的撤单时间(避免未成交订单暴露在市场中产生风险)
# 启动前回调,添加交易账户,订阅行情,策略初始化计算等
def pre_start(ctx):
ctx.add_account(TD_SOURCE, ACCOUNT)
for market in (MarketType.kSSE, MarketType.kSZSE): # 全市场订阅,选强势股
ctx.subscribe_all(MD_SOURCE, market, SubscribeCategoryType.kStock, SubscribeSecuDataType.kSnapshot) # 只订阅股票、只订阅快照(盘口)
ctx.orders = {} # 开盘追涨停订单的保存 symbol -> [order_id, 7.77, 2000] [订单ID, 挂单价格, 挂单数量]
ctx.canceled = False # 是否对未成交的买单,发起过撤单
print('pre_start')
ctx.log.info('pre_start()')
# 启动准备工作完成后回调,策略只能在本函数回调以后才能进行获取持仓和报单
def post_start(ctx):
book = ctx.get_account_book(TD_SOURCE, ACCOUNT)
ctx.log.info(f'[可用资金]: {book.asset.avail}') # 这里可用检查一下资金账户的金额是否足够
ctx.log.warning("post_start()")
# 用户自定义的因子计算函数,可以利用 订阅的股票盘口(Quote对象),进一步筛选出 下单标的
def symbol_filter(quote):
symbol = quote.instrument_id
# 对每一个symbol(非科创、非创业、非B股),利用 开盘价、买一量进行筛选
return not symbol.startswith('688') and not symbol[0] in ('2', '3') and int(symbol) % 10 == 1 and quote.open_price / quote.pre_close_price >= HIGHER_PERCENT and quote.bid_volume[0] >= BID1_VOL
# 收到快照行情时回调,行情信息通过quote对象获取
def on_quote(ctx, quote):
if calc_tm * 1000000000 <= quote.data_time < stop_tm * 1000000000: # 收到开盘(9:25)的第一个盘口Quote对象
# 有可能:开盘价是零,缺少昨收价,没有人挂买单,等等异常情况;此时不做处理,直到出现一个正常的盘口
if quote.open_price>0 and quote.pre_close_price>0 and len(quote.bid_volume)>0:
symbol = quote.instrument_id
if symbol not in ctx.orders: # 当日已下过买单的symbol, 直接略过
if symbol_filter(quote): # 对盘口进行计算,如果满足 用户自定义的追涨停条件
ctx.log.warning(f'开盘追涨停挂单: {quote.exchange_id}.{symbol}, 开盘价偏离幅度: {round(quote.open_price / quote.pre_close_price, 2)}, 买一封单: {quote.bid_volume[0]}')
oid = ctx.insert_order(symbol, SSE if quote.exchange_id=='SSE' else SZE, ACCOUNT, quote.upper_limit_price, VOLUME, LIMIT, BUY, OPEN)
if oid > 0:
ctx.orders[symbol] = [oid, quote.upper_limit_price, VOLUME]
elif stop_tm * 100000000 <= quote.data_time: # 到了撤单时间 and 还未发起过撤单
if not ctx.canceled:
ctx.log.info('到了撤单时间, 撤掉所有的买入挂单')
for symbol in ctx.orders: # 把未成交的买单,全部撤回
oid, _, quantity = ctx.orders[symbol]
ctx.cancel_order(oid)
ctx.canceled = True
# 订单信息返回
def on_order(ctx, order):
symbol = order.instrument_id
if order.status == OrderStatus.Error: # 如果买单报盘,出现错误,就删除掉
del ctx.orders[symbol]
elif order.status in (OrderStatus.Cancelled, OrderStatus.PartialFilledNotActive): # 撤单成功、部成部撤
ctx.orders[symbol][-1] -= order.volume_left # 从下单总数量里,减掉 撤单成功的数量
if ctx.orders[symbol][-1] == 0: # 撤单成交,导致下单总数量为零
del ctx.orders[symbol]
if not ctx.orders and time.time() >= stop_tm:
ctx.log.info('今日追涨停结束,策略退出')
os.kill(os.getpid(), signal.SIGINT)
# 订单成交信息返回
def on_trade(ctx, trade):
symbol = trade.instrument_id
ctx.orders[symbol][-1] -= trade.volume # 从下单总数量里,减掉 成交的数量
if ctx.orders[symbol][-1] == 0: # 成交导致下单总数量为零
del ctx.orders[symbol]
if not ctx.orders and time.time() >= stop_tm:
ctx.log.info('今日追涨停结束,策略退出')
os.kill(os.getpid(), signal.SIGINT)
#encoding=utf-8
# 本策略仅为TFast策略云服务的策略技术开发示例,不视为投资建议。
"""
日内T0的策略逻辑:
(1)买入信号:最新价小于MA5
(2)卖出信号:盈利高于(1+PROFIT_RATIO+FEE_RATIO+TAX_RATIO)或收盘前卖出平仓
"""
import os, signal, time
from datetime import datetime, timedelta
from kungfu.wingchun.constants import *
EXCHANGE, BUY, SELL, LIMIT, LONG, OPEN, CLOSE = Exchange.SSE, Side.Buy, Side.Sell, PriceType.Limit, Direction.Long, Offset.Open, Offset.Close
MD_SOURCE, TD_SOURCE, ACCOUNT = 'sim', 'sim', '123123123**'
# 每次出现买入信号时,只买入固定数量的VOLUME
SYMBOL, VOLUME, PROFIT_RATIO, FEE_RATIO, TAX_RATIO = '6880**', 4000, 3/100, 1/10000, 1/1000
# MA均线步长(比如5, 10, 15, 20)
STEP = 5
# 距离收盘的前5分钟内,强制卖出平仓
LAST_TIME_FRAME = 5
# 以第五档买盘价格,强制卖出平仓当日所有的买入
SELL_PRICE_LEVEL = 4
# 计算收盘时间
now = datetime.now()
closing_time = datetime.strptime(f'{now.year}-{now.month}-{now.day} 15:00:00', '%Y-%m-%d %H:%M:%S')
# 计算卖出的deadline
sell_deadline = closing_time - timedelta(minutes=LAST_TIME_FRAME)
#################################################################
# 启动前回调,添加交易账户,订阅行情,策略初始化计算等
def pre_start(ctx):
ctx.add_account(TD_SOURCE, ACCOUNT)
ctx.subscribe(MD_SOURCE, [SYMBOL], EXCHANGE) # 订阅盘口Depth
ctx.subscribe('bar', [SYMBOL], EXCHANGE) # 订阅K线,用K线来计算MA的价格
ctx.bar_close_prices = [] # K线(收盘价)的保存
ctx.moving_average_prices = [] # 移动平均线(价格)的保存
ctx.orders = { BUY:{}, SELL:{} } # 日内所下订单的保存 BUY -> { 7.77: {id1: 4000, id2: 2000}, 7.3: {id3: 1000} } 数量为:待成交的数量
ctx.last_sell_order = {} # 保存最后强制卖出的订单
ctx.dealed_orders = {BUY:[], SELL:[]} # 日内成交订单的保存: BUY -> [ [id1, 7.77, 2000], [id2, 7.77, 1000], [id3, 7.3, 500] ]
# 判断 可用资金 / 持仓 情况
def check_account(ctx):
ctx.my_book = ctx.get_account_book(TD_SOURCE, ACCOUNT)
if ctx.my_book.asset.avail > 0:
ctx.log.info(f'[可用资金]: {ctx.my_book.asset.avail}')
else:
ctx.log.error(f'账户{ACCOUNT}没有可用资金, T0策略退出执行')
os.kill(os.getpid(), signal.SIGINT)
if ctx.my_book.has_long_position(EXCHANGE, SYMBOL):
ctx.position = ctx.my_book.get_position(LONG, EXCHANGE, SYMBOL)
ctx.yesterday_volume = ctx.position.yesterday_volume
ctx.log.warning(f'[可用持仓]: {ctx.yesterday_volume}')
else:
ctx.log.warning(f'账户{ACCOUNT}没有{SYMBOL}持仓, T0策略退出执行')
os.kill(os.getpid(), signal.SIGINT)
# 启动准备工作完成后回调,策略只能在本函数回调以后才能进行获取持仓和报单
def post_start(ctx):
ctx.log.warning('post_start()')
check_account(ctx)
# 用户(根据盘口行情)自定义的日内的买入信号
def buy_signal(ctx, quote):
return ctx.moving_average_prices and quote.last_price < ctx.moving_average_prices[-1]
# 收到盘口|快照行情时回调,行情信息通过quote对象获取
def on_quote(ctx, quote):
# quote.last_price = round( random.uniform(35, 39), 2)
ctx.log.info(f'=====[on_quote] {quote.last_price}')
# 当可用持仓数量>0 and 可用资金>0 and 出现了买入信号
if ctx.yesterday_volume > 0 and ctx.my_book.asset.avail > 0 and buy_signal(ctx, quote):
price = quote.last_price
oid = ctx.insert_order(SYMBOL, EXCHANGE, ACCOUNT, price, VOLUME, LIMIT, BUY, OPEN) # 下买单
# 记录每笔买单的 价格|订单号|数量
if price not in ctx.orders[BUY]:
ctx.orders[BUY][price] = {oid: VOLUME}
else:
ctx.orders[BUY][price][oid] = VOLUME
ctx.log.info(f'日内订单: {ctx.orders}')
now = datetime.now()
if sell_deadline <= now < closing_time: # 到了最后的卖出deadline
ctx.log.info(f'即将收盘, {now}, 强制卖出平仓') # T0的关键:日终时,保持标的持仓数量不变
# 把当日下的未成交的买单,全部撤回
for price in ctx.orders[BUY]:
for oid in ctx.orders[BUY][price]:
ctx.cancel_order(oid)
ctx.orders[BUY][price].clear()
ctx.orders[BUY].clear()
# 把当日下的未成交的卖单,全部撤回;并记录每一笔卖单剩余的数量
vol_to_sell = 0
for price in ctx.orders[SELL]:
for oid in ctx.orders[SELL][price]:
ctx.cancel_order(oid)
vol_to_sell += ctx.orders[SELL][price][oid]
ctx.orders[SELL][price].clear()
ctx.orders[SELL].clear()
# 把未成交的ctx.last_sell_order也撤回,并记录剩余的数量
for oid in ctx.last_sell_order:
ctx.cancel_order(oid)
vol_to_sell += ctx.last_sell_order[oid]
ctx.last_sell_order.clear()
# 以此时的第五档买盘价格,强制卖出平仓
order_id = ctx.insert_order(SYMBOL, EXCHANGE, ACCOUNT, quote.bid_price[SELL_PRICE_LEVEL], vol_to_sell, LIMIT, SELL, CLOSE)
# 记录最后一笔下单的 价格|订单号|数量
ctx.last_sell_order[order_id] = vol_to_sell
ctx.log.warning(f'下最后一笔卖单: {order_id}')
# 收到K线行情时回调,行情信息通过bar对象获取;需要通过配置菜单,提前打开K线计算选项
def on_bar(ctx, bar):
ctx.log.info(f'[on_bar] {bar}')
if len(ctx.bar_close_prices) == STEP:
ctx.bar_close_prices.pop(0)
ctx.bar_close_prices.append(bar.close)
last_avg_price = sum(ctx.bar_close_prices) / len(ctx.bar_close_prices)
ctx.moving_average_prices.append( last_avg_price )
ctx.log.info(f'最新的移动平均线价格: {last_avg_price}')
# 订单成交信息返回
def on_trade(ctx, trade):
ctx.log.info(f'[on_trade] {trade}')
ctx.dealed_orders[trade.side].append( [trade.order_id, trade.price, trade.volume] )
if 'order_id' in ctx.last_sell_order and ctx.last_sell_order['order_id'] == trade.order_id:
ctx.yesterday_volume -= trade.volume # 更新可用的仓位数量
ctx.log.warning(f'可用持仓: {ctx.yesterday_volume}')
ctx.last_sell_order['order_id'] -= trade.volume
if ctx.last_sell_order['order_id'] == 0:
ctx.log.info(f'最后强制卖出的订单全部成交,今日T0结束')
ctx.log.info(f'今日成交情况:')
ctx.log.info(ctx.dealed_orders)
os.kill(os.getpid(), signal.SIGINT)
else:
# 更新今日所下订单
orders = ctx.orders[trade.side][trade.price]
orders[trade.order_id] -= trade.volume
if orders[trade.order_id] == 0: # 订单ID对应的下单数量,全部成交了,就从orders里,删除这笔订单
del orders[trade.order_id]
if not orders:
del ctx.orders[trade.side][trade.price]
if trade.side == SELL:
ctx.yesterday_volume -= trade.volume # 更新可用的仓位数量
ctx.log.warning(f'可用持仓: {ctx.yesterday_volume}')
else:
sell_price = round( trade.price * (1+PROFIT_RATIO+FEE_RATIO+TAX_RATIO), 2) # 做T的卖出价格
sell_vol = trade.volume
sell_oid = ctx.insert_order(SYMBOL, EXCHANGE, ACCOUNT, sell_price, sell_vol, LIMIT, SELL, CLOSE) # 下单卖出做T
# 记录:每笔卖单的 价格|订单号|数量
if sell_price not in ctx.orders[SELL]:
ctx.orders[SELL][sell_price] = {sell_oid: sell_vol}
else:
ctx.orders[SELL][sell_price][sell_oid] = sell_vol
ctx.log.info(f'买单成交:{trade.order_id}, {trade.price}, {trade.volume}; 下卖单做T:{sell_oid')