class POSITION: def __init__(self, platform, instrument_id, time_frame): self.__platform = platform self.__instrument_id = instrument_id self.__time_frame = time_frame self.__market = MARKET(self.__platform, self.__instrument_id, self.__time_frame) def direction(self): """获取当前持仓方向""" result = self.__platform.get_position()['direction'] return result def amount(self): """获取当前持仓数量""" result = self.__platform.get_position()['amount'] return result def price(self): """获取当前的持仓价格""" result = self.__platform.get_position()['price'] return result def coverlong_profit(self): """计算平多的单笔交易利润""" self.__value = self.__market.contract_value() # 合约面值 result = (self.__market.last() - self.price()) * (self.amount() * self.__value) return result def covershort_profit(self): """计算平空的单笔交易利润""" self.__value = self.__market.contract_value() # 合约面值 result = (self.price() - self.__market.last()) * (self.amount() * self.__value) return result
def __init__(self, instrument_id, time_frame, start_asset): # 策略初始化时需传入合约id、k线周期、初始资金参数 print("{} {} 海龟交易策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads("config.json") # 载入配置文件 self.instrument_id = instrument_id # 合约id self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id, leverage=20) # 初始化交易所 self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化position self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators self.database = "回测" # 如从purequant服务器的数据库上获取历史k线数据进行回测,必须为"回测" self.datasheet = self.instrument_id.split("-")[0].lower() + "_" + time_frame # 数据表 if config.first_run == "true": # 程序第一次启动时保存数据,实盘时如策略中止再重启时,可以将配置文件中的first_run改成"false",程序再次启动会直接读取数据库中保存的数据 storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金、总盈亏数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 # 一些策略参数 self.contract_value = self.market.contract_value() # 合约面值 self.ATRLength = 20 # 平均波动周期 self.boLength = 20 # 短周期 BreakOut Length self.fsLength = 55 # 长周期 FailSafe Length self.teLength = 10 # 离市周期 Trailing Exit Length self.LastProfitableTradeFilter = 1 # 使用入市过滤条件 self.PreBreakoutFailure = False # 前一次是否突破失败 self.CurrentEntries = 0 # 当前持仓的开仓次数 self.counter = 0 # 计数器,用以控制单根bar最大交易次数
def __init__(self, instrument_id, time_frame, fast_length, slow_length, long_stop, short_stop, start_asset): config.loads('config.json') # 载入配置文件 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 初始化交易所 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化potion self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators # 在第一次运行程序时,将初始资金数据保存至数据库中 self.database = "回测" # 无论实盘或回测,此处database名称可以任意命名 self.datasheet = self.instrument_id.split("-")[0].lower() + "_" + time_frame if config.first_run: storage.mysql_save_strategy_run_info(self.database, self.datasheet, "策略参数为" + str(fast_length) + "&" + str(slow_length), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.counter = 0 # 计数器 self.fast_length = fast_length # 短周期均线长度 self.slow_length = slow_length # 长周期均线长度 self.long_stop = long_stop # 多单止损幅度 self.short_stop = short_stop # 空单止损幅度 self.total_profit = 0 self.contract_value = self.market.contract_value() # 合约面值,每次获取需发起网络请求,故于此处声明变量,优化性能 # 声明持仓方向、数量与价格变量,每次开平仓后手动重新赋值 self.hold_direction = "none" self.hold_amount = 0 self.hold_price = 0 print("{} {} 双均线多空策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息
def __init__(self, instrument_id, time_frame, fast_length, slow_length, long_stop, short_stop, start_asset): try: print("{} {} 双均线多空策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads('config.json') # 载入配置文件 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 初始化交易所 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化potion self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators # 在第一次运行程序时,将初始资金数据保存至数据库中 self.database = "回测" # 回测时必须为"回测" self.datasheet = self.instrument_id.split("-")[0].lower() + "_" + time_frame if config.first_run == "true": storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 self.counter = 0 # 计数器 self.fast_length = fast_length # 短周期均线长度 self.slow_length = slow_length # 长周期均线长度 self.long_stop = long_stop # 多单止损幅度 self.short_stop = short_stop # 空单止损幅度 self.contract_value = self.market.contract_value() # 合约面值,每次获取需发起网络请求,故于此处声明变量,优化性能 except: logger.warning()
def __init__(self, platform, symbol, time_frame): self.__platform = platform self.__symbol = symbol self.__time_frame = time_frame self.__market = MARKET(self.__platform, self.__symbol, self.__time_frame) # pull some data self.__indicators = INDICATORS(self.__platform, self.__symbol, self.__time_frame) self.__kline = platform.get_kline(self.__time_frame) self.__kline.reverse() # format it in pandas try: # dataframe有7列的情况 self.__df = pd.DataFrame(self.__kline, columns=[ 'time', 'open', 'high', 'low', 'close', 'volume', 'currency_volume' ]) self.__df = self.__df.astype({ 'time': 'datetime64[ns]', 'open': 'float64', 'close': 'float64', 'high': 'float64', 'low': 'float64', 'volume': 'float64', 'currency_volume': 'float64' }) except: # dataframe只有6列的情况,如okex的现货k线数据 self.__df = pd.DataFrame( self.__kline, columns=['time', 'open', 'high', 'low', 'close', 'volume']) self.__df = self.__df.astype({ 'time': 'datetime64[ns]', 'open': 'float64', 'close': 'float64', 'high': 'float64', 'low': 'float64', 'volume': 'float64' }) # create three plot 创建三层图纸,第一层画k线,第二层画成交量,第三层画一些适宜于副图显示的指标 fplt.foreground = '#FFFFFF' # 前景色 fplt.background = '#333333' # 背景色 fplt.odd_plot_background = '#333333' # 第二层图纸的背景色 fplt.cross_hair_color = "#FFFFFF" # 准星的颜色 self.__ax, self.__ax2, self.__ax3 = fplt.create_plot(symbol, rows=3) # plot candle sticks candles = self.__df[['time', 'open', 'close', 'high', 'low']] fplt.candlestick_ochl(candles, ax=self.__ax) # overlay volume on the plot volumes = self.__df[['time', 'open', 'close', 'volume']] fplt.volume_ocv(volumes, ax=self.__ax2) fplt.add_legend("VOLUME", self.__ax2) # 增加"VOLUME"图例
def __init__(self, databank, database, data_sheet, exchange, instrument_id, time_frame): print("{} {} 持仓同步功能已启动!".format(get_localtime(), instrument_id)) self.__databank = databank self.__database = database self.__datasheet = data_sheet self.__exchange = exchange self.__instrument_id = instrument_id self.__time_frame = time_frame self.__position = POSITION(self.__exchange, self.__instrument_id, self.__time_frame) self.__market = MARKET(self.__exchange, self.__instrument_id, self.__time_frame) self.__overprice_range = config.overprice_range
def __init__(self, instrument_id, time_frame, bollinger_lengths, filter_length, start_asset): try: # 策略启动时控制台输出提示信息 print("{} {} 布林强盗突破策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads("config.json") # 载入配置文件 # 初始化 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 交易所 self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 行情 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 持仓 self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 指标 # 在第一次运行程序时,将初始资金、总盈亏等数据保存至数据库中 self.database = "回测" # 数据库,回测时必须为"回测" self.datasheet = self.instrument_id.split( "-")[0].lower() + "_" + time_frame # 数据表 if config.first_run == "true": storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 # 策略参数 self.contract_value = self.market.contract_value() # 合约面值 self.counter = 0 # 计数器 self.bollinger_lengths = bollinger_lengths # 布林通道参数 self.filter_length = filter_length # 过滤器参数 self.out_day = 50 # 自适应出场ma的初始值为50,开仓后赋值为布林通道参数的值 except: logger.warning()
def __init__(self, instrument_id, time_frame, fast_length, slow_length, long_stop, short_stop, start_asset, precision): try: print("{} {} 双均线多空策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads('config.json') # 载入配置文件 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.precision = precision # 精度,即币对的最小交易数量 self.exchange = OKEXSPOT(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 初始化交易所 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化potion self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators # 在第一次运行程序时,将初始资金数据保存至数据库中 self.database = "回测" # 回测时必须为"回测" self.datasheet = self.instrument_id.split( "-")[0].lower() + "_" + time_frame if config.first_run == "true": storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 self.counter = 0 # 计数器 self.fast_length = fast_length # 短周期均线长度 self.slow_length = slow_length # 长周期均线长度 self.long_stop = long_stop # 多单止损幅度 self.short_stop = short_stop # 空单止损幅度 self.hold_price = 0 # 注意:okex的现货没有获取持仓均价的接口,故需实盘时需要手动记录入场价格。此种写法对于不同的交易所是通用的。 # 此种写法,若策略重启,持仓价格会回归0 except: logger.warning()
class Strategy: def __init__(self, instrument_id, time_frame, fast_length, slow_length, long_stop, short_stop, start_asset): try: print("{} {} 双均线多空策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads('config.json') # 载入配置文件 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 初始化交易所 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化potion self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators # 在第一次运行程序时,将初始资金数据保存至数据库中 self.database = "回测" # 回测时必须为"回测" self.datasheet = self.instrument_id.split("-")[0].lower() + "_" + time_frame if config.first_run == "true": storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 self.counter = 0 # 计数器 self.fast_length = fast_length # 短周期均线长度 self.slow_length = slow_length # 长周期均线长度 self.long_stop = long_stop # 多单止损幅度 self.short_stop = short_stop # 空单止损幅度 self.contract_value = self.market.contract_value() # 合约面值,每次获取需发起网络请求,故于此处声明变量,优化性能 except: logger.warning() def begin_trade(self, kline=None): try: if self.indicators.CurrentBar(kline=kline) < self.slow_length: # 如果k线数据不够长就返回 return timestamp = ts_to_datetime_str(utctime_str_to_ts(kline[-1][0])) if kline else get_localtime() # 非回测模式下时间戳就是当前本地时间 # 计算策略信号 ma = self.indicators.MA(self.fast_length, self.slow_length, kline=kline) fast_ma = ma[0] slow_ma = ma[1] cross_over = fast_ma[-2] >= slow_ma[-2] and fast_ma[-3] < slow_ma[-3] # 不用当根k线上的ma来计算信号,防止信号闪烁 cross_below = slow_ma[-2] >= fast_ma[-2] and slow_ma[-3] < fast_ma[-3] if self.indicators.BarUpdate(kline=kline): # 如果k线更新,计数器归零 self.counter = 0 if self.counter < 1: # 按照策略信号开平仓 if cross_over: # 金叉时 if self.position.amount() == 0: # 若当前无持仓,则买入开多并推送下单结果 price = self.market.open(-1, kline=kline) # 下单价格=此根k线收盘价 amount = round(self.total_asset / self.contract_value) # 数量=总资金/价格/合约面值 info = self.exchange.buy(price, amount) push(info) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入开多", price, amount, amount * self.contract_value, price, "long", amount, 0, self.total_profit, self.total_asset) # 将信息保存至数据库 if self.position.direction() == 'short': # 若当前持空头,先平空再开多 profit = self.position.covershort_profit(market_type="usd_contract", last=self.market.open(-1, kline=kline)) # 在平空前先计算逻辑盈亏,当前最新成交价为开盘价 self.total_profit += profit self.total_asset += profit # 计算此次盈亏后的总资金 cover_short_price = self.market.open(-1, kline=kline) cover_short_amount = self.position.amount() open_long_price = self.market.open(-1, kline=kline) open_long_amount = round(self.total_asset / self.contract_value) info = self.exchange.BUY(cover_short_price, cover_short_amount, open_long_price, open_long_amount) push("此次盈亏:{} 当前总资金:{}".format(profit, self.total_asset) + str(info)) # 需将返回的下单结果info转换为字符串后进行拼接 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "平空开多", open_long_price, open_long_amount, open_long_amount * self.contract_value, open_long_price, "long", open_long_amount, profit, self.total_profit, self.total_asset) if cross_below: # 死叉时 if self.position.amount() == 0: price = self.market.open(-1, kline=kline) amount = round(self.total_asset / self.contract_value) info = self.exchange.sellshort(price, amount) push(info) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出开空", price, amount, amount * self.contract_value, price, "short", amount, 0, self.total_profit, self.total_asset) if self.position.direction() == 'long': profit = self.position.coverlong_profit(market_type="usd_contract", last=self.market.open(-1, kline=kline)) # 在平多前先计算逻辑盈亏,当前最新成交价为开盘价 self.total_profit += profit self.total_asset += profit cover_long_price = self.market.open(-1, kline=kline) cover_long_amount = self.position.amount() open_short_price = self.market.open(-1, kline=kline) open_short_amount = round(self.total_asset / self.contract_value) info = self.exchange.SELL(cover_long_price, cover_long_amount, open_short_price, open_short_amount) push("此次盈亏:{} 当前总资金:{}".format(profit, self.total_asset) + str(info)) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "平多开空", open_short_price, open_short_amount, open_short_amount * self.contract_value, open_short_price, "short", open_short_amount, profit, self.total_profit, self.total_asset) # 止损 if self.position.amount() > 0: if self.position.direction() == 'long' and self.market.low(-1, kline=kline) <= self.position.price() * self.long_stop: # 多单止损 profit = self.position.coverlong_profit(market_type="usd_contract", last=self.position.price() * self.long_stop) # 在平多前先计算逻辑盈亏,当前最新成交价为止损价 self.total_profit += profit self.total_asset += profit price = self.position.price() * self.long_stop amount = self.position.amount() info = self.exchange.sell(price, amount) push("此次盈亏:{} 当前总资金:{}".format(profit, self.total_asset) + str(info)) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出止损", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 # 计数器加1,控制此根k线上不再下单 if self.position.direction() == 'short' and self.market.high(-1, kline=kline) >= self.position.price() * self.short_stop: # 空头止损 profit = self.position.covershort_profit(market_type="usd_contract", last=self.position.price() * self.short_stop) self.total_profit += profit self.total_asset += profit price = self.position.price() * self.short_stop amount = self.position.amount() info = self.exchange.buytocover(price, amount) push("此次盈亏:{} 当前总资金:{}".format(profit, self.total_asset) + str(info)) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入止损", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 except: logger.info()
def __init__(self, platform, instrument_id, time_frame): self.__platform = platform self.__instrument_id = instrument_id self.__time_frame = time_frame self.__market = MARKET(self.__platform, self.__instrument_id, self.__time_frame)
class POSITION: def __init__(self, platform, instrument_id, time_frame): self.__platform = platform self.__instrument_id = instrument_id self.__time_frame = time_frame self.__market = MARKET(self.__platform, self.__instrument_id, self.__time_frame) def direction(self): """获取当前持仓方向""" if config.backtest is False: # 实盘模式下实时获取账户实际持仓方向,仅支持单向持仓模式下的查询 result = self.__platform.get_position()['direction'] return result else: # 回测模式下从数据库中读取持仓方向 result = storage.read_mysql_datas(0, "回测", self.__instrument_id.split("-")[0].lower() + "_" + self.__time_frame, "总资金", ">")[-1][6] return result def amount(self, mode=None, side=None): """获取当前持仓数量""" if config.backtest is False: # 实盘模式下实时获取账户实际持仓数量 if mode == "both": # 如果传入参数"both",查询双向持仓模式的持仓数量 result = self.__platform.get_position(mode=mode) if side == "long": long_amount = result["long"]["amount"] return long_amount elif side == "short": short_amount = result["short"]["amount"] return short_amount else: result = self.__platform.get_position()['amount'] return result else: # 回测模式下从数据库中读取持仓数量 result = storage.read_mysql_datas(0, "回测", self.__instrument_id.split("-")[0].lower() + "_" + self.__time_frame, "总资金", ">")[-1][7] return result def price(self, mode=None, side=None): """获取当前的持仓价格""" if config.backtest is False: # 实盘模式下实时获取账户实际持仓价格 if mode == "both": # 如果传入参数"both",查询双向持仓模式的持仓价格 result = self.__platform.get_position(mode=mode) if side == "long": long_price = result["long"]["price"] return long_price elif side == "short": short_price = result["short"]["price"] return short_price else: result = self.__platform.get_position()['price'] return result else: # 回测模式下从数据库中读取持仓价格 result = storage.read_mysql_datas(0, "回测", self.__instrument_id.split("-")[0].lower() + "_" + self.__time_frame, "总资金", ">")[-1][5] return result def coverlong_profit(self, market_type=None, last=None): """ 计算平多的单笔交易利润 :param market_type: 默认是USDT合约,可填"usd_contract"(币本位合约)或者"spot"(现货) :param last: 回测模式可以传入最新成交价 :return: 返回计算出的利润结果 """ if market_type == "usd_contract": # 如果是币本位合约 self.__value = self.__market.contract_value() # 合约面值 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (last - self.price()) * ((self.amount() * self.__value) / self.price()) # 利润=价差*(合约张数*面值)/持仓价格 else: # 如果是实盘模式 result = (self.__market.last() - self.price()) * ((self.amount() * self.__value) / self.price()) # 利润=价差*(合约张数*面值)/持仓价格 elif market_type == "spot": # 如果是现货 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (last - self.price()) * self.amount() # 利润=价差*持仓数量 else: # 如果是实盘模式 result = (self.__market.last() - self.price()) * self.amount() else: # 默认是usdt合约 self.__value = self.__market.contract_value() # 合约面值 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (last - self.price()) * (self.amount() * self.__value) # 利润=价差*(持仓数量*面值) else: # 如果是实盘模式 result = (self.__market.last() - self.price()) * (self.amount() * self.__value) return result def covershort_profit(self, market_type=None, last=None): """ 计算平空的单笔交易利润 :param market_type: 默认是USDT合约,可填"usd_contract"(币本位合约)或者"spot"(现货) :param last: 回测模式可以传入最新成交价 :return: 返回计算出的利润结果 """ if market_type == "usd_contract": # 如果是币本位合约 self.__value = self.__market.contract_value() # 合约面值 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (self.price() - last) * ((self.amount() * self.__value) / self.price()) # 利润=价差*(合约张数*面值)/持仓价格 else: # 如果是实盘模式 result = (self.price() - self.__market.last()) * ( (self.amount() * self.__value) / self.price()) # 利润=价差*(合约张数*面值)/持仓价格 elif market_type == "spot": # 如果是现货 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (self.price() - last) * self.amount() # 利润=价差*持仓数量 else: # 如果是实盘模式 result = (self.price() - self.__market.last()) * self.amount() else: # 默认是usdt合约 self.__value = self.__market.contract_value() # 合约面值 if config.backtest and last is not None: # 如果是回测模式且传入了last最新成交价 result = (self.price() - last) * (self.amount() * self.__value) # 利润=价差*(持仓数量*面值) else: # 如果是实盘模式 result = (self.price() - self.__market.last()) * (self.amount() * self.__value) return result
class Strategy: """布林强盗策略""" def __init__(self, instrument_id, time_frame, bollinger_lengths, filter_length, start_asset): try: # 策略启动时控制台输出提示信息 print("{} {} 布林强盗突破策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads("config.json") # 载入配置文件 # 初始化 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 交易所 self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 行情 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 持仓 self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 指标 # 在第一次运行程序时,将初始资金、总盈亏等数据保存至数据库中 self.database = "回测" # 数据库,回测时必须为"回测" self.datasheet = self.instrument_id.split( "-")[0].lower() + "_" + time_frame # 数据表 if config.first_run == "true": storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas( 0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 # 策略参数 self.contract_value = self.market.contract_value() # 合约面值 self.counter = 0 # 计数器 self.bollinger_lengths = bollinger_lengths # 布林通道参数 self.filter_length = filter_length # 过滤器参数 self.out_day = 50 # 自适应出场ma的初始值为50,开仓后赋值为布林通道参数的值 except: logger.warning() def begin_trade(self, kline=None): try: # 异常处理 if self.indicators.CurrentBar( kline=kline) < self.bollinger_lengths: # 如果k线数据不够长就返回 return timestamp = ts_to_datetime_str(utctime_str_to_ts( kline[-1] [0])) if kline else get_localtime() # 非回测模式下时间戳就是当前本地时间 if self.indicators.BarUpdate(kline=kline): self.counter = 0 # k线更新时还原计数器 if self.out_day > 10: # 计算MA的天数最小递减到10。如果达到10,则不再递减。 self.out_day -= 1 # 自适应出场ma的长度参数根据持仓周期递减,持有头寸的时间每多一天,计算MA的天数减1 deviation = float( self.indicators.STDDEV(self.bollinger_lengths, nbdev=2, kline=kline)[-1]) # 标准差 middleband = float( self.indicators.BOLL(self.bollinger_lengths, kline=kline)['middleband'][-1]) # 布林通道中轨 upperband = float(middleband + deviation) # 布林通道上轨 lowerband = float(middleband - deviation) # 布林通道下轨 filter = float( self.market.close(-1, kline=kline) - self.market.close( (self.filter_length * -1) - 1, kline=kline)) # 过滤器:当日收盘价减去30日前的收盘价 ma = float(self.indicators.MA(self.out_day, kline=kline)[-1]) # 自适应移动出场平均线 # 策略主体 # 若k线数据足够长,且满足过滤条件,且当根k线最高价大于等于布林通道上轨,买入开多。 # 开仓处也设置计数器过滤,是为了防止没有启用交易助手的情况下挂单未成交,仓位为零时当根k线一直满足开仓条件,会重复挂单。 if self.indicators.CurrentBar( kline=kline ) >= self.bollinger_lengths and filter > 0 and self.market.high( -1, kline=kline) > upperband and self.counter < 1: if self.position.amount() == 0: # 若当前无持仓 price = upperband # 开多价格为布林通道上轨的值 amount = round(self.total_asset / upperband / self.contract_value) # 合约张数取整 info = self.exchange.buy(price, amount) # 买入开多,并将返回的信息赋值给变量info push(info) # 推送信息 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "买入开多", price, amount, amount * price * self.contract_value, price, "long", amount, 0, self.total_profit, self.total_asset) # 将信息保存至数据库 self.counter += 1 # 此策略是在盘中开仓,而在回测时,每根bar只会运行一次,每根bar上的价格不分时间先后,故此处开仓后计数器加1,也就是当根k线不平仓 # 因为实盘时每个ticker进来策略就会运行一次。注意回测和实盘策略运行机制的不同。 self.out_day = self.bollinger_lengths # 开仓后赋值 # 开空 if self.indicators.CurrentBar( kline=kline ) >= self.bollinger_lengths and filter < 0 and self.market.low( -1, kline=kline) < lowerband and self.counter < 1: if self.position.amount() == 0: price = lowerband amount = round(self.total_asset / upperband / self.contract_value) info = self.exchange.sellshort(price, amount) push(info) storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "卖出开空", price, amount, amount * price * self.contract_value, price, "short", amount, 0, self.total_profit, self.total_asset) self.counter += 1 self.out_day = self.bollinger_lengths # 开仓后赋值 # 如果当前持多,且当根k线最低价小于等于中轨值,触发保护性止损,就平多止损 # 因为回测是一根k线上运行整个策略一次,所以要实现当根k线开仓后当根k线不平仓,需要将self.counter < 1的条件加在平仓的地方 if self.position.direction() == "long" and self.market.low( -1, kline=kline) < middleband and self.counter < 1: profit = self.position.coverlong_profit( last=middleband) # 此处计算平多利润时,传入最新价last为中轨值,也就是触发止损价格的那个值。 self.total_profit += profit # 计算经过本次盈亏后的总利润 self.total_asset += profit # 计算经过本次盈亏后的总资金 price = middleband # 平多价格为中轨值 amount = self.position.amount() # 平仓数量为当前持仓数量 info = self.exchange.sell(price, amount) push(info) self.counter += 1 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "卖出止损", price, amount, price * amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) if self.position.direction() == "short" and self.market.high( -1, kline=kline) > middleband and self.counter < 1: profit = self.position.covershort_profit(last=middleband) self.total_profit += profit self.total_asset += profit price = middleband amount = self.position.amount() info = self.exchange.buytocover(price, amount) push(info) self.counter += 1 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "买入止损", price, amount, amount * price * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) # 平多 if self.position.direction( ) == "long" and upperband > ma > self.market.low( -1, kline=kline) and self.counter < 1: profit = self.position.coverlong_profit(last=ma) self.total_profit += profit self.total_asset += profit price = ma # 平仓价格为自适应出场均线的值 amount = self.position.amount() info = self.exchange.sell(price, amount) push(info) self.counter += 1 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "卖出平多", price, amount, price * amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) # 平空 if self.position.direction( ) == "short" and lowerband < ma < self.market.high( -1, kline=kline) and self.counter < 1: profit = self.position.covershort_profit(last=ma) self.total_profit += profit self.total_asset += profit price = ma amount = self.position.amount() info = self.exchange.buytocover(price, amount) push(info) self.counter += 1 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "买入平空", price, amount, amount * price * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) except: logger.error()
class SYNCHRONIZE: """持仓同步""" def __init__(self, databank, database, data_sheet, exchange, instrument_id, time_frame): print("{} {} 持仓同步功能已启动!".format(get_localtime(), instrument_id)) self.__databank = databank self.__database = database self.__datasheet = data_sheet self.__exchange = exchange self.__instrument_id = instrument_id self.__time_frame = time_frame self.__position = POSITION(self.__exchange, self.__instrument_id, self.__time_frame) self.__market = MARKET(self.__exchange, self.__instrument_id, self.__time_frame) self.__overprice_range = config.overprice_range def save_strategy_position(self, strategy_direction, strategy_amount): """下单后将仓位信息保存至数据库.""" if self.__databank == "mysql": storage.mysql_save_strategy_position(self.__database, self.__datasheet, strategy_direction, strategy_amount) elif self.__databank == "mongodb": storage.mongodb_save(data={"时间": get_localtime(), "strategy_direction": strategy_direction, "strategy_amount": strategy_amount}, database=self.__database, collection=self.__datasheet) else: raise DataBankError def match(self): # 获取当前账户持仓信息 account_direction = self.__position.direction() account_amount = self.__position.amount() # 获取当前策略应持仓位信息 if self.__databank == "mysql": strategy_direction = storage.read_mysql_datas(0, self.__database, self.__datasheet, "amount", ">=")[-1][-2] strategy_amount = storage.read_mysql_datas(0, self.__database, self.__datasheet, "amount", ">=")[-1][-1] elif self.__databank == "mongodb": strategy_direction = storage.mongodb_read_data(self.__database, self.__datasheet)[-1][0]["strategy_direction"] strategy_amount = int(storage.mongodb_read_data(self.__database, self.__datasheet)[-1][0]["strategy_amount"]) else: strategy_direction = None strategy_amount = None raise DataBankError # 比较账户持仓与策略持仓,如不匹配则同步之 if strategy_direction == "long" and account_direction == "long": if account_amount < strategy_amount: receipt = self.__exchange.buy(self.__market.last() * (1 + self.__overprice_range), strategy_amount - account_amount, 0) return "当前持多,当前实际持仓小于策略应持仓位数量,自动同步结果:{}".format(receipt) elif account_amount > strategy_amount: receipt = self.__exchange.sell(self.__market.last() * (1 - self.__overprice_range), account_amount - strategy_amount, 0) return "当前持多,当前实际持仓大于策略应持仓位数量,自动同步结果:{}".format(receipt) if strategy_direction == "short" and account_direction == "short": # 策略与账户均持空时 if account_amount < strategy_amount: receipt = self.__exchange.sellshort(self.__market.last() * (1 - self.__overprice_range), strategy_amount - account_amount, 0) return "当前持空,当前实际持仓小于策略应持仓位数量,自动同步结果:{}".format(receipt) elif account_amount > strategy_amount: receipt = self.__exchange.buytocover(self.__market.last() * (1 + self.__overprice_range), account_amount - strategy_amount, 0) return "当前持空,当前实际持仓大于策略应持仓位数量,自动同步结果:{}".format(receipt) if strategy_direction == "long" and account_direction == "short": # 策略持多,账户却持空时 receipt1 = self.__exchange.buytocover(self.__market.last() * (1 + self.__overprice_range), account_amount, 0) if "完全成交" not in receipt1: return "策略应持多,当前实际持空,自动同步结果:{}".format(receipt1) else: receipt2 = self.__exchange.buy(self.__market.last() * (1 + self.__overprice_range), strategy_amount, 0) return "策略应持多,当前实际持空,自动同步结果:{}".format(receipt1 + receipt2) if strategy_direction == "short" and account_direction == "long": # 策略持空,账户却持多时 receipt1 = self.__exchange.sell(self.__market.last() * (1 - self.__overprice_range), account_amount, 0) if "完全成交" not in receipt1: return "策略应持空,当前实际持多,自动同步结果:{}".format(receipt1) else: receipt2 = self.__exchange.sellshort(self.__market.last() * (1 - self.__overprice_range), strategy_amount, 0) return "策略应持空,当前实际持多,自动同步结果:{}".format(receipt1 + receipt2) if strategy_direction == "none" and account_direction == "long": # 策略无持仓,账户却持多时 receipt = self.__exchange.sell(self.__market.last() * (1 - self.__overprice_range), account_amount, 0) return "策略应无持仓,当前实际持多,自动同步结果:{}".format(receipt) if strategy_direction == "none" and account_direction == "short": # 策略无持仓,账户却持空时 receipt = self.__exchange.buytocover(self.__market.last() * (1 + self.__overprice_range), account_amount, 0) return "策略应无持仓,当前实际持空,自动同步结果:{}".format(receipt) if account_direction == "none" and strategy_direction == "long": # 账户无持仓,策略却应持多时 receipt = self.__exchange.buy(self.__market.last() * (1 + self.__overprice_range), strategy_amount, 0) return "策略应持多仓,当前实际无持仓,自动同步结果:{}".format(receipt) if account_direction == "none" and strategy_direction == "short": # 账户无持仓,策略却应持空 receipt = self.__exchange.sellshort(self.__market.last() * (1 - self.__overprice_range), strategy_amount, 0) return "策略应持空仓,当前实际无持仓,自动同步结果:{}".format(receipt) if account_amount == strategy_amount and account_direction == strategy_direction: dict = {"策略持仓方向": strategy_direction, "策略持仓数量": strategy_amount, "账户实际持仓方向": account_direction, "账户实际持仓数量": account_amount} return "策略持仓与账户持仓匹配! {}".format(dict) else: raise MatchError
class Strategy: def __init__(self, instrument_id, time_frame, start_asset): # 策略初始化时需传入合约id、k线周期、初始资金参数 print("{} {} 海龟交易策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 config.loads("config.json") # 载入配置文件 self.instrument_id = instrument_id # 合约id self.time_frame = time_frame # k线周期 self.exchange = OKEXFUTURES(config.access_key, config.secret_key, config.passphrase, self.instrument_id, leverage=20) # 初始化交易所 self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化position self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators self.database = "回测" # 如从purequant服务器的数据库上获取历史k线数据进行回测,必须为"回测" self.datasheet = self.instrument_id.split("-")[0].lower() + "_" + time_frame # 数据表 if config.first_run == "true": # 程序第一次启动时保存数据,实盘时如策略中止再重启时,可以将配置文件中的first_run改成"false",程序再次启动会直接读取数据库中保存的数据 storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金、总盈亏数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 # 一些策略参数 self.contract_value = self.market.contract_value() # 合约面值 self.ATRLength = 20 # 平均波动周期 self.boLength = 20 # 短周期 BreakOut Length self.fsLength = 55 # 长周期 FailSafe Length self.teLength = 10 # 离市周期 Trailing Exit Length self.LastProfitableTradeFilter = 1 # 使用入市过滤条件 self.PreBreakoutFailure = False # 前一次是否突破失败 self.CurrentEntries = 0 # 当前持仓的开仓次数 self.counter = 0 # 计数器,用以控制单根bar最大交易次数 def begin_trade(self, kline=None): # 实盘时从交易所实时获取k线数据,回测时传入自定义的kline try: # 如果k线数据不够长就返回 if self.indicators.CurrentBar(kline=kline) < self.fsLength: return # 非回测模式下时间戳就是当前本地时间 timestamp = ts_to_datetime_str(utctime_str_to_ts(kline[-1][0])) if kline else get_localtime() # k线更新时计数器归零 if self.indicators.BarUpdate(kline=kline): self.counter = 0 AvgTR = self.indicators.ATR(self.ATRLength, kline=kline) # 计算真实波幅 N = float(AvgTR[-2]) # N值为前一根bar上的ATR值,需将numpy.float64数据类型转换为float类型,下面的转换同理 Units = int(self.total_asset / self.contract_value / 5) # 每一份头寸大小为总资金的20% """计算短周期唐奇安通道""" # 唐奇安通道上轨,延后1个Bar DonchianHi = float(self.indicators.HIGHEST(self.boLength, kline=kline)[-2]) # 唐奇安通道下轨,延后1个Bar DonchianLo = float(self.indicators.LOWEST(self.boLength, kline=kline)[-2]) """计算长周期唐奇安通道""" # 唐奇安通道上轨,延后1个Bar,长周期 fsDonchianHi = float(self.indicators.HIGHEST(self.fsLength, kline=kline)[-2]) # 唐奇安通道下轨,延后1个Bar,长周期 fsDonchianLo = float(self.indicators.LOWEST(self.fsLength, kline=kline)[-2]) """计算止盈唐奇安通道""" # 离市时判断需要的N周期最低价 ExitLowestPrice = float(self.indicators.LOWEST(self.teLength, kline=kline)[-2]) # 离市时判断需要的N周期最高价 ExitHighestPrice = float(self.indicators.HIGHEST(self.teLength, kline=kline)[-2]) # 当不使用过滤条件,或者使用过滤条件且条件PreBreakoutFailure为True时,短周期开仓 if self.indicators.CurrentBar(kline=kline) >= self.boLength and self.position.amount() == 0 and (self.LastProfitableTradeFilter != 1 or self.PreBreakoutFailure == False) and self.counter < 1: if self.market.high(-1, kline=kline) >= DonchianHi: # 突破了短周期唐奇安通道上轨 price = DonchianHi # 开多价格为短周期唐奇安通道上轨 amount = Units # 开多数量为Units receipt = self.exchange.buy(price, amount) # 开多 push(receipt) # 推送下单结果 self.CurrentEntries += 1 # 记录一次开仓次数 self.PreBreakoutFailure = False # 将标识重置为默认值,根据离场时的盈亏情况再修改 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入开多", price, amount, amount * self.contract_value, price, "long", amount, 0, self.total_profit, self.total_asset) # 将信息保存至数据库 self.counter += 1 # 计数器加1 if self.market.low(-1, kline=kline) <= DonchianLo: # 突破了短周期唐奇安通道下轨 price = DonchianLo # 开空价格为DonchianLo amount = Units # 开空数量为Units receipt = self.exchange.sellshort(price, amount) # 开空 push(receipt) # 推送下单结果 self.CurrentEntries += 1 # 记录一次开仓次数 self.PreBreakoutFailure = False # 将标识重置为默认值,根据离场时的盈亏情况再修改 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出开空", price, amount, amount * self.contract_value, price, "short", amount, 0, self.total_profit, self.total_asset) # 保存信息至数据库 self.counter += 1 # 计数器加1 # 长周期突破开仓,其他逻辑和短周期突破开仓一样。 if self.indicators.CurrentBar(kline=kline) >= self.fsLength and self.position.amount() == 0 and self.counter < 1: if self.market.high(-1, kline=kline) >= fsDonchianHi: # 突破了长周期唐奇安通道上轨 price = fsDonchianHi # 开多价格为长周期唐奇安通道上轨值 amount = Units # 数量为Units receipt = self.exchange.buy(price, amount) # 下单并返回下单结果 push(receipt) # 推送下单结果 self.CurrentEntries += 1 # 记录一次开仓次数 self.PreBreakoutFailure = False # 将标识重置为默认值 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入开多", price, amount, amount * self.contract_value, price, "long", amount, 0, self.total_profit, self.total_asset) # 将信息保存至数据库 self.counter += 1 # 计数器加1 if self.market.low(-1, kline=kline) <= fsDonchianLo: # 突破长周期唐奇安通道下轨 price = fsDonchianLo # 开空价格为长周期唐奇安通道下轨值 amount = Units # 开空数量为Units receipt = self.exchange.sellshort(price, amount) # 下单并返回下单结果 push(receipt) # 推送下单结果 self.CurrentEntries += 1 # 记录一次开仓次数 self.PreBreakoutFailure = False # 将标识重置为默认值 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出开空", price, amount, amount * self.contract_value, price, "short", amount, 0, self.total_profit, self.total_asset) self.counter += 1 # 计数器加1 # 止盈、加仓和止损 if self.position.direction() == "long" and self.counter < 1: # 持多仓的情况。回测时是一根k线上整个策略从上至下运行一次,所以在此处设置计数器过滤 if self.market.low(-1, kline=kline) <= ExitLowestPrice: # 跌破止盈价 profit = self.position.coverlong_profit(last=ExitLowestPrice, market_type="usd_contract") # 平仓前计算利润,传入最新价以及计算盈利的合约类型 self.total_profit += profit # 计算经过本次盈亏后的总利润 self.total_asset += profit # 计算经过本次盈亏后的总资金 price = ExitLowestPrice # 平多价格为ExitLowestPrice amount = self.position.amount() # 数量为当前持仓数量 receipt = self.exchange.sell(price, amount) # 平所有多单仓位 push(receipt) # 推送下单结果 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出平多", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 # 计数器加1 self.CurrentEntries = 0 # 平仓后将开仓次数还原为0 else: # 加仓指令 '''以最高价为标准,判断是否能加仓,并限制最大加仓次数 如果价格过前次开仓价格1/2N,则直接加仓 ''' while self.market.high(-1, kline=kline) >= (self.position.price() + 0.5 * N) and (self.CurrentEntries <= 4): price = self.position.price() + 0.5 * N # 加仓的开仓价格为持仓价格+0.5 * N amount = Units # 数量为Units storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "多头加仓", price, amount, amount * self.contract_value, (self.position.price() + price) / 2, "long", self.position.amount() + amount, 0, self.total_profit, self.total_asset) receipt = self.exchange.buy(price, amount) push(receipt) self.CurrentEntries += 1 # 止损指令 if self.market.low(-1, kline=kline) <= (self.position.price() - 2 * N): # 如果回落大于最后下单价格-2n,就止损 profit = self.position.coverlong_profit(last=self.position.price() - 2 * N, market_type="usd_contract") self.total_profit += profit # 计算经过本次盈亏后的总利润 self.total_asset += profit # 计算经过本次盈亏后的总资金 price = self.position.price() - 2 * N amount = self.position.amount() receipt = self.exchange.sell(price, amount) # 全部止损平仓 push(receipt) self.PreBreakoutFailure = True # 记录为突破失败,下次交易将使用长周期开仓 storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "卖出止损", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 self.CurrentEntries = 0 # 平仓后将开仓次数还原为0 elif self.position.direction() == "short" and self.counter < 1: # 持空头的情况,除方向以外,其他逻辑和上面持多仓的一致 if self.market.high(-1, kline=kline) >= ExitHighestPrice: profit = self.position.covershort_profit(last=ExitHighestPrice, market_type="usd_contract") self.total_profit += profit self.total_asset += profit price = ExitHighestPrice amount = self.position.amount() receipt = self.exchange.buytocover(price, amount) push(receipt) storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入平空", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 self.CurrentEntries = 0 # 平仓后将开仓次数还原为0 else: while self.market.low(-1, kline=kline) <= (self.position.price() - 0.5 * N) and (self.CurrentEntries <= 4): price = self.position.price() - 0.5 * N amount = Units storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "空头加仓", price, amount, amount * self.contract_value, (self.position.price() + price) / 2, "short", self.position.amount() + amount, 0, self.total_profit, self.total_asset) receipt = self.exchange.sellshort(self.position.price() - 0.5 * N, Units) push(receipt) self.CurrentEntries += 1 if self.market.high(-1, kline=kline) >= (self.position.price() + 2 * N): profit = self.position.covershort_profit(last=self.position.price() + 2 * N, market_type="usd_contract") self.total_profit += profit self.total_asset += profit price = self.position.price() + 2 * N amount = self.position.amount() receipt = self.exchange.buytocover(price, amount) push(receipt) self.PreBreakoutFailure = True storage.mysql_save_strategy_run_info(self.database, self.datasheet, timestamp, "买入止损", price, amount, amount * self.contract_value, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 self.CurrentEntries = 0 # 平仓后将开仓次数还原为0 except: logger.error()
class SIGNALIZE: """实盘时根据从交易所获取的k线数据绘制k线图、成交量图及指标""" def __init__(self, platform, symbol, time_frame): self.__platform = platform self.__symbol = symbol self.__time_frame = time_frame self.__market = MARKET(self.__platform, self.__symbol, self.__time_frame) # pull some data self.__indicators = INDICATORS(self.__platform, self.__symbol, self.__time_frame) self.__kline = platform.get_kline(self.__time_frame) self.__kline.reverse() # format it in pandas try: # dataframe有7列的情况 self.__df = pd.DataFrame(self.__kline, columns=[ 'time', 'open', 'high', 'low', 'close', 'volume', 'currency_volume' ]) self.__df = self.__df.astype({ 'time': 'datetime64[ns]', 'open': 'float64', 'close': 'float64', 'high': 'float64', 'low': 'float64', 'volume': 'float64', 'currency_volume': 'float64' }) except: # dataframe只有6列的情况,如okex的现货k线数据 self.__df = pd.DataFrame( self.__kline, columns=['time', 'open', 'high', 'low', 'close', 'volume']) self.__df = self.__df.astype({ 'time': 'datetime64[ns]', 'open': 'float64', 'close': 'float64', 'high': 'float64', 'low': 'float64', 'volume': 'float64' }) # create three plot 创建三层图纸,第一层画k线,第二层画成交量,第三层画一些适宜于副图显示的指标 fplt.foreground = '#FFFFFF' # 前景色 fplt.background = '#333333' # 背景色 fplt.odd_plot_background = '#333333' # 第二层图纸的背景色 fplt.cross_hair_color = "#FFFFFF" # 准星的颜色 self.__ax, self.__ax2, self.__ax3 = fplt.create_plot(symbol, rows=3) # plot candle sticks candles = self.__df[['time', 'open', 'close', 'high', 'low']] fplt.candlestick_ochl(candles, ax=self.__ax) # overlay volume on the plot volumes = self.__df[['time', 'open', 'close', 'volume']] fplt.volume_ocv(volumes, ax=self.__ax2) fplt.add_legend("VOLUME", self.__ax2) # 增加"VOLUME"图例 """ plot indicators """ def show(self): """最后必须调用此函数以显示图像""" fplt.show() def plot_last(self, color=None): """在图上画出最新成交价这根横线,便于观察""" last = self.__market.last() array = np.empty(len(self.__kline)) array.fill(last) color = color if color is not None else "#CD7F32" # 默认设置为红色 fplt.plot(self.__df['time'], array, color=color, ax=self.__ax, legend="LAST {}".format(last)) def plot_array(self, array, ax, legend, color=None): """ 绘制任意的数组成线性 :param array: 传入一个数组 :param ax: 加载在第几行的图上 :param legend: 图例名称 :param color: 颜色 :return: """ if ax == 1: ax = self.__ax elif ax == 2: ax = self.__ax2 elif ax == 3: ax = self.__ax3 color = color if color is not None else "#FF0000" # 默认设置为红色 fplt.plot(self.__df['time'], array, color=color, ax=ax, legend=legend) def plot_atr(self, length, color=None): """ 在图上画出ATR :param length: ATR指标参数 :param color: 线的颜色 :return: """ color = color if color is not None else "#FF0000" # 默认设置为红色 fplt.plot(self.__df['time'], self.__indicators.ATR(length), color=color, ax=self.__ax3, legend='ATR({})'.format(length)) def plot_boll(self, length, color1=None, color2=None, color3=None): """ 在图上画出布林通道的上轨、中轨、下轨 :param length: BOLL指标参数 :param upperband_color: 上轨颜色 :param middleband_color: 中轨颜色 :param lowerband_color: 下轨颜色 :return: """ color1 = color1 if color1 is not None else "#FF0000" # 默认设置为红色 color2 = color2 if color2 is not None else "#00FF00" # 默认设置为绿色 color3 = color3 if color3 is not None else "#0000FF" # 默认设置为蓝色 upperband_array = self.__indicators.BOLL(length)['upperband'] middleband_array = self.__indicators.BOLL(length)["middleband"] lowerband_array = self.__indicators.BOLL(length)["lowerband"] fplt.plot(self.__df['time'], upperband_array, color=color1, ax=self.__ax, legend='BOLL({})-UPPERBAND'.format(length)) fplt.plot(self.__df['time'], middleband_array, color=color2, ax=self.__ax, legend='BOLL({})-MIDDLEBAND'.format(length)) fplt.plot(self.__df['time'], lowerband_array, color=color3, ax=self.__ax, legend='BOLL({})-LOWERBAND'.format(length)) # 副图上也加载 fplt.plot(self.__df['time'], upperband_array, color=color1, ax=self.__ax3, legend='BOLL({})-UPPERBAND'.format(length)) fplt.plot(self.__df['time'], middleband_array, color=color2, ax=self.__ax3, legend='BOLL({})-MIDDLEBAND'.format(length)) fplt.plot(self.__df['time'], lowerband_array, color=color3, ax=self.__ax3, legend='BOLL({})-LOWERBAND'.format(length)) def plot_highest(self, length, color=None): """ 在图上画出最高价 :param length: HIGHEST指标参数 :param color: 线的颜色 :return: """ color = color if color is not None else "#FF0000" # 默认设置红黑色 fplt.plot(self.__df['time'], self.__indicators.HIGHEST(length), color=color, ax=self.__ax, legend='HIGHEST({})'.format(length)) # 副图也加载 fplt.plot(self.__df['time'], self.__indicators.HIGHEST(length), color=color, ax=self.__ax3, legend='HIGHEST({})'.format(length)) def plot_ma(self, length, color=None): """ 在图上画出移动平均线 :param length: 简单移动平均线参数 :param color: 线的颜色 :return: """ color = color if color is not None else "#FF0000" # 默认设置为红色 # 主图与副图加载指标 fplt.plot(self.__df['time'], self.__indicators.MA(length), color=color, ax=self.__ax, legend='MA({})'.format(length)) fplt.plot(self.__df['time'], self.__indicators.MA(length), color=color, ax=self.__ax3, legend='MA({})'.format(length)) def plot_macd(self, fastperiod, slowperiod, signalperiod, color1=None, color2=None, color3=None): """ 在图上画出MACD指标 :param fastperiod: :param slowperiod: :param signalperiod: :param color1: :param color2: :param color3: :return: """ color1 = color1 if color1 is not None else "#FF0000" # 默认设置为红色 color2 = color2 if color2 is not None else "#00FF00" # 默认设置为绿色 color3 = color3 if color3 is not None else "#0000FF" # 默认设置为蓝色 dif = self.__indicators.MACD(fastperiod, slowperiod, signalperiod)['DIF'] dea = self.__indicators.MACD(fastperiod, slowperiod, signalperiod)["DEA"] macd = self.__indicators.MACD(fastperiod, slowperiod, signalperiod)["MACD"] fplt.plot(self.__df['time'], dif, color=color1, ax=self.__ax3, legend='MACD({}, {}, {})-DIF'.format(fastperiod, slowperiod, signalperiod)) fplt.plot(self.__df['time'], dea, color=color2, ax=self.__ax3, legend='MACD({}, {}, {})-DEA'.format(fastperiod, slowperiod, signalperiod)) fplt.plot(self.__df['time'], macd, color=color3, ax=self.__ax3, legend='MACD({}, {}, {})-MACD'.format( fastperiod, slowperiod, signalperiod)) def plot_ema(self, length, color=None): """ 在图上画出EMA指标 :param length: :param color: :return: """ color = color if color is not None else "#FF0000" # 默认设置为红色 fplt.plot(self.__df['time'], self.__indicators.EMA(length), color=color, ax=self.__ax, legend='EMA({})'.format(length)) # 副图也加载 fplt.plot(self.__df['time'], self.__indicators.EMA(length), color=color, ax=self.__ax3, legend='EMA({})'.format(length)) def plot_kama(self, length, color=None): """在图上画出KAMA指标""" color = color if color is not None else "#FF0000" # 默认设置为红色 fplt.plot(self.__df['time'], self.__indicators.KAMA(length), color=color, ax=self.__ax, legend='KAMA({})'.format(length)) # 副图也加载 fplt.plot(self.__df['time'], self.__indicators.KAMA(length), color=color, ax=self.__ax3, legend='KAMA({})'.format(length)) def plot_kdj(self, fastk_period, slowk_period, slowd_period, color1=None, color2=None): """ 在图上画出KDJ指标 :param fastk_period: :param slowk_period: :param slowd_period: :param color1: :param color2: :param color3: :return: """ color1 = color1 if color1 is not None else "#FF0000" # 默认设置为红色 color2 = color2 if color2 is not None else "#00FF00" # 默认设置为绿色 k = self.__indicators.KDJ(fastk_period, slowk_period, slowd_period)['k'] d = self.__indicators.KDJ(fastk_period, slowk_period, slowd_period)["d"] # 仅副图加载 fplt.plot(self.__df['time'], k, color=color1, ax=self.__ax3, legend='KDJ({}, {}, {})-K'.format(fastk_period, slowk_period, slowd_period)) fplt.plot(self.__df['time'], d, color=color2, ax=self.__ax3, legend='KDJ({}, {}, {})-D'.format(fastk_period, slowk_period, slowd_period)) def plot_lowest(self, length, color=None): """LOWEST""" color = color if color is not None else "#FF0000" # 默认设置红黑色 fplt.plot(self.__df['time'], self.__indicators.LOWEST(length), color=color, ax=self.__ax, legend='LOWEST({})'.format(length)) # 副图也加载 fplt.plot(self.__df['time'], self.__indicators.LOWEST(length), color=color, ax=self.__ax3, legend='LOWEST({})'.format(length)) def plot_obv(self, color=None): """OBV""" color = color if color is not None else "#FF0000" # 默认设置红黑色 # 仅副图加载 fplt.plot(self.__df['time'], self.__indicators.OBV(), color=color, ax=self.__ax3, legend='OBV') def plot_rsi(self, length, color=None): """RSI""" color = color if color is not None else "#FF0000" # 默认设置为红色 # 仅副图加载 fplt.plot(self.__df['time'], self.__indicators.RSI(length), color=color, ax=self.__ax3, legend='RSI({})'.format(length)) def plot_roc(self, length, color=None): """ROC""" color = color if color is not None else "#FF0000" # 默认设置为红色 # 仅副图加载 fplt.plot(self.__df['time'], self.__indicators.ROC(length), color=color, ax=self.__ax3, legend='ROC({})'.format(length)) def plot_stochrsi(self, timeperiod, fastk_period, fastd_period, color1=None, color2=None): """STOCHRSI""" color1 = color1 if color1 is not None else "#FF0000" # 默认设置为红色 color2 = color2 if color2 is not None else "#00FF00" # 默认设置为绿色 stochrsi = self.__indicators.STOCHRSI(timeperiod, fastk_period, fastd_period)['stochrsi'] fastk = self.__indicators.STOCHRSI(timeperiod, fastk_period, fastd_period)["fastk"] # 仅副图加载 fplt.plot(self.__df['time'], stochrsi, color=color1, ax=self.__ax3, legend='STOCHRSI({}, {}, {})-STOCHRSI'.format( timeperiod, fastk_period, fastd_period)) fplt.plot(self.__df['time'], fastk, color=color2, ax=self.__ax3, legend='STOCHRSI({}, {}, {})-FASTK'.format( timeperiod, fastk_period, fastd_period)) def plot_sar(self, color=None): """ 在图上画出SAR :param length: SAR指标参数 :param color: 线的颜色 :return: """ color = color if color is not None else "#FF0000" # 默认设置为红色 # 主副图均加载 fplt.plot(self.__df['time'], self.__indicators.SAR(), color=color, ax=self.__ax, legend='SAR') fplt.plot(self.__df['time'], self.__indicators.SAR(), color=color, ax=self.__ax3, legend='SAR') def plot_stddev(self, length, color=None): """STDDEV""" color = color if color is not None else "#FF0000" # 默认设置为红色 # 仅副图加载 fplt.plot(self.__df['time'], self.__indicators.STDDEV(length), color=color, ax=self.__ax3, legend='STDDEV({})'.format(length)) def plot_trix(self, length, color=None): """STDDEV""" color = color if color is not None else "#FF0000" # 默认设置为红色 # 仅副图加载 fplt.plot(self.__df['time'], self.__indicators.TRIX(length), color=color, ax=self.__ax3, legend='TRIX({})'.format(length)) def plot_volume(self, color=None): """VOLUME""" color = color if color is not None else "#FF0000" # 默认设置为红色 # 仅副图均加载 fplt.plot(self.__df['time'], self.__indicators.VOLUME(), color=color, ax=self.__ax3, legend='VOLUME')
from purequant.trade import HUOBISPOT from purequant.market import MARKET from purequant.indicators import INDICATORS from purequant.position import POSITION # 账户和策略参数等信息 accessy_key = 'your access_key' secret_key = 'your secret_key' instrument_id = 'ETC-USDT' time_frame = '1d' # 初始化交易所、行情模块与指标等模块 exchange = HUOBISPOT(accessy_key, secret_key, instrument_id) market = MARKET(exchange, instrument_id, time_frame) indicators = INDICATORS(exchange, instrument_id, time_frame) position = POSITION(exchange, instrument_id, time_frame) # 下单交易,买入和卖出 # info = exchange.buy(7.35, 0.02) # 以7.35的价格买入0.02个ETC # info = exchange.sell(7.35, 0.01) # 卖出0.02个ETC # 获取行情信息 # info = exchange.get_kline(time_frame) # 获取k线数据 # info = market.last() # 获取ETC-USDT的最新成交价 # info = market.open(-1) # 获取ETC-USDT的当日开盘价 # info = market.high(-1) # 获取ETC-USDT的当日最高价 # info = market.low(-1) # 获取ETC-USDT的当日最低价 # info = market.close(-1) # 获取ETC-USDT的当日收盘价 # 持仓信息 # info = position.amount() # 获取ETC-USDT交易对的ETC可用余额
class Strategy: def __init__(self, instrument_id, time_frame, fast_length, slow_length, long_stop, short_stop, start_asset, precision): config.loads('config.json') # 载入配置文件 self.instrument_id = instrument_id # 合约ID self.time_frame = time_frame # k线周期 self.precision = precision # 精度,即币对的最小交易数量 self.exchange = OKEXSPOT(config.access_key, config.secret_key, config.passphrase, self.instrument_id) # 初始化交易所 self.position = POSITION(self.exchange, self.instrument_id, self.time_frame) # 初始化potion self.market = MARKET(self.exchange, self.instrument_id, self.time_frame) # 初始化market self.indicators = INDICATORS(self.exchange, self.instrument_id, self.time_frame) # 初始化indicators # 在第一次运行程序时,将初始资金数据保存至数据库中 self.database = "回测" # 回测时必须为"回测" self.datasheet = self.instrument_id.split( "-")[0].lower() + "_" + time_frame if config.first_run: storage.mysql_save_strategy_run_info(self.database, self.datasheet, get_localtime(), "none", 0, 0, 0, 0, "none", 0, 0, 0, start_asset) # 读取数据库中保存的总资金数据 self.total_asset = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-1] self.total_profit = storage.read_mysql_datas(0, self.database, self.datasheet, "总资金", ">")[-1][-2] # 策略总盈亏 self.counter = 0 # 计数器 self.fast_length = fast_length # 短周期均线长度 self.slow_length = slow_length # 长周期均线长度 self.long_stop = long_stop # 多单止损幅度 self.short_stop = short_stop # 空单止损幅度 self.hold_price = 0 # 注意:okex的现货没有获取持仓均价的接口,故需实盘时需要手动记录入场价格。此种写法对于不同的交易所是通用的。 # 此种写法,若策略重启,持仓价格会回归0 print("{} {} 双均线多空策略已启动!".format(get_localtime(), instrument_id)) # 程序启动时打印提示信息 def begin_trade(self, kline=None): try: if self.indicators.CurrentBar( kline=kline) < self.slow_length: # 如果k线数据不够长就返回 return timestamp = ts_to_datetime_str(utctime_str_to_ts( kline[-1] [0])) if kline else get_localtime() # 非回测模式下时间戳就是当前本地时间 # 计算策略信号 ma = self.indicators.MA(self.fast_length, self.slow_length, kline=kline) fast_ma = ma[0] slow_ma = ma[1] cross_over = fast_ma[-2] >= slow_ma[-2] and fast_ma[-3] < slow_ma[ -3] # 不用当根k线上的ma来计算信号,防止信号闪烁 cross_below = slow_ma[-2] >= fast_ma[-2] and slow_ma[-3] < fast_ma[ -3] if self.indicators.BarUpdate(kline=kline): # 如果k线更新,计数器归零 self.counter = 0 if self.counter < 1: # 按照策略信号开平仓 if cross_over and round( self.position.amount() ) < self.precision: # 金叉时,若当前无持仓,则买入开多并推送下单结果。0.1这个数值根据每个币对的最小交易数量决定 price = float(self.market.open( -1, kline=kline)) # 下单价格=此根k线开盘价 self.hold_price = price # 记录开仓价格 amount = float(self.total_asset / price) # 数量=总资金/价格 info = self.exchange.buy(price, amount) push(info) storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "买入开多", price, amount, amount * price, price, "long", amount, 0, self.total_profit, self.total_asset) # 将信息保存至数据库 if cross_below and round( self.position.amount(), 1 ) >= self.precision: # 死叉时,如果当前持多就卖出平多。当前持仓数量根据币对的最小交易数量取小数 price = float(self.market.open(-1, kline=kline)) amount = float(self.position.amount()) profit = (price - self.hold_price) * amount # 计算逻辑盈亏 self.total_profit += profit self.total_asset += profit info = self.exchange.sell(price, amount) push(info) self.hold_price = 0 # 平多后记录持仓价格为0 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "卖出平多", price, amount, amount * price, 0, "none", 0, profit, self.total_profit, self.total_asset) # 如果当前持多且最低价小于等于持仓均价*止损幅度,触发止损,卖出平多止损 if round(self.position.amount(), 1) >= self.precision and self.market.low( -1, kline=kline) <= self.hold_price * self.long_stop: price = float(self.hold_price * self.long_stop) amount = float(self.position.amount()) profit = (price - self.hold_price) * amount # 计算逻辑盈亏 self.total_profit += profit self.total_asset += profit info = self.exchange.sell(price, amount) push("此次盈亏:{} 当前总资金:{}".format(profit, self.total_asset) + str(info)) self.hold_price = 0 # 平多后记录持仓价格为0 storage.mysql_save_strategy_run_info( self.database, self.datasheet, timestamp, "卖出止损", price, amount, amount * price, 0, "none", 0, profit, self.total_profit, self.total_asset) self.counter += 1 # 计数器加1,控制此根k线上不再下单 except Exception as e: logger.info()