├── README.md ├── compare_change_and_correct_ratio.png ├── profit_curve.png └── stratege.py /README.md: -------------------------------------------------------------------------------- 1 | # 量化策略 2 | 3 | 基于FMZ平台和python 目前已复现4个策略 自己开发了1个基于HMM的策略,趋势预测正确率可达85% 4 | 5 | ###### HMM效果图 6 | 7 | ![compare_change_and_correct_ratio](compare_change_and_correct_ratio.png) 8 | 9 | ![profit_curve](profit_curve.png) -------------------------------------------------------------------------------- /compare_change_and_correct_ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydownacai/Quant-Strategy/5cf16b3ed7d2de4ae46713f5f9377e90fa197d0e/compare_change_and_correct_ratio.png -------------------------------------------------------------------------------- /profit_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydownacai/Quant-Strategy/5cf16b3ed7d2de4ae46713f5f9377e90fa197d0e/profit_curve.png -------------------------------------------------------------------------------- /stratege.py: -------------------------------------------------------------------------------- 1 | default_setting = '''backtest 2 | start: 2019-06-10 00:00:00 3 | end: 2019-08-10 00:00:00 4 | period: 1m 5 | exchanges: [{"eid":"Bitfinex","currency":"BTC_USD","balance":10000,"stocks":3}]''' 6 | from fmz import * 7 | import pandas as pd 8 | from matplotlib.pylab import date2num ## 导入日期到数值一一对应的转换工具 9 | import json 10 | from datetime import datetime,timedelta 11 | import matplotlib.pyplot as plt 12 | import matplotlib 13 | import random 14 | from hmmlearn import hmm 15 | from dateutil.parser import parse ## 导入转换到指定格式日期的工具 16 | import numpy as np 17 | matplotlib.rcParams['font.sans-serif'] = ['SimHei'] 18 | matplotlib.rcParams['font.family']='sans-serif' 19 | #解决负号'-'显示为方块的问题 20 | matplotlib.rcParams['axes.unicode_minus'] = False 21 | import mpl_finance as mpf 22 | 23 | class PeriodTooLess(Exception): 24 | def __init__(self): 25 | pass 26 | def TIME_STAMP(TIME,mode = 1): 27 | '返回字符串时间戳' 28 | if mode: 29 | return int(time.mktime(time.strptime(TIME+' 08:00:00','%Y-%m-%d %H:%M:%S'))) 30 | else: 31 | return int(time.mktime(time.strptime(TIME,'%Y-%m-%d %H:%M:%S'))) 32 | ALL_month = [ 33 | ] 34 | ALL_day = [] 35 | startday = datetime.strptime('2018-01-01 00:00:00', '%Y-%m-%d %H:%M:%S') 36 | endday = datetime.strptime('2019-08-10 00:00:00', '%Y-%m-%d %H:%M:%S') 37 | while True: 38 | ALL_day.append(str(startday)) 39 | if str(startday) == str(endday): 40 | break 41 | lastday = startday - timedelta(days=1) 42 | if lastday.month != startday.month: 43 | ALL_month.append(str(startday)) 44 | startday = startday + timedelta(days=1) 45 | class settings: 46 | def __init__(self,start = "2019-06-10 00:00:00",end="2019-07-10 00:00:00",eid = "Bitfinex",currency ="BTC_USD",balance = "10000",stocks = "1",period = "1d"): 47 | self.setting_str = default_setting 48 | self.settings = { 49 | "start":start, 50 | "end" :end, 51 | "eid" :eid, 52 | "currency":currency, 53 | "balance":str(balance), 54 | "stocks":str(stocks), 55 | "period":period 56 | } 57 | self.setting_str = self.setting_str.replace("2019-06-10 00:00:00",self.settings["start"]) 58 | self.setting_str = self.setting_str.replace("2019-08-10 00:00:00",self.settings["end"]) 59 | self.setting_str = self.setting_str.replace("Bitfinex",self.settings["eid"]) 60 | self.setting_str = self.setting_str.replace("BTC_USD",self.settings["currency"]) 61 | self.setting_str = self.setting_str.replace("10000",self.settings["balance"]) 62 | self.setting_str = self.setting_str.replace("stocks:3","stocks:{}".format(float(self.settings["stocks"]))) 63 | self.setting_str = self.setting_str.replace("1m",self.settings["period"]) 64 | def export(self): 65 | return self.setting_str 66 | def get_day(timestamp): 67 | '返回月份 与 日' 68 | format_time = time.localtime(timestamp/1000) 69 | return (format_time[0],format_time[1],format_time[2]) 70 | class strategy: 71 | def __init__(self): 72 | pass 73 | def init(self,PRINT = False): 74 | self.task = VCtx(self.setting) 75 | self.init_account = exchange.GetAccount() 76 | self.realizible_profit = 0 77 | if PRINT: 78 | print("Strategy Name:",self.name) 79 | print("Setting:", self.setting) 80 | #print(self.setting) 81 | #print("初始账户信息:",self.init_account) 82 | def main(self): 83 | while True: 84 | self.onTick() 85 | Sleep(1000) 86 | def exit_f(self): 87 | pass 88 | def format_outcome(self): 89 | for key in self.outcome: 90 | if key == "RuntimeLogs": 91 | continue 92 | if type(self.outcome[key]) != type([]) and type(self.outcome[key]) != type({}): 93 | print(key, self.outcome[key]) 94 | elif type(self.outcome[key]) == type({}): 95 | print(key) 96 | for small_key in self.outcome[key]: 97 | print(' ', small_key, self.outcome[key][small_key]) 98 | else: 99 | print(key) 100 | for item in self.outcome[key]: 101 | for small_key in item: 102 | print(' ', small_key, item[small_key]) 103 | #for Log in self.outcome['RuntimeLogs']: 104 | #print(Log) 105 | def run(self,PRINT = False): 106 | '''运行回测然后得到收益''' 107 | self.init(PRINT) 108 | try: 109 | self.main() 110 | except EOFError: 111 | #print("回测结束\n") 112 | self.exit_f() 113 | self.outcome = json.loads(self.task.Join()) 114 | for Log in self.outcome['RuntimeLogs']: 115 | Log[1] = get_day(Log[1]) 116 | #print(Log) 117 | Snapshort = self.outcome['Snapshort'][0] 118 | balance_change = Snapshort['Balance'] - self.init_account['Balance'] 119 | stock_change = Snapshort['Stocks'] - self.init_account['Stocks'] 120 | self.realizible_profit = balance_change + stock_change * Snapshort['Symbols']['BTC_USD_Bitfinex']['Last'] - Snapshort['Commission'] 121 | self.commission = float(Snapshort['Commission']) 122 | #print(Snapshort) 123 | def Multiperiodbacktest(self,periods,filename = time.strftime("%b_%d_%Y_%H_%M_%S", time.localtime(time.time()))): 124 | result = { 125 | "period":[], 126 | "profit":[], 127 | "commission":[], 128 | } 129 | 130 | if len(periods) <= 1: 131 | raise PeriodTooLess 132 | labels = [] 133 | this_dir = self.dir +filename + "/" 134 | try: 135 | os.mkdir(this_dir) 136 | except: 137 | pass 138 | write = pd.ExcelWriter(this_dir+ "result.xls") 139 | for period in periods: 140 | start = period[0] 141 | end = period[1] 142 | if start.endswith("00:00:00"): 143 | start = start[:10] 144 | if end.endswith("00:00:00"): 145 | end = end[:10] 146 | key = start 147 | labels.append(start) 148 | print('period:',start + " - " + end) 149 | result['period'].append(start + " - " + end) 150 | self.backtest(start,end,False,False) 151 | result['profit'].append(self.realizible_profit) 152 | result['commission'].append(self.commission) 153 | pd.DataFrame(result).to_excel(write,sheet_name="具体收益") 154 | average_profit = sum(result['profit'])/len(result['profit']) 155 | samplenum = len(result['profit']) 156 | inflowmonthnum = sum([1 if profit > 0 else 0 for profit in result['profit']]) 157 | metrics = { 158 | "average_profit":[average_profit], 159 | "极差":[max(result['profit']) - min(result['profit'])], 160 | "正收益月数":[inflowmonthnum], 161 | "正收益月数比率":[round(inflowmonthnum/samplenum,2)], 162 | "平均手续费":[round(sum(result['commission'])/samplenum,2)], 163 | "参数:":[" "] 164 | } 165 | for key in self.param: 166 | metrics[key] = [self.param[key]] 167 | pd.DataFrame(metrics).to_excel(write, sheet_name="指标") 168 | write.save() 169 | fig = plt.figure(figsize=(20,8),frameon =False) 170 | ax = plt.gca() 171 | ax.xaxis.set_ticks_position('bottom') 172 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 173 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 174 | ax.spines['left'].set_position(('data', 0)) 175 | plt.plot() 176 | plt.plot(list(range(samplenum)),result['profit'],label = 'profit') 177 | plt.plot(list(range(samplenum)),[average_profit]*samplenum,label = "average = {}".format(average_profit)) 178 | plt.legend(loc = 'upper right') 179 | plt.xlabel("period") 180 | plt.ylabel("profit") 181 | plt.xticks(list(range(samplenum)),labels,rotation = 45) 182 | plt.title("profit curve") 183 | plt.savefig(this_dir + "profitcurve.png") 184 | print("done!:",filename) 185 | return result 186 | def dailybacktest(self,gap): 187 | period = [] 188 | for i in range(len(ALL_day) - gap): 189 | period.append((ALL_day[i],ALL_day[i+gap])) 190 | self.Multiperiodbacktest(period,"{}日内交易回测".format(gap)) 191 | def monthlybacktest(self,gap): 192 | period = [] 193 | for i in range(len(ALL_month) - gap): 194 | period.append((ALL_month[i],ALL_month[i+gap])) 195 | suffix = "" 196 | for key in self.param: 197 | suffix += str(key) +"="+str(self.param[key]) + "&" 198 | self.Multiperiodbacktest(period,str(gap) + "月内交易回测(param_" + suffix[:-1]+")") 199 | def backtest(self,start,end,PRINTSETTING=True,PRINTLOG = False): 200 | #self.param["start"] = start 201 | #self.param['end'] = end 202 | if not start.endswith("00:00:00"): 203 | start+= " 00:00:00" 204 | if not end.endswith("00:00:00"): 205 | end += " 00:00:00" 206 | self.setting = settings(start = start,end = end,period='1d').export() 207 | 208 | self.run(PRINT=PRINTSETTING) 209 | print('profit', self.realizible_profit) 210 | if PRINTLOG: 211 | for log in self.outcome['RuntimeLogs'][::-1]: 212 | print(log) 213 | @property 214 | def param_suffix_str(self): 215 | suffix = "" 216 | for key in self.param: 217 | suffix += str(key) +"="+str(self.param[key]) + "&" 218 | suffix = suffix[:-1] 219 | return suffix 220 | @ property 221 | def setting(self): 222 | try: 223 | return self._setting 224 | except AttributeError: 225 | self._setting = settings().export() 226 | return self._setting 227 | @ setting.setter 228 | def setting(self,value): 229 | self._setting = value 230 | @property 231 | def param(self): 232 | try: 233 | return self._param 234 | except AttributeError: 235 | self._param = {} 236 | return self._param 237 | @param.setter 238 | def param(self,value): 239 | self._param = value 240 | def __getitem__(self, item): 241 | return self._param[item] 242 | def __setitem__(self, key, value): 243 | self._param[key] = value 244 | @property 245 | def name(self): 246 | if self._name not in locals().keys(): 247 | self._name = "Unnamed Strategy" 248 | return self._name 249 | @name.setter 250 | def name(self,value): 251 | self._name = value 252 | self.dir = self._name + "/" 253 | if os.path.exists(self.dir) == False: 254 | os.mkdir(self.dir) 255 | @staticmethod 256 | def ticktime(tick): 257 | return (time.strftime("%b %d %Y %H:%M:%S", time.localtime(int(str(tick['Time'])[:-3])))) 258 | class R_breaker(strategy): 259 | ''' 260 | p : 每次日内交易的btc个数 261 | ''' 262 | def load_param(self,p = 0.1): 263 | self 264 | self.p = p 265 | def main(self): 266 | LastDay = get_day(exchange.GetRecords(PERIOD_D1)[-1].Time - 1000) 267 | LastTimeStamp = exchange.GetRecords(PERIOD_D1)[-1].Time / 1000 268 | yestoday = 0 269 | BreakSsteup = False 270 | BreakBsteup = False 271 | STATE = "IDLE" 272 | Fan = False 273 | while True: 274 | ticker = exchange.GetTicker() 275 | NowDay = get_day(ticker.Time) 276 | if NowDay != LastDay: 277 | STATE = "IDLE" 278 | yestoday = exchange.GetRecords(PERIOD_D1)[-2] # -1是今日 279 | BreakSsteup = False 280 | BreakBsteup = False 281 | Log(NowDay) 282 | Fan = False 283 | NowPrice = ticker.Last 284 | High = yestoday.High 285 | Low = yestoday.Low 286 | Close = yestoday.Close 287 | Open = yestoday.Open 288 | TodayHigh = ticker.High 289 | TodayLow = ticker.Low 290 | Ssteup = High + 0.35 * (Close - Low) # 观察卖出价 291 | Bsteup = Low - 0.35 * (High - Close) # 观察买入价 292 | Senter = 1.07 / 2 * (High + Low) - 0.07 * Low # 反转卖出价 293 | Benter = 1.07 / 2 * (High + Low) - 0.07 * High # 反转买入价 294 | Bbreak = Ssteup + 0.25 * (Ssteup - Bsteup) # 突破买入价 295 | Sbreak = Bsteup - 0.25 * (Ssteup - Bsteup) # 突破卖出价 296 | if TodayHigh > Ssteup: 297 | BreakSsteup = True 298 | if TodayLow < Bsteup: 299 | BreakBsteup = True 300 | ''' 301 | 空仓的情况下:趋势跟踪 302 | 1.如果盘中价格超过突破买入价,则采取趋势策略,即在该点位开仓做多 303 | 2.如果盘中价格跌破突破卖出价,则采取趋势策略,即在该点位开仓做空 304 | 持仓的情况下: 305 | 1.当日内最高价超过观察卖出价后,盘中价格出现回落,且进一步跌破反转卖出价构成的支撑线时,采取反转策略,即在该点位(反手、开仓)做空; 306 | 2.当日内最低价低于观察买入价后,盘中价格出现反弹,且进一步超过反转买入价构成的阻力线时,采取反转策略,即在该点位(反手、开仓)做多; 307 | ''' 308 | if STATE == "IDLE": 309 | if NowPrice > Bbreak: 310 | Log("价格超过突破买入价,趋势跟踪开仓做多") 311 | exchange.Buy(ticker.Sell * 1.001, self.p) 312 | STATE = "LONG" 313 | elif NowPrice < Sbreak: 314 | Log("价格跌破突破卖出价,趋势跟踪开仓做空") 315 | exchange.Sell(ticker.Buy * 0.999, self.p) 316 | STATE = "SHORT" 317 | else: 318 | if BreakSsteup and NowPrice < Senter and STATE == "LONG" and Fan == False: 319 | Log("当日内最高价超过观察卖出价,盘中价格回落跌破反转卖出价,反手做空") 320 | exchange.Sell(ticker.Buy * 0.999, 2 * self.p) 321 | STATE = "SHORT" 322 | Fan = True 323 | if BreakBsteup and NowPrice > Benter and STATE == "SHORT"and Fan == False: 324 | Log("当日内最低价低于观察买入价,盘中价格回落跌破反转卖出价,反手做多") 325 | exchange.Buy(ticker.Sell * 1.001, 2 * self.p) 326 | STATE = "LONG" 327 | Fan = True 328 | '''每日收盘前2分钟,进行平仓''' 329 | if ticker.Time / 1000 - LastTimeStamp >= 86250: 330 | LastTimeStamp = LastTimeStamp + 86400 331 | if STATE == "LONG": 332 | Log("每日收盘平仓") 333 | exchange.Sell(ticker.Buy * 0.999, self.p) # 做多后做空平仓 334 | if STATE == "SHORT": 335 | Log("每日收盘平仓") 336 | exchange.Buy(ticker.Sell * 1.001, self.p) # 做空后做多平仓 337 | Log(exchange.GetAccount()) 338 | LastDay = NowDay 339 | Sleep(500) 340 | class Dual_Thrust(strategy): 341 | def __init__(self): 342 | self.name = 'DualThrust' 343 | self.param = { 344 | "N":5, 345 | "k1":0.9, 346 | "k2":0.5, 347 | "p":1 348 | } 349 | def main(self): 350 | Histroy_record = exchange.GetRecords() 351 | dopen = Histroy_record[-1].Open # 每日的开盘价 352 | LastDay = get_day(Histroy_record[-1].Time / 1000) # 上一次获取行情的日期 353 | Nperiod = Histroy_record[-1 * self.param['N'] - 1:][:-1] # 前N日的K数据 354 | Track = self.get_track(Nperiod,dopen) # 获取上下轨数据 355 | LastState = 'IDLE' 356 | while True: 357 | ticker = exchange.GetTicker() 358 | #print(time.strftime("%b %d %Y %H:%M:%S",time.localtime(int(str(ticker['Time'])[:-3])))) 359 | NowPrice = ticker.Last # 当前市场最后成交价格 360 | NowDay = get_day(ticker.Time) # 当前日期 361 | if (NowDay != LastDay): 362 | # 进入了新的一天,重新更新前N天数据与今日开盘价,与今日上下轨点数 363 | Histroy_record = exchange.GetRecords(PERIOD_D1) 364 | 365 | #for tick in Histroy_record: 366 | # print(time.strftime("%b %d %Y %H:%M:%S",time.localtime(int(str(tick['Time'])[:-3])))) 367 | Nperiod = Histroy_record[-1 * self.param['N'] - 1:][:-1] 368 | dopen = Histroy_record[-1].Open 369 | Track = self.get_track(Nperiod,dopen) 370 | Log(NowDay, ':', Log(exchange.GetAccount())) 371 | if (NowPrice > Track['uptrack'] and LastState != 'LONG'): 372 | Log('当前市价格', NowPrice, '突破做多触发价:', Track['downtrack']) 373 | # cancel_pending_orders(ORDER_TYPE_SELL) # 撤回所有卖单 374 | if LastState == "IDLE": 375 | exchange.Buy(ticker.Sell * 1.001, self.param['p']) 376 | else: 377 | exchange.Buy(ticker.Sell * 1.001, 2 * self.param['p']) 378 | # days_operation = True 379 | LastState = 'LONG' 380 | if (NowPrice < Track['downtrack'] and LastState != 'SHORT'): 381 | Log('当前市价格', NowPrice, '突破做空触发价:', Track['downtrack']) 382 | # cancel_pending_orders(ORDER_TYPE_BUY) # 撤回所有买单 383 | if LastState == "IDLE": 384 | exchange.Sell(ticker.Buy * 0.999, self.param['p']) 385 | else: 386 | exchange.Sell(ticker.Buy * 0.999, 2 * self.param['p']) 387 | LastState = 'SHORT' 388 | LastDay = NowDay # 更新上次获取行情的日期 389 | def get_track(self,Nperiod,dopen): 390 | '获得上下轨点数' 391 | HH = max([day.High for day in Nperiod]) 392 | LC = min([day.Close for day in Nperiod]) 393 | HC = max([day.Close for day in Nperiod]) 394 | LL = min([day.Low for day in Nperiod]) 395 | RANGE = max(HH - LC, HC - LL) 396 | return {'uptrack': dopen + self.param['k1'] * RANGE, 'downtrack': dopen - self.param['k2'] * RANGE} 397 | class 菲阿里四价(strategy): 398 | def __init__(self): 399 | self.name = "菲阿里四价" 400 | self.param['p'] = 0.1 401 | def main(self): 402 | STATE = "IDLE" 403 | LastDay = get_day(exchange.GetRecords(PERIOD_D1)[-1].Time) 404 | LastTimeStamp = exchange.GetRecords(PERIOD_D1)[-1].Time / 1000 405 | while True: 406 | p = self.param['p'] 407 | ticker = exchange.GetTicker() 408 | NowDay = get_day(ticker.Time) 409 | #TODAY = exchange.GetRecords(PERIOD_D1)[-1] 410 | yestoday = exchange.GetRecords(PERIOD_D1)[-2] # -1是今日 411 | uptrack = yestoday.High 412 | downtrack = yestoday.Low 413 | NowPrice = ticker.Last 414 | if NowDay != LastDay: 415 | STATE = "IDLE" 416 | Log(NowDay) 417 | '''日内价格突破上下轨,进行做多或者做空''' 418 | if STATE == "IDLE" and NowPrice > uptrack: 419 | exchange.Buy(ticker.Sell * 1.001, p) 420 | STATE = "LONG" 421 | if STATE == "IDLE" and NowPrice < downtrack: 422 | exchange.Sell(ticker.Buy * 0.999, p) 423 | STATE = "SHORT" 424 | '''每日收盘前2分钟,进行平仓''' 425 | if ticker.Time / 1000 - LastTimeStamp >= 86250: 426 | LastTimeStamp = LastTimeStamp + 86400 427 | if STATE == "LONG": 428 | exchange.Sell(ticker.Buy * 0.999, p) # 做多后做空平仓 429 | if STATE == "SHORT": 430 | exchange.Buy(ticker.Sell * 1.001, p) # 做空后做多平仓 431 | LastDay = NowDay 432 | Sleep(500) 433 | class skypark(strategy): 434 | def load_param(self,p = 0.1,k1=1.01,k2=0.99): 435 | self.p = p 436 | self.k1 = k1 437 | self.k2 = k2 438 | def main(self): 439 | p = self.p 440 | k1 = self.k1 441 | k2 = self.k2 442 | History = exchange.GetRecords(PERIOD_D1) 443 | TODAY = History[-1] 444 | Yestoday = History[-2] 445 | LastTimeStamp = TODAY.Time / 1000 446 | LastDay = get_day(TODAY.Time - 1000) 447 | DayFirstCandle = 0 # 每日第一个k线 448 | DayOpen = TODAY.Open 449 | LastOpen = Yestoday.Open 450 | STATE = "IDLE" 451 | while True: 452 | ticker = exchange.GetTicker() 453 | NowDay = get_day(ticker.Time) 454 | if LastDay != NowDay: 455 | '进入新的更新每日第一条k线与昨日和今日的开盘价' 456 | DayFirstCandle = ticker 457 | History = exchange.GetRecords(PERIOD_D1) 458 | TODAY = History[-1] 459 | Yestoday = History[-2] 460 | DayOpen = TODAY.Open 461 | LastOpen = Yestoday.Open 462 | if DayOpen > LastOpen * k1: 463 | Log("今日高开", "今日开盘价:", DayOpen, "昨日开盘价", LastOpen) 464 | if DayOpen < LastOpen * k2: 465 | Log("今日低开", "今日开盘价:", DayOpen, "昨日开盘价", LastOpen) 466 | STATE = "IDLE" 467 | Nowprice = ticker.Last 468 | if DayOpen > LastOpen * k1 and STATE == "IDLE" and Nowprice > DayFirstCandle.High and ticker.Time / 1000 - LastTimeStamp < 86250: 469 | Log("价格", Nowprice, "突破上轨", DayFirstCandle.High, ",买入开仓") 470 | exchange.Buy(ticker.Sell * 1.001, p) 471 | STATE = "LONG" 472 | if DayOpen < LastOpen * k2 and STATE == "IDLE" and Nowprice < DayFirstCandle.Low and ticker.Time / 1000 - LastTimeStamp < 86250: 473 | Log("价格", Nowprice, "突破下轨", DayFirstCandle.High, ",卖入开仓") 474 | exchange.Sell(ticker.Buy * 0.999, p) 475 | STATE = "SHORT" 476 | '''每日收盘前2分钟,进行平仓''' 477 | if ticker.Time / 1000 - LastTimeStamp >= 86250: 478 | LastTimeStamp = LastTimeStamp + 86400 479 | if STATE == "LONG": 480 | Log("做多后做空平仓") 481 | exchange.Sell(ticker.Buy * 0.999, p) # 做多后做空平仓 482 | if STATE == "SHORT": 483 | Log("做空后做多平仓") 484 | exchange.Buy(ticker.Sell * 1.001, p) # 做空后做多平仓 485 | LastDay = NowDay # 更新获取上一次行情的日期 486 | class Dual_Thrust_improved(strategy): 487 | def __init__(self): 488 | self.name = 'DualThrust_improved' 489 | self.defaultparam = { 490 | "N": 5, 491 | "k1": 0.4, 492 | "k2": 0.4, 493 | "p": 0.1, 494 | 'stop': 1000, 495 | "KDJ_N":9, 496 | } 497 | self.param = self.defaultparam.copy() 498 | self.k = {} 499 | self.d = {} 500 | self.j = {} 501 | 502 | def main(self): 503 | Histroy_record = exchange.GetRecords(PERIOD_D1) 504 | dopen = Histroy_record[-1].Open # 每日的开盘价 505 | LastDay = get_day(Histroy_record[-1].Time / 1000) # 上一次获取行情的日期 506 | Nperiod = Histroy_record[-1 * self.param['N'] - 1:][:-1] # 前N日的K数据 507 | Track = self.get_track(Nperiod,dopen) # 获取上下轨数据 508 | LastState = 'IDLE' 509 | self.k[LastDay] = 50 510 | self.d[LastDay] = 50 511 | self.price = 0 512 | while True: 513 | ticker = exchange.GetTicker() 514 | #print(time.strftime("%b %d %Y %H:%M:%S",time.localtime(int(str(ticker['Time'])[:-3])))) 515 | #print(ticker.Last) 516 | NowPrice = ticker.Last # 当前市场最后成交价格 517 | NowDay = get_day(ticker.Time) # 当前日期 518 | #print(time.strftime("%b %d %Y %H:%M:%S", time.localtime(int(str(ticker['Time'])[:-3])))) 519 | if (NowDay != LastDay): 520 | # 进入了新的一天,重新更新前N天数据与今日开盘价,与今日上下轨点数 521 | Histroy_record = exchange.GetRecords(PERIOD_D1) 522 | #for tick in Histroy_record: 523 | # print(time.strftime("%b %d %Y %H:%M:%S", time.localtime(int(str(tick['Time'])[:-3])))) 524 | #exit() 525 | Nperiod = Histroy_record[-1 * self.param['N'] - 1:][:-1] 526 | dopen = Histroy_record[-1].Open 527 | self.KDJ(NowDay) 528 | if self.k[NowDay] > 50 and self.d[NowDay] > 50 and self.j[NowDay] > 50: 529 | self.param['k1'] = self.defaultparam['k1'] - 0.1 530 | self.param['k2'] = self.defaultparam['k2'] 531 | #为做多市场,调整下轨 532 | if self.k[NowDay] < 50 and self.d[NowDay] < 50 and self.j[NowDay] < 50: 533 | self.param['k1'] = self.defaultparam['k1'] 534 | self.param['k2'] = self.defaultparam['k2'] - 0.1 535 | Track = self.get_track(Nperiod, dopen) 536 | Log(NowDay, ':', Log(exchange.GetAccount())) 537 | #止损 538 | if LastState == 'LONG' and self.price - ticker.Buy*0.999 >= self.param['stop']: 539 | exchange.Sell(ticker.Buy * 0.999, 1 * self.param['p']) 540 | LastState = "IDLE" 541 | if LastState == 'SHORT' and ticker.Sell*1.001 - self.price >= self.param['stop']: 542 | exchange.Buy(ticker.Sell *1.001, 1 * self.param['p']) 543 | LastState = "IDLE" 544 | #KDJ条件判断市场 545 | if (NowPrice > Track['uptrack'] and LastState != 'LONG'): 546 | Log('当前市价格', NowPrice, '突破做多触发价:', Track['downtrack']) 547 | # cancel_pending_orders(ORDER_TYPE_SELL) # 撤回所有卖单 548 | if LastState == "IDLE": 549 | exchange.Buy(ticker.Sell * 1.001, self.param['p']) 550 | else: 551 | exchange.Buy(ticker.Sell * 1.001, 2 * self.param['p']) 552 | # days_operation = True 553 | self.price = ticker.Sell * 1.001 554 | LastState = 'LONG' 555 | if (NowPrice < Track['downtrack'] and LastState != 'SHORT'): 556 | Log('当前市价格', NowPrice, '突破做空触发价:', Track['downtrack']) 557 | # cancel_pending_orders(ORDER_TYPE_BUY) # 撤回所有买单 558 | if LastState == "IDLE": 559 | exchange.Sell(ticker.Buy * 0.999, self.param['p']) 560 | else: 561 | exchange.Sell(ticker.Buy * 0.999, 2 * self.param['p']) 562 | self.price = ticker.Buy * 0.999 563 | LastState = 'SHORT' 564 | 565 | LastDay = NowDay # 更新上次获取行情的日期 566 | def get_track(self,Nperiod,dopen): 567 | '获得上下轨点数' 568 | HH = max([day.High for day in Nperiod]) 569 | LC = min([day.Close for day in Nperiod]) 570 | HC = max([day.Close for day in Nperiod]) 571 | LL = min([day.Low for day in Nperiod]) 572 | RANGE = max(HH - LC, HC - LL) 573 | return {'uptrack': dopen + self.param['k1'] * RANGE, 'downtrack': dopen - self.param['k2'] * RANGE} 574 | def RSV(self,Nperiod): 575 | Cn = Nperiod[-1].Close 576 | Ln = min([day.Close for day in Nperiod]) 577 | Hn = max([day.High for day in Nperiod]) 578 | return (Cn - Ln) / (Hn - Ln) * 100 579 | def KDJ(self,today): 580 | records = exchange.GetRecords(PERIOD_D1); 581 | kdj = TA.KDJ(records,self.param['KDJ_N'], 3, 3); 582 | #Log("k:", kdj[0], "d:", kdj[1], "j:", kdj[2]); 583 | self.k[today] = kdj[0][-1] 584 | self.d[today] = kdj[0][-1] 585 | self.j[today] = kdj[0][-1] 586 | class HMM(strategy): 587 | def __init__(self): 588 | self.name = "Hiden Markov " 589 | self.winratio = [] 590 | self.param = { 591 | "p":0.1, 592 | 'gap':2, 593 | "days":200, 594 | } 595 | def testmetric(self): 596 | global ALL_day 597 | periods = [] 598 | total_real = [] 599 | total_metr= [] 600 | ALL_day = ALL_day[-10:] 601 | for i in range(len(ALL_day)-1): 602 | periods.append((ALL_day[i],ALL_day[i+1])) 603 | labels = [] 604 | this_dir = self.dir + self.param['metrics']+ "/" 605 | try: 606 | os.mkdir(this_dir) 607 | except: 608 | pass 609 | for period in periods: 610 | start = period[0] 611 | end = period[1] 612 | if start.endswith("00:00:00"): 613 | start = start[:10] 614 | if end.endswith("00:00:00"): 615 | end = end[:10] 616 | print('period:',start + " - " + end) 617 | self.backtest(start,end,False,False) 618 | labels.append(start) 619 | total_real += self.real 620 | total_metr += self.metrics 621 | samplenum = len(total_real) 622 | fig = plt.figure(figsize=(30,8)) 623 | ax = plt.gca() 624 | ax.xaxis.set_ticks_position('bottom') 625 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 626 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 627 | ax.spines['left'].set_position(('data', 0)) 628 | ax.plot(list(range(samplenum)), total_real, label='真实成交价变动') 629 | ax2 = ax.twinx() 630 | ax2.set_xlabel("sample") 631 | ax2.set_ylabel("number") 632 | # ax2.set_ylim((0,100)) 633 | plt.title(self.param['title']) 634 | if self.param['metrics'] == 'MACD': 635 | DIF = [] 636 | DEA = [] 637 | MACD = [] 638 | for item in total_metr: 639 | DIF.append(item[0]) 640 | DEA.append(item[1]) 641 | MACD.append(item[2]) 642 | ax2.plot(list(range(samplenum)), DIF, color='red', 643 | label="DIF") 644 | ax2.plot(list(range(samplenum)), DEA, color='blue', 645 | label="DEA") 646 | ax2.plot(list(range(samplenum)), MACD, color='green', 647 | label="MACD") 648 | ax2.legend(loc='upper right') 649 | plt.savefig(this_dir +self.param['title'] + ".png") 650 | print("done!:") 651 | if self.param['metrics'] == 'RSI': 652 | ax2.plot(list(range(samplenum)), total_metr, color='red', 653 | label="该时刻前{}条1分钟k线所求出{}值".format(self.param['metrics_period'], self.param['metrics'])) 654 | ax2.legend(loc='upper right') 655 | 656 | plt.savefig(this_dir + str(self.param['metrics_period'])+'_'+self.param['title'] + ".png") 657 | print("done!:") 658 | if self.param['metrics'] == 'OBV': 659 | ax2.plot(list(range(samplenum)), total_metr, color='red', 660 | label="前一时刻所求出{}值".format(self.param['metrics'])) 661 | ax2.legend(loc='upper right') 662 | 663 | plt.savefig(this_dir +self.param['title'] + ".png") 664 | print("done!:") 665 | def testall(self): 666 | global ALL_day 667 | periods = [] 668 | total_real = [] 669 | total_pred = [] 670 | ratio = [] 671 | profits = [] 672 | ALL_day = ALL_day[-1*self.param['days']:] 673 | for i in range(len(ALL_day)-1): 674 | periods.append((ALL_day[i],ALL_day[i+1])) 675 | labels = [] 676 | this_dir = self.dir + "algorithm2" + "/" 677 | try: 678 | os.mkdir(this_dir) 679 | except: 680 | pass 681 | if self.param['gap'] != None: 682 | this_dir = self.dir +"algorithm2" + "/" + str(self.param['gap']) + '/' 683 | else: 684 | this_dir = self.dir + "algorithm2" + "/" 685 | try: 686 | os.mkdir(this_dir) 687 | except: 688 | pass 689 | for period in periods: 690 | start = period[0] 691 | end = period[1] 692 | if start.endswith("00:00:00"): 693 | start = start[:10] 694 | if end.endswith("00:00:00"): 695 | end = end[:10] 696 | print('period:',start + " - " + end) 697 | self.backtest(start,end,False,False) 698 | self.predict = self.predict[:len(self.real)] 699 | if len(self.predict) == 0: 700 | print('跳过一天') 701 | continue 702 | labels.append(start) 703 | total_real += self.real 704 | total_pred += self.predict 705 | ratio.append(self.cal_trend_correct_ratio(self.real,self.predict)) 706 | profits.append(self.realizible_profit) 707 | 708 | sum_profit = [profits[0]] 709 | for i in range(1,len(profits)): 710 | sum_profit.append(sum_profit[-1] + profits[i]) 711 | samplenum = len(total_real) 712 | fig = plt.figure(figsize=(30,5)) 713 | above_figure, axs = plt.subplots(2,1) 714 | change_ax = axs[0] 715 | correct_ax = axs[1] 716 | 717 | ax = change_ax 718 | ax.xaxis.set_ticks_position('bottom') 719 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 720 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 721 | ax.spines['left'].set_position(('data', 0)) 722 | ax.plot(list(range(samplenum)),total_real,label = '真实成交价变动') 723 | ax.plot(list(range(samplenum)),total_pred,label = "前一时刻指标值") 724 | ax.legend(loc = 'upper right') 725 | ax.set_xlabel("sample") 726 | ax.set_ylabel("number") 727 | ax.set_title("{}日内成交价真实变动与预测变动曲线".format(self.param['days'])) 728 | 729 | ax = correct_ax 730 | ax.xaxis.set_ticks_position('bottom') 731 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 732 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 733 | ax.spines['left'].set_position(('data', 0)) 734 | ax.plot(list(range(len(ratio))),ratio,label = '趋势预测正确率') 735 | ax.plot(list(range(len(ratio))),[np.average(ratio)]*len(ratio),label = '样本平均预测正确率') 736 | ax.legend(loc = 'upper right') 737 | ax.set_xlabel("sample") 738 | ax.set_ylabel("ratio") 739 | ax.set_title("{}日预测正确率".format(self.param['days'])) 740 | plt.savefig(this_dir + "compare_change_and_correct_ratio_{}.png".format(self.param_suffix_str)) 741 | 742 | fig = plt.figure() 743 | ax = plt.gca() 744 | ax.xaxis.set_ticks_position('bottom') 745 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 746 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 747 | ax.spines['left'].set_position(('data', 0)) 748 | plt.plot(list(range(len(labels))),profits,label = '每日收益曲线') 749 | plt.plot(list(range(len(labels))),sum_profit,label = "累积收益曲线") 750 | plt.legend(loc = 'upper left') 751 | plt.xlabel("day") 752 | plt.ylabel("number") 753 | plt.title("{}日收益曲线".format(self.param['days'])) 754 | plt.savefig(this_dir + "profit_curve_{}.png".format(self.param_suffix_str)) 755 | print("done!:") 756 | def testprofit(self): 757 | global ALL_day 758 | periods = [] 759 | profits = [] 760 | labels = [] 761 | ALL_day = ALL_day[-100:] 762 | for i in range(len(ALL_day)-1): 763 | periods.append((ALL_day[i],ALL_day[i+1])) 764 | for period in periods: 765 | start = period[0] 766 | end = period[1] 767 | if start.endswith("00:00:00"): 768 | start = start[:10] 769 | if end.endswith("00:00:00"): 770 | end = end[:10] 771 | print('period:',start + " - " + end) 772 | labels.append(start) 773 | self.backtest(start,end,False,False) 774 | profits.append(self.realizible_profit) 775 | this_dir = self.dir +"algorithm2" + "/" 776 | sum_profit = [profits[0]] 777 | for i in range(1,len(profits)): 778 | sum_profit.append(sum_profit[-1] + profits[i]) 779 | 780 | try: 781 | os.mkdir(this_dir) 782 | except: 783 | pass 784 | samplenum = len(labels) 785 | fig = plt.figure() 786 | ax = plt.gca() 787 | ax.xaxis.set_ticks_position('bottom') 788 | ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 789 | ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 790 | ax.spines['left'].set_position(('data', 0)) 791 | plt.plot(list(range(samplenum)),profits,label = '每日收益曲线') 792 | plt.plot(list(range(samplenum)),sum_profit,label = "累积收益曲线") 793 | plt.legend(loc = 'upper right') 794 | plt.xlabel("day") 795 | plt.ylabel("number") 796 | plt.title("100日收益曲线") 797 | plt.savefig(this_dir + "profit_curve_{}.png".format(self.param_suffix_str)) 798 | def plot(self): 799 | pass 800 | def exit_f2(self): 801 | self.predict = self.predict[:len(self.real)] 802 | self.winratio.append(self.cal_trend_correct_ratio(self.real,self.predict)) 803 | def exit_f1(self): 804 | fig = plt.figure(figsize=(12,8)) 805 | plt.plot(self.real,label = "真实的变化") 806 | plt.plot(self.preditct,label = "预测的变化") 807 | plt.title("用10点-12点的数据喂模型") 808 | plt.legend(loc = "upper_left") 809 | suffix = "" 810 | for key in self.param: 811 | suffix += str(key) +"="+str(self.param[key]) + "&" 812 | g = lambda x: 1 if x > 0 else -1 813 | f = lambda x,y: 1 if x == y else 0 814 | trend_real = [g(x) for x in self.real] 815 | trend_predic = [g(x) for x in self.preditct] 816 | ratio = sum([f(trend_real[i],trend_predic[i]) for i in range(len(trend_predic))])/(len(trend_predic)) 817 | suffix += "趋势正确预测率_"+str(round(ratio,2)) 818 | plt.savefig(self.dir + "10点-12点数据训练模型_"+suffix + ".png") 819 | def cal_trend_correct_ratio(self,real,predict): 820 | g = lambda x: 1 if x > 0 else -1 821 | f = lambda x, y: 1 if x == y else 0 822 | trend_real = [g(x) for x in real] 823 | trend_predic = [g(x) for x in predict] 824 | if len(trend_real) == 0: 825 | return np.nan 826 | ratio = sum([f(trend_real[i], trend_predic[i]) for i in range(len(trend_predic))]) / (len(trend_predic)) 827 | return round(ratio,2) 828 | def algorithm1(self): 829 | '''用早上10点-12点的数据喂HMM''' 830 | self.param['n'] = 10 831 | self.param['obeserve'] = 6 832 | self.param['threhold'] = 70 833 | LastDay = "" 834 | STATE = 'IDLE' 835 | X = [] 836 | History = exchange.GetRecords(PERIOD_M1) 837 | lastticker = History[-2] 838 | lts = lastticker.Time / 1000 839 | train_model = False 840 | self.accumulate_profit = 0 841 | self.real = [] 842 | self.predict = [] 843 | self.total_ratio = [] 844 | COMPARE = False 845 | self.profits = [] 846 | NeedStop = False 847 | self.stop = { 848 | "LONG":[], 849 | "SHORT":[], 850 | } 851 | hastart = False 852 | 853 | while True: 854 | ticker = exchange.GetTicker() 855 | #优先止损 亏损保持在5美元以内 856 | self.stop["LONG"] = list(sorted(self.stop["LONG"],key = lambda x:x[0])) 857 | self.stop["SHORT"] = list(sorted(self.stop["SHORT"], key=lambda x: x[0],reverse=True)) 858 | for item in self.stop["LONG"]: 859 | if ticker.Buy * 0.9999 - item[0] >= -1*item[1] * 5 / 0.1: 860 | exchange.Sell(ticker.Buy * 0.9999, 1 * item[1]) 861 | self.stop["LONG"].remove(item) 862 | for item in self.stop["SHORT"]: 863 | if item[0] - ticker.Sell * 1.0001 >= -1*item[1] * 5 / 0.1: 864 | exchange.Buy(ticker.Sell * 1.0001, 1 * item[1]) 865 | self.stop["SHORT"].remove(item) 866 | tickertime = strategy.ticktime(ticker) 867 | NowDay = get_day(ticker.Time) 868 | if NowDay != LastDay: 869 | Log("进入新的一天") 870 | dts = ticker.Time / 1000 871 | LastDay = NowDay 872 | train_model = False 873 | O = [] 874 | 875 | if ticker.Time/1000 - dts >= 36000 and ticker.Time/1000 - dts < 43200: 876 | "十点至十二点的数据喂养模型" 877 | change = ticker.Last - lastticker.Last 878 | X.append([change]) 879 | else: 880 | if not hastart: 881 | last_check_ticker = ticker 882 | hastart = True 883 | if ticker.Time/1000 - dts >= 43200 and ticker.Time/1000 - dts < 64800: 884 | if ticker.Time/1000 - last_check_ticker.Time/1000 < self.param['gap']*60: 885 | #需要跳过 886 | continue 887 | lastticker = last_check_ticker 888 | change = ticker.Last - lastticker.Last 889 | O.append([change]) 890 | "十二点到下午六点进行交易" 891 | if COMPARE: 892 | self.real.append(change) 893 | #print("预测:", self.predict[-1], " 实际:", change,end = " ") 894 | #print("当前成交价:",ticker.Last ," 上一时刻成交价:",lastticker.Last) 895 | #print("买一:",ticker.Buy," 卖一:",ticker.Sell,end=" ") 896 | if STATE == 'LONG': 897 | print("买: ",price) 898 | if price - ticker.Buy * 0.9999 >= self.param['p'] * 30 / 0.1 : 899 | print("变动过大,止损,有仓位需要后续止损") 900 | self.stop[STATE].append((price,self.param['p'])) 901 | else: 902 | exchange.Sell(ticker.Buy * 0.9999, 1 * self.param['p']) 903 | print("收益:",ticker.Buy * 0.9999 - price) 904 | self.profits.append(ticker.Buy * 0.9999 - price) 905 | STATE = "IDLE" 906 | 907 | if STATE == 'SHORT': 908 | print("卖: ", price) 909 | if ticker.Sell * 1.0001 - price>= self.param['p'] * 30 / 0.1 : 910 | print("变动过大,止损,有仓位需要后续止损") 911 | self.stop[STATE].append((price,self.param['p'])) 912 | else: 913 | exchange.Buy(ticker.Sell * 1.0001, 1 * self.param['p']) 914 | print("收益:",price - ticker.Sell * 1.000 ) 915 | self.profits.append(price - ticker.Sell * 1.000) 916 | STATE = "IDLE" 917 | COMPARE = False 918 | #print() 919 | if not train_model: 920 | "先训练模型" 921 | m = hmm.GaussianHMM(n_components=self.param['n'],covariance_type="full") 922 | seen = np.array(X).reshape(-1, 1) 923 | try: 924 | m.fit(seen) 925 | except: 926 | return 0 927 | train_model = True 928 | trans = m.transmat_ 929 | else: 930 | "训练好模型,观察到5个就交易,就对下一个进行交易" 931 | if len(O) == self.param['obeserve'] and len(self.stop["LONG"]) + len(self.stop["SHORT"]) <= 2: 932 | "最多只能有两个未止损" 933 | try: 934 | I = m.predict_proba(O) 935 | except: 936 | return 0 937 | TRAN = I[-1:] 938 | next_state_pro = (TRAN @ trans).T 939 | distriution_means = m.means_ 940 | expected_change = (next_state_pro.T @ distriution_means)[0][0] 941 | if expected_change > self.param['threhold'] and STATE == "IDLE": 942 | exchange.Buy(ticker.Sell *1.0001, 1 * self.param['p']) 943 | STATE = "LONG" 944 | price = ticker.Sell *1.0001 945 | if expected_change < -1*self.param['threhold'] and STATE == "IDLE": 946 | exchange.Sell(ticker.Buy *0.9999, 1 * self.param['p']) 947 | STATE = "SHORT" 948 | price = ticker.Buy *0.9999 949 | self.predict.append(expected_change) 950 | COMPARE = True 951 | O = O[1::] 952 | #O = [] 953 | last_check_ticker = ticker 954 | if ticker.Time/1000 - dts >= 86250: 955 | Log("进入一天末尾准备平仓") 956 | for item in self.stop["LONG"]: 957 | exchange.Sell(ticker.Buy * 0.9999, 1 * item[1]) 958 | self.stop["LONG"].remove(item) 959 | for item in self.stop["SHORT"]: 960 | exchange.Buy(ticker.Sell * 1.0001, 1 * item[1]) 961 | self.stop["SHORT"].remove(item) 962 | STATE = 'IDLE' 963 | Log(exchange.GetAccount()) 964 | print(self.profits) 965 | print(sum(self.profits)) 966 | lastticker = ticker 967 | 968 | def algorithm2(self): 969 | '''用早上10点-12点的数据喂HMM''' 970 | self.param['n'] = 18 971 | self.param['obeserve'] = 5 972 | self.param['threhold'] = 20 973 | self.param['stop'] = True 974 | LastDay = "" 975 | STATE = 'IDLE' 976 | X = [] 977 | History = exchange.GetRecords(PERIOD_M1) 978 | lastticker = History[-2] 979 | lts = lastticker.Time / 1000 980 | train_model = False 981 | self.accumulate_profit = 0 982 | self.real = [] 983 | self.predict = [] 984 | self.total_ratio = [] 985 | COMPARE = False 986 | self.profits = [] 987 | NeedStop = False 988 | self.stop = { 989 | "LONG": [], 990 | "SHORT": [], 991 | } 992 | hastart = False 993 | hastrainstart = False 994 | while True: 995 | ticker = exchange.GetTicker() 996 | # 优先止损 亏损保持在5美元以内 997 | ''' 998 | self.stop["LONG"] = list(sorted(self.stop["LONG"], key=lambda x: x[0])) 999 | self.stop["SHORT"] = list(sorted(self.stop["SHORT"], key=lambda x: x[0], reverse=True)) 1000 | for item in self.stop["LONG"]: 1001 | if ticker.Buy * 0.9999 - item[0] >= -1 * item[1] * 5 / 0.1: 1002 | exchange.Sell(ticker.Buy * 0.9999, 1 * item[1]) 1003 | self.stop["LONG"].remove(item) 1004 | for item in self.stop["SHORT"]: 1005 | if item[0] - ticker.Sell * 1.0001 >= -1 * item[1] * 5 / 0.1: 1006 | exchange.Buy(ticker.Sell * 1.0001, 1 * item[1]) 1007 | self.stop["SHORT"].remove(item) 1008 | ''' 1009 | tickertime = strategy.ticktime(ticker) 1010 | NowDay = get_day(ticker.Time) 1011 | if NowDay != LastDay: 1012 | Log("进入新的一天") 1013 | dts = ticker.Time / 1000 1014 | LastDay = NowDay 1015 | train_model = False 1016 | O = [] 1017 | 1018 | if ticker.Time / 1000 - dts >= 25000 and ticker.Time / 1000 - dts < 43200: 1019 | "十点至十二点的数据喂养模型" 1020 | if not hastrainstart: 1021 | last_train_ticker = lastticker 1022 | hastrainstart = False 1023 | if ticker.Time / 1000 - last_train_ticker.Time / 1000 < self.param['gap'] * 60: 1024 | # 需要跳过 1025 | continue 1026 | change = ticker.Last - last_train_ticker.Last 1027 | X.append([change]) 1028 | last_train_ticker = ticker 1029 | else: 1030 | if not hastart: 1031 | last_check_ticker = ticker 1032 | hastart = True 1033 | if ticker.Time / 1000 - dts >= 43200 and ticker.Time / 1000 - dts < 64800: 1034 | if ticker.Time / 1000 - last_check_ticker.Time / 1000 < self.param['gap'] * 60: 1035 | # 需要跳过 1036 | continue 1037 | lastticker = last_check_ticker 1038 | change = ticker.Last - lastticker.Last 1039 | O.append([change]) 1040 | "十二点到下午六点进行交易" 1041 | if COMPARE: 1042 | self.real.append(change) 1043 | #print("预测:", self.predict[-1], " 实际:", change,end = " ") 1044 | #print("当前成交价:",ticker.Last ," 上一时刻成交价:",lastticker.Last) 1045 | #print("买一:",ticker.Buy," 卖一:",ticker.Sell,end=" ") 1046 | if STATE == 'LONG': 1047 | print("买: ",price) 1048 | exchange.Sell(ticker.Buy * 0.9999, 1 * self.param['p']) 1049 | print("收益:",ticker.Buy * 0.9999 - price) 1050 | self.profits.append(ticker.Buy * 0.9999 - price) 1051 | STATE = "IDLE" 1052 | 1053 | if STATE == 'SHORT': 1054 | print("卖: ", price) 1055 | exchange.Buy(ticker.Sell * 1.0001, 1 * self.param['p']) 1056 | print("收益:",price - ticker.Sell * 1.000 ) 1057 | self.profits.append(price - ticker.Sell * 1.000) 1058 | STATE = "IDLE" 1059 | COMPARE = False 1060 | 1061 | if not train_model: 1062 | "先训练模型" 1063 | m = hmm.GaussianHMM(n_components=self.param['n'], covariance_type="full") 1064 | seen = np.array(X).reshape(-1, 1) 1065 | try: 1066 | m.fit(seen) 1067 | except: 1068 | return 0 1069 | train_model = True 1070 | trans = m.transmat_ 1071 | else: 1072 | "训练好模型,观察到5个就交易,就对下一个进行交易" 1073 | if len(O) == self.param['obeserve'] : 1074 | "最多只能有两个未止损" 1075 | try: 1076 | I = m.predict_proba(O) 1077 | except: 1078 | return 0 1079 | TRAN = I[-1:] 1080 | next_state_pro = (TRAN @ trans).T 1081 | distriution_means = m.means_ 1082 | expected_change = (next_state_pro.T @ distriution_means)[0][0] 1083 | if expected_change > self.param['threhold'] and STATE == "IDLE": 1084 | exchange.Buy(ticker.Sell * 1.0001, 1 * self.param['p']) 1085 | STATE = "LONG" 1086 | price = ticker.Sell * 1.0001 1087 | 1088 | if expected_change < -1 * self.param['threhold'] and STATE == "IDLE": 1089 | '' 1090 | exchange.Sell(ticker.Buy * 0.9999, 1 * self.param['p']) 1091 | STATE = "SHORT" 1092 | price = ticker.Buy * 0.9999 1093 | 1094 | self.predict.append(expected_change) 1095 | COMPARE = True 1096 | O = O[1::] 1097 | # O = [] 1098 | last_check_ticker = ticker 1099 | if ticker.Time / 1000 - dts >= 86250: 1100 | Log("进入一天末尾准备平仓") 1101 | for item in self.stop["LONG"]: 1102 | exchange.Sell(ticker.Buy * 0.9999, 1 * item[1]) 1103 | self.stop["LONG"].remove(item) 1104 | for item in self.stop["SHORT"]: 1105 | exchange.Buy(ticker.Sell * 1.0001, 1 * item[1]) 1106 | self.stop["SHORT"].remove(item) 1107 | STATE = 'IDLE' 1108 | Log(exchange.GetAccount()) 1109 | print(self.profits) 1110 | print(sum(self.profits)) 1111 | lastticker = ticker 1112 | 1113 | def RSI(self): 1114 | LastDay = "" 1115 | History = exchange.GetRecords(PERIOD_M1) 1116 | lastticker = History[-2] 1117 | self.real = [] 1118 | self.metrics = [] 1119 | self.stop = { 1120 | "LONG": [], 1121 | "SHORT": [], 1122 | } 1123 | self.param['metrics'] = 'RSI' 1124 | while True: 1125 | ticker = exchange.GetTicker() 1126 | NowDay = get_day(ticker.Time) 1127 | if NowDay != LastDay: 1128 | #Log("进入新的一天") 1129 | dts = ticker.Time / 1000 1130 | LastDay = NowDay 1131 | if ticker.Time / 1000 - dts >= 36000 and ticker.Time / 1000 - dts < 57600: 1132 | "十点至下午四点的数据观察指标与真实变动的情况" 1133 | change = ticker.Last 1134 | self.real.append(change) 1135 | records = exchange.GetRecords(PERIOD_M1)[:-2][::-1]; 1136 | rsi = TA.RSI(records) 1137 | self.metrics.append(rsi[-1]) 1138 | lastticker = ticker 1139 | def OBV(self): 1140 | LastDay = "" 1141 | History = exchange.GetRecords(PERIOD_M1) 1142 | lastticker = History[-2] 1143 | self.real = [] 1144 | self.metrics = [] 1145 | self.stop = { 1146 | "LONG": [], 1147 | "SHORT": [], 1148 | } 1149 | self.param['metrics'] = 'OBV' 1150 | while True: 1151 | ticker = exchange.GetTicker() 1152 | NowDay = get_day(ticker.Time) 1153 | if NowDay != LastDay: 1154 | # Log("进入新的一天") 1155 | dts = ticker.Time / 1000 1156 | LastDay = NowDay 1157 | if ticker.Time / 1000 - dts >= 36000 and ticker.Time / 1000 - dts < 57600: 1158 | "十点至下午四点的数据观察指标与真实变动的情况" 1159 | change = ticker.Last 1160 | self.real.append(change) 1161 | records = exchange.GetRecords(PERIOD_M1)[:-2][::-1]; 1162 | OBV = TA.OBV(records) 1163 | self.metrics.append(OBV[-1]) 1164 | lastticker = ticker 1165 | def MACD(self): 1166 | LastDay = "" 1167 | History = exchange.GetRecords(PERIOD_M1) 1168 | self.real = [] 1169 | self.metrics = [] 1170 | self.stop = { 1171 | "LONG": [], 1172 | "SHORT": [], 1173 | } 1174 | self.param['metrics'] = 'MACD' 1175 | while True: 1176 | ticker = exchange.GetTicker() 1177 | NowDay = get_day(ticker.Time) 1178 | if NowDay != LastDay: 1179 | # Log("进入新的一天") 1180 | dts = ticker.Time / 1000 1181 | LastDay = NowDay 1182 | if ticker.Time / 1000 - dts >= 36000 and ticker.Time / 1000 - dts < 57600: 1183 | "十点至下午四点的数据观察指标与真实变动的情况" 1184 | change = ticker.Last-lastticker.Last 1185 | #change = ticker.Last 1186 | self.real.append(change) 1187 | records = exchange.GetRecords(PERIOD_M1)[:-2][::-1]; 1188 | MACD = TA.MACD(records, 40, 80, 20) 1189 | self.metrics.append([MACD[0][-1],MACD[1][-1],MACD[2][-1]]) 1190 | lastticker = ticker 1191 | def GET_SAR(self,history:list,period:int = 4): 1192 | '''传入一个历史K线数据,计算对应的SAR序列''' 1193 | SAR = np.zeros_like(history) 1194 | states = np.zeros_like(history) 1195 | SAR[:period - 1] = None 1196 | #首先确定第一天是涨势还是跌势 1197 | 1198 | for i in range(period - 1,len(history) - 1): 1199 | if i == period - 1 or states[i - 1] == 'reserve': 1200 | #设置跳转后进入新阶段的SAR值 1201 | if (history[i].Close > history[period - 1].Open and i == period - 1) or (states[i] == 'long' and states[i-1] == 'reserve'): 1202 | states[i] = 'long' 1203 | SAR[i] = min(map(lambda x: x.Low, history[:period]))#该周期内的最小值 1204 | EP = max(map(lambda x: x.High, history[:period]))#该周期类的最大值 1205 | AF = 0.02 1206 | if (history[i].Close <= history[period - 1].Open and i == period - 1) or (states[i] == 'short' and states[i-1] == 'reserve'): 1207 | states[i] = 'short' 1208 | SAR[i] = max(map(lambda x: x.High, history[:period]))#该周期内的最大值 1209 | EP = min(map(lambda x: x.Low, history[:period]))#该周期类的最小值 1210 | AF = 0.02 1211 | else: 1212 | SAR[i] = SAR[i - 1] + AF*(EP - SAR[i - 1]) 1213 | if states[i -1] == 'long': 1214 | if SAR[i] > history[i].Low: 1215 | states[i] = 'reserve' 1216 | states[i+1] = 'short' 1217 | else: 1218 | states[i] = 'long' 1219 | EP = max(map(lambda x: x.High, history[i+1-period:i+1]))#该周期类的最大值 1220 | if history[i].High > max(map(lambda x: x.High, history[i-period:i])): 1221 | #如果该时间段的最低价L(t+1),比前面N个时间段(即,t-N+1,……,t)的最低价低 1222 | AF = min(AF + 0.02,0.2) 1223 | if states[i - 1] == 'short': 1224 | if SAR[i] < history[i].High: 1225 | states[i] = 'reserve' 1226 | states[i+1] = 'long' 1227 | else: 1228 | states[i] = 'short' 1229 | EP = min(map(lambda x: x.Low, history[i+1-period:i+1]))#该周期类的最小值 1230 | if history[i].Low < min(map(lambda x: x.Low, history[i-period:i])): 1231 | #如果该时间段的最低价L(t+1),比前面N个时间段(即,t-N+1,……,t)的最低价低 1232 | AF = min(AF + 0.02,0.2) 1233 | return (SAR,states) 1234 | def draw_SAR(self): 1235 | LastDay = "" 1236 | History = exchange.GetRecords(PERIOD_M1) ##股票数据,格式是往列表里添加元组, 每个元组代表一个股票信息。其中元组的格式是(日期,开盘价,最高价,最低价,收盘价) 1237 | datalist = list(map(lambda x:[float(x.Time/1000),x.Open,x.High,x.Low,x.Close],History[:-1])) 1238 | last = list(map(lambda x:x.Open,History[:-1])) 1239 | self.GET_SAR(History,self.period) 1240 | thisdir = self.dir +"SAR" + "/" 1241 | try: 1242 | os.mkdir(self.dir) 1243 | except: 1244 | pass 1245 | fig = plt.figure(figsize=(15,8)) 1246 | #datalist= [(date2num(parse(str(20181110))),10,20,5,15)] 1247 | ax = plt.gca() 1248 | #ax.xaxis.set_ticks_position('bottom') 1249 | #ax.yaxis.set_ticks_position('left') # 指定下边的边作为 x 轴 指定左边的边为 y 轴 1250 | #ax.spines['bottom'].set_position(('data', 0)) # 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上 1251 | #ax.spines['left'].set_position(('data', 0)) 1252 | #mpf.candlestick_ohlc(ax, datalist, width=11, colorup='r', colordown='b', alpha=1) ##设置利用mpf画股票K线图 1253 | ax.set_ylabel("k线") 1254 | ax.set_xlabel("样本") 1255 | (SAR,states) = self.GET_SAR(History,self.period) 1256 | ax.plot(last,linewidth = 1,color = 'red',label = '分钟k线价格') 1257 | for i in range(self.period - 1, len(last)): 1258 | color = {"long": 'orange', "short": "green", "reserve": 'black'}[states[i]] 1259 | marker = 'o' if states[i] != 'reserve' else 'x' 1260 | size = 50 if states[i] == 'reserve' else 9 1261 | ax.scatter(i, SAR[i], s=size, color=color, marker=marker) 1262 | plt.title(self.title) 1263 | plt.legend(loc = 'upper right') 1264 | plt.savefig(thisdir + "8月10日前{}条1分钟k线数据_".format(len(last))+str(self.title) + ".png") 1265 | def main(self): 1266 | self.algorithm2() 1267 | a = HMM() 1268 | a.testall() 1269 | 1270 | ''' 1271 | for period in [3,4,5,10,15,20,30,40,50]: 1272 | a.period = period 1273 | a.title = 'SAR周期'+str(a.period) + '_1分钟k线价格与1分钟K线所求SAR值_黑_跳转_紫_涨势_绿_跌势' 1274 | a.backtest('2019-07-08','2019-07-09') 1275 | ''' 1276 | ''' 1277 | a.param['metrics'] = 'OBV' 1278 | a.param['title'] = '30日成交价与' + a.param['metrics'] + '值曲线' 1279 | a.testmetric() 1280 | ''' 1281 | 1282 | ''' 1283 | for period in [5]: 1284 | a.param['metrics'] = 'RSI' 1285 | a.param['metrics_period'] = period 1286 | a.param['title'] = '五日成交价值与'+a.param['metrics']+ '值曲线' 1287 | a.testmetric() 1288 | ''' --------------------------------------------------------------------------------