def finish(self): order_map = self.portfolio.get_portfolio(PortfolioManager.STATE_ALL) for sid, order_info in order_map.items(): print format_log("order_info", order_info) records = self.portfolio.get_trade_records(sid) for record in records: print format_log("trade_record", record)
def finish(self): order_map = self.portfolio.get_portfolio(PortfolioManager.STATE_ALL) for sid, order_info in order_map.items(): print format_log("order_info", order_info) records = self.portfolio.get_trade_records(sid) for record in records: print format_log("trade_record", record)
def refresh_rise_factor(redis_config, cur_day, past_datamap, riseset_key): redis_conn = redis.StrictRedis(redis_config['host'], redis_config['port']) rise_set = redis_conn.smembers(riseset_key) #print rise_set rf_zset_key = "risefactor-" + str(cur_day) for sid in rise_set: if sid not in past_datamap: continue past_data_value = past_datamap[sid] past_data = json.loads(past_data_value) daily_data_value = redis_conn.get("daily-" + str(sid) + "-" + str(cur_day)) #print daily_data_value if daily_data_value is None: continue stock_daily_data = json.loads(daily_data_value) if stock_daily_data['close_price'] < stock_daily_data['open_price'] or stock_daily_data['high_price'] == stock_daily_data['open_price']: redis_conn.zrem(rf_zset_key, sid) continue vary_portion = (stock_daily_data['close_price'] - stock_daily_data['open_price']) / stock_daily_data['open_price'] * 100 volume_ratio = stock_daily_data['predict_volume'] / past_data['avg_volume'] high_portion = (stock_daily_data['close_price'] - stock_daily_data['open_price']) / (stock_daily_data['high_price'] - stock_daily_data['open_price']); rise_factor = round(vary_portion * volume_ratio * high_portion, 1) print format_log("refresh_rise_factor", {'sid': sid, 'vary_portion': vary_portion, 'volume_ratio': volume_ratio, 'high_portion': high_portion, 'rise_factor': rise_factor}) if rise_factor >= 3.0: redis_conn.zadd(rf_zset_key, rise_factor, sid)
def get_stock_realtime(stock_info): sid = stock_info[0] scode = stock_info[1] #url = "http://web.ifzq.gtimg.cn/appstock/app/UsMinute/query?_var=min_data_usWUBA&code=usWUBA.N&r=" + str(random.random()) url = "http://data.gtimg.cn/flashdata/hushen/minute/" + scode + ".js?maxage=10&" + str(random.random()) #print scode, url try: response = urllib2.urlopen(url, timeout=1) content = response.read() except Exception as e: print "err=get_stock_realtime sid=" + str(sid) + " exception=" + str(e.reason) return None content = content.strip(' ;"\n').replace("\\n\\", "") lines = content.split("\n") #print lines date_info = lines[1].split(":") day = int("20" + date_info[1]) hq_time = list() hq_price = list() hq_volume = list() for line in lines[2:]: fields = line.split(" ") # 直接用小时+分组成的时间, 格式为HHMM time = int(fields[0]) item = dict() item['sid'] = sid item['code'] = scode[2:] item['day'] = day item['time'] = time item['price'] = float(fields[1]) item['volume'] = int(fields[2]) if item['volume'] <= 0: continue hq_time.append(time) hq_price.append(item['price']) hq_volume.append(item['volume']) # 表示当天所有的成交量都为0, 当天停牌 if len(hq_volume) == 0: return hq_dict = {'time': hq_time, 'price': hq_price, 'volume': hq_volume} #print hq_dict if 'REDIS' in config_info : key = "realtime-" + str(sid) + "-" + str(day) conn = redis.StrictRedis(config_info['REDIS']['host'], int(config_info['REDIS']['port'])) conn.set(key, json.dumps(hq_dict), 86400) print format_log("fetch_realtime", {'sid': sid, 'scode': scode, 'time': hq_time[len(hq_time) - 1], 'price': hq_price[len(hq_price) - 1]}) return hq_dict
def fill_order(self, fill_event): sid = self.code2sid(fill_event['code']) if sid == 0: self.logger.error("err=invalid_code code=%s", fill_event['code']) return 0 order_info = self.order_stock[sid] if order_info['state'] == PortfolioManager.STATE_CLOSED: self.logger.info("%s", format_log("ignore_closed_order", order_info)) return 0 # TODO: 暂时不支持追加买入/卖出订单, 或者做多平仓后做空 if order_info[ 'state'] == PortfolioManager.STATE_WAIT_OPEN and order_info[ 'op'] == fill_event['op']: order_info['state'] = PortfolioManager.STATE_OPENED order_info['quantity'] = fill_event['quantity'] order_info['open_price'] = fill_event['price'] order_info['open_cost'] = fill_event['cost'] elif order_info[ 'state'] == PortfolioManager.STATE_WAIT_CLOSE and order_info[ 'op'] != fill_event['op']: order_info[ 'quantity'] = order_info['quantity'] - fill_event['quantity'] order_info['close_price'] = fill_event['price'] order_info['close_cost'] = fill_event['cost'] if order_info['quantity'] <= 0: order_info['state'] = PortfolioManager.STATE_CLOSED order_info['profit'] = order_info['close_cost'] - order_info[ 'open_cost'] order_info['profit_portion'] = ( order_info['close_cost'] - order_info['open_cost']) / order_info['open_cost'] * 100 self.logger.info("%s", format_log("order_profit", order_info)) trade_info = { 'code': fill_event['code'], 'op': fill_event['op'], 'quantity': fill_event['quantity'], 'order_id': fill_event['order_id'], 'price': fill_event['price'], 'cost': fill_event['cost'] } trade_info['sid'] = sid trade_info['order_time'] = fill_event['time'] if sid not in self.traded_map: self.traded_map[sid] = list() self.traded_map[sid].append(trade_info) self.update_stat("FILL", fill_event) self.logger.info("%s", format_log("fill_order", trade_info)) self.update_holdings_from_fill(fill_event) return sid
def close(self, sid, close_item): opened_map = self.get_portfolio(PortfolioManager.STATE_OPENED) if sid not in opened_map: self.logger.info("op=stock_not_open sid=%d", sid) return False elif close_item['op'] == opened_map[sid]['op']: self.logger.info( "op=close_same_op sid=%d code=%s open_op=%d count=%d close_op=%d ", sid, opened_map[sid]['code'], opened_map[sid]['op'], opened_map[sid]['count'], close_item['op']) return False elif not self.check_allow("CLOSE", close_item): self.logger.info("%s %s", format_log("close_not_allowed", close_item), json.dumps(self.port_statinfo)) return False # TODO: 推送下单消息, 默认为市价卖出, 设置close_price时极为触及市价卖出, 后续支持order_type指定订单类型(市价/限价) order_event = { 'sid': sid, 'order_type': 'MKT', 'day': close_item['day'], 'code': close_item['code'], 'op': close_item['op'], 'quantity': opened_map[sid]['quantity'] } if 'price' in close_item: order_event['price'] = close_item['price'] # TODO: 设置订单类型为触及市价 self.redis_conn.rpush("order-queue", json.dumps(order_event)) # 更新订单状态 opened_map[sid]['state'] = PortfolioManager.STATE_WAIT_CLOSE order_event['time'] = close_item['time'] # 更新持仓管理的统计 self.update_stat("CLOSE", close_item) self.logger.info("%s", format_log("close_order", order_event)) # TODO: 目前手动构造成交订单 sign = 1 if close_item['op'] == MinuteTrend.OP_SHORT else -1 close_cost = sign * order_event['price'] * order_event['quantity'] fill_event = { 'code': order_event['code'], 'op': order_event['op'], 'quantity': opened_map[sid]['quantity'], 'price': order_event['price'], 'cost': close_cost, 'time': close_item['time'] } fill_event['order_id'] = random.randint(100, 500) self.fill_order(fill_event) return True
def rise_factor(self, item): sid = item['sid'] sid_str = str(sid) if sid_str not in self.datamap['past_data']: self.logger.warning("op=non_exist_pastdata sid=%d day=%d", sid, item['day']) return past_data_value = self.datamap['past_data'][sid_str] if past_data_value is None: return past_data = json.loads(past_data_value) open_vary_portion = (item['open_price'] - item['last_close_price'] ) / item['last_close_price'] * 100 day_vary_portion = (item['close_price'] - item['open_price']) / item['open_price'] * 100 volume_ratio = item['predict_volume'] / past_data['avg_volume'] if abs(item['open_price'] - item['high_price']) < 0.01: high_portion = item['close_price'] / item['high_price'] else: high_portion = (item['close_price'] - item['open_price']) / ( item['high_price'] - item['open_price']) rise_factor = round(day_vary_portion * volume_ratio * high_portion, 1) daily_policy_key = "daily-policy-" + str(sid) + "-" + str(item['day']) stock_rise_map = { 'open_vary_portion': open_vary_portion, 'day_vary_portion': day_vary_portion, 'volume_ratio': volume_ratio, 'high_portion': high_portion, 'rise_factor': rise_factor } self.redis_conn.hmset(daily_policy_key, stock_rise_map) stock_rise_map['sid'] = sid stock_rise_map['day'] = item['day'] self.logger.info(format_log("daily_rise_factor", stock_rise_map)) # 当前价格比昨日收盘价上涨1%以上 且 高于开盘价 rf_zset_key = "rf-" + str(item['day']) if item['vary_portion'] >= 1.00 and item['close_price'] > item[ 'open_price'] and rise_factor >= 3.0: self.redis_conn.zadd(rf_zset_key, rise_factor, sid) self.logger.info(format_log("add_rise_factor", stock_rise_map)) else: self.redis_conn.zrem(rf_zset_key, sid) # 把涨幅超过1% 或 涨跌幅在1%以内的股票加入拉取分笔交易的集合 daily_ts_key = "tsset-" + str(item['day']) if volume_ratio >= 2.0 and (item['vary_portion'] > 1.00 or abs(item['vary_portion']) <= 1.00): self.redis_conn.sadd(daily_ts_key, item['sid']) else: self.redis_conn.srem(daily_ts_key, [item['sid']])
def parse_stock_daily(self, line): parts = line.split("=") #print line, parts content = parts[1].strip('"') #print content fields = content.split("~") #print fields if len(fields) < 44: line_str = safestr(line) self.logger.error( format_log("daily_lack_fields", { 'line': line_str, 'content': content })) return None # 当日停牌则不能存入 open_price = float(fields[5]) close_price = float(fields[3]) if open_price == 0.0 or close_price == 0.0: return None item = dict() try: item['name'] = safestr(fields[1]) item['code'] = stock_code = fields[2] item['sid'] = int(self.datamap['code2id'][stock_code]) item['day'] = self.day item['last_close_price'] = float(fields[4]) item['open_price'] = open_price item['high_price'] = float(fields[33]) item['low_price'] = float(fields[34]) item['close_price'] = close_price # 当前时刻, 格式为HHMMSS item['time'] = fields[30][8:] item['vary_price'] = float(fields[31]) item['vary_portion'] = float(fields[32]) # 成交量转化为手 item['volume'] = int(fields[36]) item['predict_volume'] = get_predict_volume( item['volume'], item['time']) # 成交额转化为万元 item['amount'] = int(fields[37]) item['exchange_portion'] = fields[38] item['pe'] = fields[39] item['swing'] = fields[43] item['out_capital'] = fields[44] except IndexError: self.logger.error(format_log("parse_daily", {'content': content})) return None return item
def open(self, sid, open_item): wait_open_map = self.get_portfolio(PortfolioManager.STATE_WAIT_OPEN) if sid in wait_open_map: self.logger.info("op=stock_wait_open sid=%d code=%s", sid, wait_open_map[sid]['code']) return False # 暂时不允许已建仓的股票再建仓 opened_map = self.get_portfolio(PortfolioManager.STATE_OPENED) if sid in opened_map and open_item['op'] == opened_map[sid]['op']: self.logger.info("%s", format_log("stock_order_opened", opened_map[sid])) return False # 建仓是否允许 if not self.check_allow("OPEN", open_item): self.logger.info("%s %s", format_log("open_not_allowed", open_item), json.dumps(self.port_statinfo)) return False # TODO: cash仅在成交后才会扣减, 实际通过IB下单时, 会出现成交前多只股票都可以下单 rest_money = self.holdings['cash'] quantity = self.cal_quantity(sid, open_item['op'], rest_money, open_item['open_price']) if quantity <= 0: self.logger.info("op=no_enough_money sid=%d code=%s op=%d rest_money=%d open_price=%.2f", sid, open_item['code'], open_item['op'], rest_money, open_item['open_price']) return False # TODO: 推送下单消息, 设置建仓价格 + 止损价格, 设置下单类型为限价单 order_info = {'sid': sid, 'day': open_item['day'], 'code': open_item['code'], 'op': open_item['op'], 'quantity': quantity, 'stop_price': open_item['stop_price']} order_event = dict(order_info) order_event['order_type'] = "LMT" order_event['price'] = open_item['open_price'] push_result = self.redis_conn.rpush("order-queue", json.dumps(order_event)) # 更新剩余的现金 sign = 1 if open_item['op'] == MinuteTrend.OP_LONG else -1 cost = sign * quantity * open_item['open_price'] order_event['time'] = open_item['time'] self.logger.info("%s cost=%d", format_log("open_order", order_event), cost) order_info['open_price'] = open_item['open_price'] order_info['state'] = self.STATE_WAIT_OPEN self.order_stock[sid] = order_info # 更新持仓管理的统计 self.update_stat("OPEN", open_item) # TODO: 目前先手动构造fill_event来成交 fill_event = {'code': order_event['code'], 'op': order_event['op'], 'quantity': quantity, 'price': order_event['price'], 'cost': cost, 'time': open_item['time']} fill_event['order_id'] = random.randint(100, 500) self.fill_order(fill_event) return True
def core(config_info, queue, location, day): redis_config = config_info['REDIS'] conn = redis.StrictRedis(redis_config['host'], redis_config['port']) count = 0 return_list = [] while True: try: pop_data = conn.blpop(queue, 1) if pop_data is None: time.sleep(1) break data = pop_data[1] item = json.loads(data) #print item if item is None: continue if 'daily_item' in item and item['daily_item']['day'] != day: logging.getLogger("chance").error( "err=ignore_expired_item item_day=%d day=%d", item['daily_item']['day'], day) break return_info = chance_return(config_info, location, day, item) #print return_info return_list.append(return_info) logging.getLogger("chance").info( "%s", format_log("chance_return", return_info)) ''' count += 1 if count % 5 == 0: break ''' except Exception as e: logging.getLogger("chance").exception("err=pop_item") return_list.sort(key=lambda x: (x['close_portion'], x['fall_portion']), reverse=True) for return_item in return_list[0:50]: logging.getLogger("chance").info("%s", format_log("top_return", return_item)) return_pd = pd.DataFrame(return_list) #print return_pd filename = "./return_" + str(day) + "_" + str(location) + ".csv" return_pd.to_csv(filename, index=False)
def core(self, item): scode = item url = "http://qt.gtimg.cn/r=" + str(random.random()) + "q=" + scode #print url try: response = urllib2.urlopen(url, timeout=1) content = response.read() except urllib2.HTTPError as e: self.logger.warning("err=get_stock_daily scode=%s code=%s", scode, str(e.code)) return except urllib2.URLError as e: self.logger.warning("err=get_stock_daily scode=%s reason=%s", scode, str(e.reason)) return if content: content = safestr(content.decode('gbk')) #self.logger.info("desc=daily_content content=%s", content) lines = content.strip("\r\n").split(";") for line in lines: if 0 == len(line): continue daily_item = self.parse_stock_daily(line) if daily_item is None: continue # 追加到redis队列中 if self.conn: self.conn.rpush("daily-queue", json.dumps(daily_item)) self.logger.info(format_log("fetch_daily", daily_item))
def parse_stock_daily(self, line): try: parts = line.split("=") #print line, parts content = parts[1].strip('"') #print content fields = content.split("~") #print fields if len(fields) < 44: line_str = safestr(line) self.logger.error(format_log("daily_lack_fields", {'line': line_str, 'content': content})) return None # 当日停牌则不能存入 open_price = float(fields[5]) close_price = float(fields[3]) if open_price == 0.0 or close_price == 0.0: return None item = dict() try: item['name'] = safestr(fields[1]) ''' stock_code = fields[2] if self.location == 3: # 美股返回为usWUBA.N code_parts = stock_code.split(".") stock_code = code_parts[0] ''' item['code'] = fields[2] item['sid'] = int(self.datamap['code2id'][item['code']]) item['day'] = self.day item['last_close_price'] = float(fields[4]) item['open_price'] = open_price item['high_price'] = float(fields[33]) item['low_price'] = float(fields[34]) item['close_price'] = close_price # 当前时刻, 格式为HHMMSS item['time'] = fields[30][8:] item['vary_price'] = float(fields[31]) item['vary_portion'] = float(fields[32]) # 成交量转化为手 item['volume'] = int(fields[36]) item['predict_volume'] = get_predict_volume(item['volume'], item['time'], self.location) # 成交额转化为万元 item['amount'] = int(fields[37]) item['exchange_portion'] = fields[38] item['pe'] = fields[39] item['swing'] = fields[43] item['out_capital'] = fields[44] except Exception: self.logger.exception("err=parse_daily_index content=%s", content) return None except Exception: self.logger.exception("err=parse_daily_content line=%s content=%s", line, content) return None return item
def refresh_rise_factor(redis_config, cur_day, past_datamap, riseset_key): redis_conn = redis.StrictRedis(redis_config['host'], redis_config['port']) rise_set = redis_conn.smembers(riseset_key) #print rise_set rf_zset_key = "risefactor-" + str(cur_day) for sid in rise_set: if sid not in past_datamap: continue past_data_value = past_datamap[sid] past_data = json.loads(past_data_value) daily_data_value = redis_conn.get("daily-" + str(sid) + "-" + str(cur_day)) #print daily_data_value if daily_data_value is None: continue stock_daily_data = json.loads(daily_data_value) if stock_daily_data['close_price'] < stock_daily_data[ 'open_price'] or stock_daily_data[ 'high_price'] == stock_daily_data['open_price']: redis_conn.zrem(rf_zset_key, sid) continue vary_portion = (stock_daily_data['close_price'] - stock_daily_data['open_price'] ) / stock_daily_data['open_price'] * 100 volume_ratio = stock_daily_data['predict_volume'] / past_data[ 'avg_volume'] high_portion = ( stock_daily_data['close_price'] - stock_daily_data['open_price'] ) / (stock_daily_data['high_price'] - stock_daily_data['open_price']) rise_factor = round(vary_portion * volume_ratio * high_portion, 1) print format_log( "refresh_rise_factor", { 'sid': sid, 'vary_portion': vary_portion, 'volume_ratio': volume_ratio, 'high_portion': high_portion, 'rise_factor': rise_factor }) if rise_factor >= 3.0: redis_conn.zadd(rf_zset_key, rise_factor, sid)
def core(config_info, queue, location, day): redis_config = config_info['REDIS'] conn = redis.StrictRedis(redis_config['host'], redis_config['port']) count = 0 return_list = [] while True: try: pop_data = conn.blpop(queue, 1) if pop_data is None: time.sleep(1) break data = pop_data[1] item = json.loads(data) #print item if item is None: continue if 'daily_item' in item and item['daily_item']['day'] != day: logging.getLogger("chance").error("err=ignore_expired_item item_day=%d day=%d", item['daily_item']['day'], day) break return_info = chance_return(config_info, location, day, item) #print return_info return_list.append(return_info) logging.getLogger("chance").info("%s", format_log("chance_return", return_info)) ''' count += 1 if count % 5 == 0: break ''' except Exception as e: logging.getLogger("chance").exception("err=pop_item") return_list.sort(key=lambda x : (x['close_portion'], x['fall_portion']), reverse = True) for return_item in return_list[0:50]: logging.getLogger("chance").info("%s", format_log("top_return", return_item)) return_pd = pd.DataFrame(return_list) #print return_pd filename = "./return_" + str(day) + "_" + str(location) + ".csv" return_pd.to_csv(filename, index=False)
def core(self, location, day, param_sid): stock_df = pd.read_sql_query( "select * from t_stock where location = " + str(location) + " and type = 1 and status = 'Y'", self.conn, index_col='id') #print stock_df.index, stock_df.columns sid_list = [param_sid] if param_sid > 0 else stock_df.index for sid in sid_list: dyn_info = self.update_dyn(day, sid) if dyn_info is None: print format_log("stock_paused", { 'sid': sid, 'location': location, 'day': day }) continue sql = SqlUtil.create_insert_sql("t_stock_dyn", dyn_info) try: db_conn = SqlUtil.get_db(self.db_config) db_conn.query_sql(sql, True) except Exception as e: print "err=insert_dyn sid=" + str(sid) + " day=" + str( day) + " ex=" + str(e) continue print format_log("add_stock_dyn", dyn_info) print format_log("finish_dyn", {'location': location, 'day': day})
def fill_order(self, fill_event): sid = self.code2sid(fill_event['code']) if sid == 0: self.logger.error("err=invalid_code code=%s", fill_event['code']) return 0 order_info = self.order_stock[sid] if order_info['state'] == PortfolioManager.STATE_CLOSED: self.logger.info("%s", format_log("ignore_closed_order", order_info)) return 0 # TODO: 暂时不支持追加买入/卖出订单, 或者做多平仓后做空 if order_info['state'] == PortfolioManager.STATE_WAIT_OPEN and order_info['op'] == fill_event['op']: order_info['state'] = PortfolioManager.STATE_OPENED order_info['quantity'] = fill_event['quantity'] order_info['open_price'] = fill_event['price'] order_info['open_cost'] = fill_event['cost'] elif order_info['state'] == PortfolioManager.STATE_WAIT_CLOSE and order_info['op'] != fill_event['op']: order_info['quantity'] = order_info['quantity'] - fill_event['quantity'] order_info['close_price'] = fill_event['price'] order_info['close_cost'] = fill_event['cost'] if order_info['quantity'] <= 0: order_info['state'] = PortfolioManager.STATE_CLOSED order_info['profit'] = order_info['close_cost'] - order_info['open_cost'] order_info['profit_portion'] = (order_info['close_cost'] - order_info['open_cost']) / order_info['open_cost'] * 100 self.logger.info("%s", format_log("order_profit", order_info)) trade_info = {'code': fill_event['code'], 'op': fill_event['op'], 'quantity': fill_event['quantity'], 'order_id': fill_event['order_id'], 'price': fill_event['price'], 'cost': fill_event['cost']} trade_info['sid'] = sid trade_info['order_time'] = fill_event['time'] if sid not in self.traded_map: self.traded_map[sid] = list() self.traded_map[sid].append(trade_info) self.update_stat("FILL", fill_event) self.logger.info("%s", format_log("fill_order", trade_info)) self.update_holdings_from_fill(fill_event) return sid
def get_stock_daily(stock_info): #scode = stock_info[1] scode = stock_info url = "http://qt.gtimg.cn/r=" + str(random.random()) + "q=" + scode #print scode, url try: response = requests.get(url, timeout=10) content = response.text except Exception as e: print "err=get_stock_daily scode=" + scode + " error_msg=" + str(e) return if content: lines = content.strip("\n").split(";") conn = None if 'REDIS' in config_info : conn = redis.StrictRedis(config_info['REDIS']['host'], int(config_info['REDIS']['port'])) for line in lines: if 0 == len(line): continue item = parse_stock_daily(line) if item is None: continue # 存到缓存中 if conn: key = "daily-" + str(item['sid']) + "-" + str(day) conn.set(key, json.dumps(item), 86400) # 当日开盘上涨 且 当前价格高于昨日收盘价格 if item['vary_price'] > 0.0 and item['close_price'] > item['open_price']: conn.sadd("daily-riseset-" + str(day), item['sid']) print format_log("fetch_daily", item)
def realtime_trend(self, item): sid = int(item['sid']) day = item['day'] daily_key = "daily-" + str(sid) + "-" + str(day) daily_cache_value = self.redis_conn.get(daily_key); if daily_cache_value is None: self.logger.error("err=fetch_daily sid=%d day=%d", sid, day) return daily_item = json.loads(daily_cache_value) rt_key = "rt-" + str(sid) + "-" + str(day) item_count = self.redis_conn.llen(rt_key) if item_count < 3: return if item_count % 5 == 0: # 暂定每5分钟调用分析一次, 后续根据时间段调整 item_list = self.redis_conn.lrange(rt_key, 0, -1) minute_items = [] for item_json in item_list: minute_items.append(json.loads(item_json)) now_time = minute_items[-1]['time'] instance = MinuteTrend(sid) (trend_stage, trend_info) = instance.core(daily_item, minute_items) self.logger.debug("%s", format_log("minute_trend", trend_stage)) self.logger.debug("%s", format_log("trend_parse", trend_info)) trend_detail = self.refresh_trend(sid, day, minute_items, trend_info) if 'trend' in trend_detail: self.logger.info("%s sid=%d day=%d item_count=%d time=%d", format_log("trend_detail", trend_detail), sid, day, item_count, now_time) if trend_stage['chance'] and trend_stage['chance']['op'] != MinuteTrend.OP_WAIT: trend_stage['trend_detail'] = trend_detail self.redis_conn.rpush("chance-queue", json.dumps(trend_stage)) self.logger.info("%s", format_log("realtime_chance", trend_stage))
def open_position(self, location, day, cur_timenumber, sid, item): # 获取该股票所有的操作机会 same_count = 1 contray_count = 0 key = "chance-" + str(sid) + "-" + str(day) # 该机会不一定是最新的1个 chance_item_list = self.redis_conn.lrange(key, 0, -1) for chance_item_data in chance_item_list: chance_item = json.loads(chance_item_data) # 忽略自己 if chance_item['time'] == item['time']: continue if chance_item['chance']['op'] == item['chance']['op']: same_count += 1 else: contray_count += 1 # 直接买入 #print same_count, contray_count if contray_count == 0 or same_count > contray_count: order_event = {'sid': sid, 'day': day, 'code': item['code'], 'time': item['time']} stop_price = item['chance']['stop_price'] if item['chance']['op'] == MinuteTrend.OP_LONG: open_price = item['chance']['price_range'][1] stop_price = min(stop_price, open_price * (1 - self.chance_config[location]['stop_portion'] / 100)) else: open_price = item['chance']['price_range'][0] stop_price = max(stop_price, open_price * (1 + self.chance_config[location]['stop_portion'] / 100)) # <=1000时建仓, 取区间的中间价格作为建仓价 if item['time'] <= 1000: open_price = (item['chance']['price_range'][0] + item['chance']['price_range'][1]) / 2 order_event['open_price'] = open_price order_event['stop_price'] = stop_price order_event['op'] = item['chance']['op'] # 调用PortfioManager进行建仓 open_result = self.portfolio.open(sid, order_event) self.logger.info("%s open_result=%s", format_log("open_position", order_event), str(open_result)) # 建仓的机会push到单独的队列中 self.redis_conn.rpush("order-chance", json.dumps(item)) return True return False
def close(self, sid, close_item): opened_map = self.get_portfolio(PortfolioManager.STATE_OPENED) if sid not in opened_map: self.logger.info("op=stock_not_open sid=%d", sid) return False elif close_item['op'] == opened_map[sid]['op']: self.logger.info("op=close_same_op sid=%d code=%s open_op=%d count=%d close_op=%d ", sid, opened_map[sid]['code'], opened_map[sid]['op'], opened_map[sid]['count'], close_item['op']) return False elif not self.check_allow("CLOSE", close_item): self.logger.info("%s %s", format_log("close_not_allowed", close_item), json.dumps(self.port_statinfo)) return False # TODO: 推送下单消息, 默认为市价卖出, 设置close_price时极为触及市价卖出, 后续支持order_type指定订单类型(市价/限价) order_event = {'sid': sid, 'order_type': 'MKT', 'day': close_item['day'], 'code': close_item['code'], 'op': close_item['op'], 'quantity': opened_map[sid]['quantity']} if 'price' in close_item: order_event['price'] = close_item['price'] # TODO: 设置订单类型为触及市价 self.redis_conn.rpush("order-queue", json.dumps(order_event)) # 更新订单状态 opened_map[sid]['state'] = PortfolioManager.STATE_WAIT_CLOSE order_event['time'] = close_item['time'] # 更新持仓管理的统计 self.update_stat("CLOSE", close_item) self.logger.info("%s", format_log("close_order", order_event)) # TODO: 目前手动构造成交订单 sign = 1 if close_item['op'] == MinuteTrend.OP_SHORT else -1 close_cost = sign * order_event['price'] * order_event['quantity'] fill_event = {'code': order_event['code'], 'op': order_event['op'], 'quantity': opened_map[sid]['quantity'], 'price': order_event['price'], 'cost': close_cost, 'time': close_item['time']} fill_event['order_id'] = random.randint(100, 500) self.fill_order(fill_event) return True
def filter(self, item): sid = item['sid'] day = item['daily_item']['day'] chance_info = item['chance'] daily_item = item['daily_item'] day_vary_portion = (daily_item['close_price'] - daily_item['open_price']) / daily_item['open_price'] * 100 # 日内涨幅 >= 6%且操作时间在10:20之后, 不建议追高 if chance_info['op'] == MinuteTrend.OP_LONG and item['time'] >= 1020 and abs(day_vary_portion) >= 6.00: return # TODO: 从redis中获取大盘趋势 key = "chance-" + str(sid) + "-" + str(day) self.redis_conn.rpush(key, json.dumps(item)) # 全局list, 倒序排列 self.redis_conn.lpush("chance-" + str(day), json.dumps(item)) self.logger.debug("%s", format_log("chance_item", item)) '''
def day_trend(self, item): trend = op = 0 day_vary_portion = (item['close_price'] - item['open_price']) / item['open_price'] * 100 max_vary = item['high_price'] - item['close_price'] min_vary = item['close_price'] - item['low_price'] # 开盘即涨停 if item['close_price'] == item[ 'open_price'] and item['vary_portion'] >= 9.6: trend = 3 # 涨跌幅在1%以内, 认为是震荡 elif abs(day_vary_portion) <= 1: trend = 2 op = 2 elif item['vary_price'] > 0.0: if max_vary == 0.0 or max_vary < min_vary: trend = 3 else: trend = 1 else: if min_vary == 0.0 or min_vary < max_vary: trend = 1 else: trend = 3 if trend == 1: op = 1 elif trend == 3: op = 3 daily_policy_key = "daily-policy-" + str(item['sid']) + "-" + str( item['day']) trend_info = {'trend': trend, 'op': op} self.redis_conn.hmset(daily_policy_key, trend_info) trend_info['sid'] = item['sid'] trend_info['day'] = item['day'] self.logger.debug("%s", format_log("daily_day_trend", trend_info))
def chance_return(config_info, location, day, item): hqdata = get_hqdata(config_info['DB'], config_info['REDIS'], day, item['sid']) print hqdata['daily'] chance_item = item['chance'] trend_item = item['trend_item'] # 卖出时用平仓价格减卖出价格, 需要乘-1 factor = 1 if 1 == chance_item['op'] else -1 # 严格一点, 应该用time之后的high_price/low_price计算收益和回撤, 买入点以较大价格进入, 卖出以较低价格卖出 enter_price = chance_item['price_range'][1] if 1 == chance_item['op'] else chance_item['price_range'][0] exit_price = float(hqdata['daily']['close_price']) fall_price = float(hqdata['daily']['low_price']) if 1 == chance_item['op'] else float(hqdata['daily']['high_price']) rise_portion = factor * (exit_price - enter_price) / enter_price * 100 fall_portion = factor * (fall_price - enter_price) / enter_price * 100 return_info = {"sid": item['sid'], "code": item['code'], "time": item['time'], "op": chance_item['op'], "trend_length": trend_item['length'], "trend_portion": trend_item['vary_portion'], "enter": enter_price, "exit": exit_price, "fall": fall_price, "close_portion": rise_portion, "fall_portion": fall_portion, "vary_portion": hqdata['daily']['vary_portion']} # TODO: 输出 dyn数据 record_list = [] try: sql = "select * from t_stock_dyn where sid = {sid} and day < {day} order by day desc limit 1".format(sid=item['sid'], day=day) #print sql db_conn = SqlUtil.get_db(config_info['DB']) record_list = db_conn.query_sql(sql) except Exception as e: print e logging.getLogger("chance").error("err=get_stock_dyn sid=%d code=%s location=%d day=%d", item['sid'], item['code'], location, day) else: #print record_list if len(record_list) == 1: stock_dyn = record_list[0] for key in ['ma5_swing', 'ma20_swing', 'ma5_vary_portion', 'ma20_vary_portion', 'ma5_exchange_portion', 'ma20_exchange_portion', 'volume_ratio']: return_info[key] = stock_dyn[key] logging.getLogger("chance").info("%s", format_log("chance_item", item)) return return_info
def core(self, item): scode = item cur_timestamp = int(time.time() * 1000) url = "http://hq.sinajs.cn/rn=" + str(cur_timestamp) + "&list=" + scode print url try: response = urllib2.urlopen(url, timeout=5) content = response.read() except urllib2.HTTPError as e: self.logger.warning("err=get_stock_daily scode=%s code=%s", scode, str(e.code)) return except urllib2.URLError as e: self.logger.warning("err=get_stock_daily scode=%s reason=%s", scode, str(e.reason)) return if content: content = safestr(content.decode('gbk')) self.logger.debug("desc=daily_content scode=%s content=%s", scode, content) lines = content.strip("\r\n").split(";") for line in lines: if 0 == len(line): continue daily_item = self.parse_stock_daily(line) if daily_item is None: continue # 追加到redis队列中 json_item = json.dumps(daily_item) if self.conn: self.conn.rpush("daily-queue", json_item) self.logger.info(format_log("fetch_daily", daily_item)) #print format_log("fetch_daily", daily_item) # 设置dump则把数据dump到日志中, 暂定每5mindump一次, 可配置 self.dump(int(daily_item['time'][2:4]), json_item, "daily")
def filter(self, item): sid = item['sid'] day = item['daily_item']['day'] chance_info = item['chance'] daily_item = item['daily_item'] day_vary_portion = ( daily_item['close_price'] - daily_item['open_price']) / daily_item['open_price'] * 100 # 日内涨幅 >= 6%且操作时间在10:20之后, 不建议追高 if chance_info['op'] == MinuteTrend.OP_LONG and item[ 'time'] >= 1020 and abs(day_vary_portion) >= 6.00: return # TODO: 从redis中获取大盘趋势 key = "chance-" + str(sid) + "-" + str(day) self.redis_conn.rpush(key, json.dumps(item)) # 全局list, 倒序排列 self.redis_conn.lpush("chance-" + str(day), json.dumps(item)) self.logger.debug("%s", format_log("chance_item", item)) '''
def core(self, item): scode_list = item url = "http://qt.gtimg.cn/r=" + str(random.random()) + "q=" + scode_list print url try: response = urllib2.urlopen(url, timeout=1) content = response.read() except urllib2.HTTPError as e: self.logger.warning("err=get_stock_daily scode_list=%s code=%s", scode_list, str(e.code)) return except urllib2.URLError as e: self.logger.warning("err=get_stock_daily scode_list=%s reason=%s", scode_list, str(e.reason)) return if content: content = safestr(content.decode('gbk')) #self.logger.info("desc=daily_content content=%s", content) lines = content.strip("\r\n").split(";") for line in lines: if 0 == len(line): continue daily_item = self.parse_stock_daily(line) if daily_item is None: continue # 追加到redis队列中 json_data = json.dumps(daily_item) if self.conn: self.conn.rpush("daily-queue", json_data) self.logger.info(format_log("fetch_daily", daily_item)) # 设置dump则把数据dump到日志中, 暂定每5mindump一次, 可配置 self.dump(int(daily_item['time'][2:4]), json_data, "daily")
def core(self, location, day, param_sid): stock_df = pd.read_sql_query("select * from t_stock where location = " + str(location) + " and type = 1 and status = 'Y'", self.conn, index_col='id') #print stock_df.index, stock_df.columns sid_list = [param_sid] if param_sid > 0 else stock_df.index for sid in sid_list: dyn_info = self.update_dyn(day, sid) if dyn_info is None: print format_log("stock_paused", {'sid': sid, 'location': location, 'day': day}) continue sql = SqlUtil.create_insert_sql("t_stock_dyn", dyn_info) try: db_conn = SqlUtil.get_db(self.db_config) db_conn.query_sql(sql, True) except Exception as e: print "err=insert_dyn sid=" + str(sid) + " day=" + str(day) + " ex=" + str(e) continue print format_log("add_stock_dyn", dyn_info) print format_log("finish_dyn", {'location': location, 'day': day})
def refresh_stock_histdata(redis_config, db_config, stock_list, today_data_list, day, location = 1, refresh = True): db_conn = SqlUtil.get_db(db_config) high_field_list = ["hist_high", "year_high", "month6_high", "month3_high"] low_field_list = ["hist_low", "year_low", "month6_low", "month3_low"] vary_stock_list = dict() for sid, stock_info in stock_list.items(): # 忽略指数 if sid not in today_data_list or int(stock_info['type']) == 2: continue #print stock_info stock_data = today_data_list[sid] #print stock_data high_index = 4 low_index = 4 close_price = float(stock_data['close_price']) out_capital = close_price * float(stock_info['out_capital']); capital_limit = 10 if 3 == location: out_capital = out_capital / 10000 capital_limit = 15 # capital < 10/15(us) if out_capital <= capital_limit: continue for index, field_name in enumerate(high_field_list): if close_price > float(stock_info[field_name]): high_index = index break for index, field_name in enumerate(low_field_list): if close_price < float(stock_info[field_name]): low_index = index break # 表明当天价格存在最高价或者最低价 #print sid, high_index, low_index if high_index < 4 or low_index < 4: vary_stock_list[sid] = {'high_index': high_index, 'low_index': low_index} sql = "update t_stock set " field_list = [] high_type = low_type = 0 # 起始日期定为之前3个交易日, 3个交易日内无同类型突破记录 range_start_day = get_past_openday(day, 3, location) if high_index < 4: high_type = high_index + 1 high_threshold_list = get_stock_price_threshold(db_config, sid, range_start_day, day, high_type, 0) if 0 == len(high_threshold_list): add_result = add_stock_price_threshold(db_config, sid, day, close_price, high_type, low_type) print format_log("add_high_price_threshold", {'sid': sid, 'day': day, 'close_price': close_price, 'high_type': high_type, 'result':add_result}) if add_result and high_type <= 2: # 年内新高/历史最高才加入股票池 pool_result = add_stock_pool(db_config, redis_config, sid, day, 2, {'wave':1}) print format_log("add_stock_pool", {'sid': sid, 'day': day, 'close_price': close_price, 'result':pool_result}) for field_name in high_field_list[high_index:]: stock_info[field_name] = close_price field_list.append(field_name + "=" + str(stock_info[field_name])) if low_index < 4: low_type = low_index + 1 low_threshold_list = get_stock_price_threshold(db_config, sid, range_start_day, day, 0, low_type) if 0 == len(low_threshold_list): add_result = add_stock_price_threshold(db_config, sid, day, close_price, high_type, low_type) print format_log("add_low_price_threshold", {'sid': sid, 'day': day, 'close_price': close_price, 'low_type': low_type, 'result':add_result}) # TODO: 把下跌突破也加入股票池 for field_name in low_field_list[low_index:]: stock_info[field_name] = close_price field_list.append(field_name + "=" + str(stock_info[field_name])) sql = sql + ", ".join(field_list) + " where id=" + str(stock_info['id']) print sql # 股票且设置刷新, 才插入价格突破记录 if refresh and int(stock_info['type']) == 1: try: db_conn.query_sql(sql, True) except Exception as e: continue log_info = {'sid': sid, 'code': stock_info['code'], 'name': stock_info['name'], 'day': day, 'close_price': stock_data['close_price'], 'high_price': stock_data['high_price'], 'low_price': stock_data['low_price'], 'high_index': high_index, 'low_index': low_index} print format_log("refresh_stock_info", log_info) #TODO: 统一删除变化的stock_info if refresh: conn = redis.StrictRedis(redis_config['host'], redis_config['port']) key_list = [ "stock:info-" + str(sid) for sid in vary_stock_list.keys() ] conn.delete(tuple(key_list)) return vary_stock_list
def rapid_fall(self, item): sid = item['sid'] price_pair_map = self.vary_map[sid] if price_pair_map is None: self.logger.warning("desc=non_exist_pairmap sid=%d", sid) return elif len(price_pair_map) <= 3: return start_time = int(item['items'][0]['time'] / 100) last_time = self.time_map[sid] #print price_pair_map time_list = price_pair_map.keys() time_list.sort() key = "ts-rf-" + str(item['day']) fall_info = None fall_map = dict() refresh = False cache_value = self.redis_conn.hget(key, sid) # 已经存在则判断[start_time, last_time]对应的最低价 <= low, 是则更新其时间 if cache_value: fall_map = json.loads(cache_value) for fall_start_time, fall_info in fall_map.items(): now_time = fall_info['now_time'] diff_sec = self.get_diff_time(start_time, now_time) # 超过5min不再认为是连续下跌 if diff_sec >= 0 and diff_sec <= 60 * 5: (fall_info, refresh) = self.refresh_rapid(sid, fall_info, start_time, False) if refresh: self.redis_conn.hmset(key, {sid: json.dumps(fall_map)}) self.logger.info(format_log("ts_refresh_rapid_fall", fall_info)) break return try: index = time_list.index(start_time) except ValueError: self.logger.warning("err=time_not_exist sid=%d start_time=%d", sid, start_time) else: while index >= 2 and index < len(time_list): now_time = time_list[index] past_time = time_list[index-2] index += 1 # 对当前时间的最低价 减去 2分钟前的最高价,若跌幅比例超过1.6%,则认为存在快速拉升的可能 cur_low_price = price_pair_map[now_time][1] past_high_price = price_pair_map[past_time][0] vary_portion = round((cur_low_price - past_high_price) / past_high_price * 100, 1) if (past_high_price >= 3.0 and vary_portion <= -1.6) or (past_high_price < 3.0 and vary_portion <= -2.5): fall_info = {'start_time': past_time, 'now_time': now_time, 'low': cur_low_price, 'high': past_high_price, 'vary_portion': vary_portion} fall_info['duration'] = self.get_diff_time(fall_info['now_time'], fall_info['start_time']) break if fall_info: if now_time < last_time and index < len(time_list): (fall_info, refresh) = self.refresh_rapid(sid, fall_info, time_list[index], False) fall_map[fall_info['start_time']] = fall_info self.redis_conn.hmset(key, {sid: json.dumps(fall_map)}) fall_info['sid'] = sid fall_info['day'] = item['day'] self.logger.info(format_log("ts_new_rapid_fall", fall_info))
def close_position(self, location, day, cur_timenumber, sid, item): if sid not in self.stock_map or self.stock_map[sid][ 'state'] >= PortfolioManager.STATE_WAIT_CLOSE: self.logger.debug( "desc=stock_closed_already location=%d sid=%d day=%d", location, sid, day) return stock_order = self.stock_map[sid] daily_item = self.get_stock_currentinfo(sid, day) if daily_item is None: return current_price = daily_item['close_price'] stock_time = daily_item['time'] vary_portion = (current_price - stock_order['open_price'] ) / stock_order['open_price'] * 100 if stock_order['op'] == MinuteTrend.OP_SHORT: vary_portion = -1 * vary_portion need_close = False reason = "" # 尝试获利平仓 if vary_portion > 0: for close_portion_item in self.chance_config[location][ 'close_portion_cond']: (hour_time, hour_portion) = close_portion_item if stock_time >= hour_time and vary_portion >= hour_portion: need_close = True reason = "profit" break # 止损平仓: 越过止损位 if not need_close and (stock_order['op'] == MinuteTrend.OP_LONG and current_price <= stock_order['stop_price']) or ( stock_order['op'] == MinuteTrend.OP_SHORT and current_price >= stock_order['stop_price']): reason = "stop" need_close = True # 结合最近30min趋势来分析是否平仓 if not need_close: trend_key = "trend-" + str(sid) + "-" + str(day) suggest_op = MinuteTrend.OP_WAIT latest_trend_value = self.redis_conn.lindex(trend_key, -1) trend_node = json.loads( latest_trend_value) if latest_trend_value else None if trend_node: suggest_op = MinuteTrend.OP_MAP[trend_node['trend'][0]] elif item is not None: suggest_op = item['trend_detail']['op'] if item['trend_detail']['changed']: need_close = True reason = "pivot" if suggest_op != MinuteTrend.OP_WAIT and suggest_op != stock_order[ 'op']: need_close = True reason = "pivot" # 超过指定时间平仓 if not need_close and stock_time >= self.chance_config[location][ 'close_deadline_time']: reason = "time" need_close = True item_json = "" if item is None else json.dumps(item) self.logger.info( "%s need_close=%s reason=%s location=%d day=%d time=%d current_price=%.2f vary_portion=%.2f item=%s", format_log("close_detail", stock_order), str(need_close), reason, location, day, stock_time, current_price, abs(vary_portion), item_json) #TODO: 调用订单平仓, 这里需要注意下单平仓后, 成交之前重复下单 if need_close: close_item = { 'sid': sid, 'day': day, 'code': daily_item['code'], 'time': cur_timenumber, 'price': current_price } close_item['op'] = MinuteTrend.OP_SHORT if stock_order[ 'op'] == MinuteTrend.OP_LONG else MinuteTrend.OP_LONG self.portfolio.close(sid, close_item)
def open_position(self, location, day, cur_timenumber, sid, item): # 获取该股票所有的操作机会 same_count = 1 contray_count = 0 key = "chance-" + str(sid) + "-" + str(day) # 该机会不一定是最新的1个 chance_item_list = self.redis_conn.lrange(key, 0, -1) for chance_item_data in chance_item_list: chance_item = json.loads(chance_item_data) # 忽略自己 if chance_item['time'] == item['time']: continue if chance_item['chance']['op'] == item['chance']['op']: same_count += 1 else: contray_count += 1 # 直接买入 #print same_count, contray_count if contray_count == 0 or same_count > contray_count: order_event = { 'sid': sid, 'day': day, 'code': item['code'], 'time': item['time'] } stop_price = item['chance']['stop_price'] if item['chance']['op'] == MinuteTrend.OP_LONG: open_price = item['chance']['price_range'][1] stop_price = min( stop_price, open_price * (1 - self.chance_config[location]['stop_portion'] / 100)) else: open_price = item['chance']['price_range'][0] stop_price = max( stop_price, open_price * (1 + self.chance_config[location]['stop_portion'] / 100)) # <=1000时建仓, 取区间的中间价格作为建仓价 if item['time'] <= 1000: open_price = (item['chance']['price_range'][0] + item['chance']['price_range'][1]) / 2 order_event['open_price'] = open_price order_event['stop_price'] = stop_price order_event['op'] = item['chance']['op'] # 调用PortfioManager进行建仓 open_result = self.portfolio.open(sid, order_event) self.logger.info("%s open_result=%s", format_log("open_position", order_event), str(open_result)) # 建仓的机会push到单独的队列中 self.redis_conn.rpush("order-chance", json.dumps(item)) return True return False
def chance_return(config_info, location, day, item): hqdata = get_hqdata(config_info['DB'], config_info['REDIS'], day, item['sid']) print hqdata['daily'] chance_item = item['chance'] trend_item = item['trend_item'] # 卖出时用平仓价格减卖出价格, 需要乘-1 factor = 1 if 1 == chance_item['op'] else -1 # 严格一点, 应该用time之后的high_price/low_price计算收益和回撤, 买入点以较大价格进入, 卖出以较低价格卖出 enter_price = chance_item['price_range'][1] if 1 == chance_item[ 'op'] else chance_item['price_range'][0] exit_price = float(hqdata['daily']['close_price']) fall_price = float( hqdata['daily']['low_price']) if 1 == chance_item['op'] else float( hqdata['daily']['high_price']) rise_portion = factor * (exit_price - enter_price) / enter_price * 100 fall_portion = factor * (fall_price - enter_price) / enter_price * 100 return_info = { "sid": item['sid'], "code": item['code'], "time": item['time'], "op": chance_item['op'], "trend_length": trend_item['length'], "trend_portion": trend_item['vary_portion'], "enter": enter_price, "exit": exit_price, "fall": fall_price, "close_portion": rise_portion, "fall_portion": fall_portion, "vary_portion": hqdata['daily']['vary_portion'] } # TODO: 输出 dyn数据 record_list = [] try: sql = "select * from t_stock_dyn where sid = {sid} and day < {day} order by day desc limit 1".format( sid=item['sid'], day=day) #print sql db_conn = SqlUtil.get_db(config_info['DB']) record_list = db_conn.query_sql(sql) except Exception as e: print e logging.getLogger("chance").error( "err=get_stock_dyn sid=%d code=%s location=%d day=%d", item['sid'], item['code'], location, day) else: #print record_list if len(record_list) == 1: stock_dyn = record_list[0] for key in [ 'ma5_swing', 'ma20_swing', 'ma5_vary_portion', 'ma20_vary_portion', 'ma5_exchange_portion', 'ma20_exchange_portion', 'volume_ratio' ]: return_info[key] = stock_dyn[key] logging.getLogger("chance").info("%s", format_log("chance_item", item)) return return_info
portfolio_list = manager.get_portfolio(PortfolioManager.STATE_ALL) print portfolio_list # 平仓下单 close_item = dict() close_item['sid'] = sid close_item['code'] = code close_item['day'] = day close_item['time'] = 950 close_item['op'] = MinuteTrend.OP_SHORT close_item['close_price'] = 22.00 close_result = manager.close(sid, close_item) # 平仓订单成交 close_event = dict() close_event['order_id'] = 10013 close_event['code'] = code close_event['op'] = MinuteTrend.OP_SHORT close_event['quantity'] = 80 close_event['price'] = 22.00 close_event['cost'] = close_event['quantity'] * close_event['price'] close_event['time'] = 1030 manager.fill_order(close_event) for sid, order_info in manager.order_stock.items(): print format_log("order_info", order_info) records = manager.get_trade_records(sid) for record in records: print format_log("trade_record", record)
def rapid_rise(self, item): sid = item['sid'] start_time = int(item['items'][0]['time'] / 100) last_time = self.time_map[sid] price_pair_map = self.vary_map[sid] if price_pair_map is None: self.logger.warning("desc=non_exist_pairmap sid=%d", sid) return elif len(price_pair_map) <= 3: return # 取出的key列表后按照时间大小排列 time_list = price_pair_map.keys() time_list.sort() rise_map = dict() rise_info = None key = "ts-rr-" + str(item['day']) cache_value = self.redis_conn.hget(key, sid) refresh = False # 已经存在则判断[start_time, last_time]对应的最高价 >= high, 是则更新其时间 # 连续拉升结束后, 超过5min后的再次拉升作为一个新的拉升波段, 根据起始时间存储多个拉升波段 if cache_value: rise_map = json.loads(cache_value) for rise_start_time, rise_info in rise_map.items(): now_time = rise_info['now_time'] diff_sec = self.get_diff_time(start_time, now_time) # 5min 以内作为一个新的波段持续 if diff_sec >= 0 and diff_sec <= 60 * 5: (rise_info, refresh) = self.refresh_rapid(sid, rise_info, start_time, True) if refresh: self.redis_conn.hmset(key, {sid: json.dumps(rise_map)}) self.logger.info( format_log("ts_refresh_rapid_rise", rise_info)) break return try: index = max(time_list.index(start_time), 2) except ValueError: self.logger.warning("err=time_not_exist sid=%d start_time=%d", sid, start_time) else: while index >= 2 and index < len(time_list): now_time = time_list[index] past_time = time_list[index - 2] index += 1 # 对当前时间的最高价 减去 2分钟前的最低价,若涨幅比例超过1.6%,则认为存在快速拉升的可能 cur_high_price = price_pair_map[now_time][0] past_low_price = price_pair_map[past_time][1] vary_portion = round( (cur_high_price - past_low_price) / past_low_price * 100, 2) if (past_low_price >= 3.0 and vary_portion >= 1.6) or (past_low_price < 3.0 and vary_portion >= 2.5): rise_info = { 'start_time': past_time, 'now_time': now_time, 'low': past_low_price, 'high': cur_high_price, 'vary_portion': vary_portion } rise_info['duration'] = self.get_diff_time( rise_info['now_time'], rise_info['start_time']) break #print rise_info, now_time if rise_info: if now_time < last_time and index < len(time_list): (rise_info, refresh) = self.refresh_rapid(sid, rise_info, time_list[index], True) rise_map[rise_info['start_time']] = rise_info self.redis_conn.hmset(key, {sid: json.dumps(rise_map)}) rise_info['sid'] = sid rise_info['day'] = item['day'] self.logger.info(format_log("ts_new_rapid_rise", rise_info))
def core(self, item): sid = item[0] scode = item[1] url = "" if 1 == self.location: key = scode url = "http://web.ifzq.gtimg.cn/appstock/app/minute/query?_var=min_data_{CODE}&code={CODE}&r=" + str(random.random()) elif 3 == self.location: stock_info = self.datamap['stock_list'][sid] if 1 == int(stock_info['type']): ecode_str = "OQ" if 4 == int(stock_info['ecode']) else "N" key = scode + "." + ecode_str else: key = scode url = "http://web.ifzq.gtimg.cn/appstock/app/UsMinute/query?_var=min_data_{CODE}&code={CODE}&r=" + str(random.random()) try: request_url = url.format(CODE=key) content = "" try: response = requests.get(request_url, timeout=5) content = response.text except Exception as e: self.logger.exception("err=get_stock_realtime sid=%d scode=%s", sid, scode) return None hq_json = None if content.find("=") != -1: part = content.split("=") if len(part) >= 2 and part[1]: hq_json = json.loads(part[1]) else: #部分股票返回时没有=及前面部分, 直接是个json hq_json = json.loads(content) if hq_json is None or hq_json["code"] == -1: self.logger.error("err=invalid_realtime_content sid=%d scode=%s url=%s content=%s", sid, scode, url, content) return data_json = hq_json['data'][key]['data'] #print data_json #qt包含市场指数和明细, mx为最近2min的逐笔成交明细, price为分价数据 date_str = data_json['date'].strip() #返回的日期为当前日期,非美国时区的日期 data_day = int(date_str) if len(date_str) > 0 else self.day hq_item = list() last_time = 0 if sid in self.time_map: last_time = self.time_map[sid] new_time = last_time for line in data_json['data']: fields = line.split(" ") try: if len(fields) < 3 or len(fields[0]) == 0: continue # 直接用小时+分组成的时间, 格式为HHMM time = int(fields[0].lstrip("0")) if time <= last_time: continue data_item = dict() data_item['time'] = time data_item['price'] = float(fields[1].replace(",", "")) # 美股拉取的成交量为累计成交量 data_item['volume'] = int(fields[2]) if data_item['volume'] <= 0: continue hq_item.append(data_item) new_time = max(time, new_time) except Exception as e: self.logger.exception("err=parse_stock_realtime sid=%d scode=%s line=%s", sid, scode, line) continue except Exception as ex: self.logger.exception("err=get_stock_realtime sid=%d scode=%s url=%s", sid, scode, url) return # 表示当天所有的成交量都为0, 当天停牌 if len(hq_item) == 0: return # 更新last_time print scode, key, request_url, len(hq_item) self.time_map[sid] = new_time json_item = json.dumps({'sid': sid, 'day': data_day, 'items': hq_item}) self.conn.rpush("realtime-queue", json_item) self.logger.info(format_log("fetch_realtime", {'sid': sid, 'scode': scode, 'time': hq_item[len(hq_item) - 1]['time'], 'price': hq_item[len(hq_item) - 1]['price']})) # 设置dump则把数据dump到日志中, 暂定每5min dump一次, 可配置 self.dump(new_time, json_item, "realtime")
def rapid_fall(self, item): sid = item['sid'] price_pair_map = self.vary_map[sid] if price_pair_map is None: self.logger.warning("desc=non_exist_pairmap sid=%d", sid) return elif len(price_pair_map) <= 3: return start_time = int(item['items'][0]['time'] / 100) last_time = self.time_map[sid] #print price_pair_map time_list = price_pair_map.keys() time_list.sort() key = "ts-rf-" + str(item['day']) fall_info = None fall_map = dict() refresh = False cache_value = self.redis_conn.hget(key, sid) # 已经存在则判断[start_time, last_time]对应的最低价 <= low, 是则更新其时间 if cache_value: fall_map = json.loads(cache_value) for fall_start_time, fall_info in fall_map.items(): now_time = fall_info['now_time'] diff_sec = self.get_diff_time(start_time, now_time) # 超过5min不再认为是连续下跌 if diff_sec >= 0 and diff_sec <= 60 * 5: (fall_info, refresh) = self.refresh_rapid(sid, fall_info, start_time, False) if refresh: self.redis_conn.hmset(key, {sid: json.dumps(fall_map)}) self.logger.info( format_log("ts_refresh_rapid_fall", fall_info)) break return try: index = time_list.index(start_time) except ValueError: self.logger.warning("err=time_not_exist sid=%d start_time=%d", sid, start_time) else: while index >= 2 and index < len(time_list): now_time = time_list[index] past_time = time_list[index - 2] index += 1 # 对当前时间的最低价 减去 2分钟前的最高价,若跌幅比例超过1.6%,则认为存在快速拉升的可能 cur_low_price = price_pair_map[now_time][1] past_high_price = price_pair_map[past_time][0] vary_portion = round( (cur_low_price - past_high_price) / past_high_price * 100, 1) if (past_high_price >= 3.0 and vary_portion <= -1.6) or ( past_high_price < 3.0 and vary_portion <= -2.5): fall_info = { 'start_time': past_time, 'now_time': now_time, 'low': cur_low_price, 'high': past_high_price, 'vary_portion': vary_portion } fall_info['duration'] = self.get_diff_time( fall_info['now_time'], fall_info['start_time']) break if fall_info: if now_time < last_time and index < len(time_list): (fall_info, refresh) = self.refresh_rapid(sid, fall_info, time_list[index], False) fall_map[fall_info['start_time']] = fall_info self.redis_conn.hmset(key, {sid: json.dumps(fall_map)}) fall_info['sid'] = sid fall_info['day'] = item['day'] self.logger.info(format_log("ts_new_rapid_fall", fall_info))
def serialize(self, item): key = "daily-" + str(item['sid']) + "-" + str(item['day']) result = self.redis_conn.set(key, json.dumps(item), 86400) self.logger.debug("%s", format_log("daily_item", item))
def open(self, sid, open_item): wait_open_map = self.get_portfolio(PortfolioManager.STATE_WAIT_OPEN) if sid in wait_open_map: self.logger.info("op=stock_wait_open sid=%d code=%s", sid, wait_open_map[sid]['code']) return False # 暂时不允许已建仓的股票再建仓 opened_map = self.get_portfolio(PortfolioManager.STATE_OPENED) if sid in opened_map and open_item['op'] == opened_map[sid]['op']: self.logger.info("%s", format_log("stock_order_opened", opened_map[sid])) return False # 建仓是否允许 if not self.check_allow("OPEN", open_item): self.logger.info("%s %s", format_log("open_not_allowed", open_item), json.dumps(self.port_statinfo)) return False # TODO: cash仅在成交后才会扣减, 实际通过IB下单时, 会出现成交前多只股票都可以下单 rest_money = self.holdings['cash'] quantity = self.cal_quantity(sid, open_item['op'], rest_money, open_item['open_price']) if quantity <= 0: self.logger.info( "op=no_enough_money sid=%d code=%s op=%d rest_money=%d open_price=%.2f", sid, open_item['code'], open_item['op'], rest_money, open_item['open_price']) return False # TODO: 推送下单消息, 设置建仓价格 + 止损价格, 设置下单类型为限价单 order_info = { 'sid': sid, 'day': open_item['day'], 'code': open_item['code'], 'op': open_item['op'], 'quantity': quantity, 'stop_price': open_item['stop_price'] } order_event = dict(order_info) order_event['order_type'] = "LMT" order_event['price'] = open_item['open_price'] push_result = self.redis_conn.rpush("order-queue", json.dumps(order_event)) # 更新剩余的现金 sign = 1 if open_item['op'] == MinuteTrend.OP_LONG else -1 cost = sign * quantity * open_item['open_price'] order_event['time'] = open_item['time'] self.logger.info("%s cost=%d", format_log("open_order", order_event), cost) order_info['open_price'] = open_item['open_price'] order_info['state'] = self.STATE_WAIT_OPEN self.order_stock[sid] = order_info # 更新持仓管理的统计 self.update_stat("OPEN", open_item) # TODO: 目前先手动构造fill_event来成交 fill_event = { 'code': order_event['code'], 'op': order_event['op'], 'quantity': quantity, 'price': order_event['price'], 'cost': cost, 'time': open_item['time'] } fill_event['order_id'] = random.randint(100, 500) self.fill_order(fill_event) return True
def evaluate(self, day, policy): stock_info = self.get_stock_info(self.sid) print stock_info cur_day = str(day) current_time = datetime.datetime(int(cur_day[0:4]), int(cur_day[4:6]), int(cur_day[6:8])) start_day = '{0:%Y%m%d}'.format(current_time + datetime.timedelta(days=-60)) # 获取最近60天内的交易数据 history_data = self.get_histdata_range(self.sid, start_day, cur_day) if len(history_data) <= 20: return None today_data = history_data[0] print today_data check_result = self.check(stock_info, history_data, today_data) if check_result < 0: print format_log( "check_failed", { 'sid': self.sid, 'name': stock_info['name'], 'day': day, 'result': check_result, 'close_price': today_data['close_price'] }) return None today_open_price = float(today_data['open_price']) today_close_price = float(today_data['close_price']) # 股票趋势 trend_info = self.get_trend(history_data[0:10], 5) print trend_info # 判断股票是否符合指定的分析策略 judge_info = self.judge(trend_info, stock_info, history_data, policy) print judge_info if judge_info is False: print format_log( "judge_failed", { 'sid': self.sid, 'name': stock_info['name'], 'day': day, 'trend': trend_info['trend'], 'wave': trend_info['wave'], 'close_price': today_data['close_price'] }) return None # TODO: 评估股票的综合得分 score = self.rank(trend_info, stock_info, history_data, policy) pool_info = dict() pool_info['trend'] = trend_info['trend'] pool_info['wave'] = trend_info['wave'] pool_info.update({ 'low_price': judge_info[0], 'high_price': judge_info[1] }) pool_info['current_price'] = today_data['close_price'] pool_info['sid'] = self.sid pool_info['day'] = cur_day pool_info['score'] = score # 把股票加入股票池中 add = self.add_stock_pool(self.sid, day, pool_info) print "op=add_pool_record, add=" + str(add) return pool_info
def core(self, item): sid = item[0] scode = item[1] if sid in self.ignore_set: return if sid in self.pos_map: (pno, last_id) = self.pos_map[sid] else: pno = last_id = 0 url = "http://stock.gtimg.cn/data/index.php?appn=detail&action=data&c=" + scode + "&p=" + str(pno) #print scode, url try: response = urllib2.urlopen(url, timeout=5) content = response.read() except urllib2.HTTPError as e: self.logger.warning("err=get_stock_transaction sid=%d scode=%s pno=%d code=%s", sid, scode, pno, str(e.code)) return None except urllib2.URLError as e: self.logger.warning("err=get_stock_transaction sid=%d scode=%s pno=%d reason=%s", sid, scode, pno, str(e.reason)) return None # 拉取内容为空, 表明股票当天停牌, TODO: 加入公共的停牌列表中 if 0 == len(content.strip()): self.ignore_set.add(sid) return lines = content.split('"') if len(lines) < 2: self.logger.warning("err=invalid_resp sid=%d scode=%s content=%s", sid, scode, content) return #print lines elements = lines[1].split("|") new_id = last_id transaction_list = [] for element in elements: field_list = element.split("/") #print field_list transaction = dict() id = int(field_list[0]) if id <= last_id : continue transaction['time'] = field_list[1].replace(":", "") transaction['price'] = float(field_list[2]) transaction['vary_price'] = float(field_list[3]) transaction['volume'] = int(field_list[4]) transaction['amount'] = int(field_list[5]) # 类型为B/S/M, 分别代表买盘/卖盘/中性盘 transaction['type'] = field_list[6] new_id = max(id, new_id) transaction_list.append(transaction) transaction_count = len(transaction_list) #print format_log("fetch_transaction", {'sid': sid, 'scode': scode, 'p': pno, 'last_id': last_id, 'new_id': new_id, 'detail_count': transaction_count}) self.logger.info(format_log("fetch_transaction", {'sid': sid, 'scode': scode, 'p': pno, 'last_id': last_id, 'new_id': new_id, 'detail_count': transaction_count})) if transaction_count > 0: # 每个时间段达到70笔成交记录时, p需要加1 if transaction_count == 70: pno += 1 #更新pno和last_id的值 self.pos_map[sid] = (pno, new_id) self.conn.rpush("ts-queue", json.dumps({'sid': sid, 'day': self.day, 'items': transaction_list})) else: # 没有新记录表明拉取完了 self.ignore_set.add(sid) return
def rapid_rise(self, item): sid = item['sid'] start_time = int(item['items'][0]['time'] / 100) last_time = self.time_map[sid] price_pair_map = self.vary_map[sid] if price_pair_map is None: self.logger.warning("desc=non_exist_pairmap sid=%d", sid) return elif len(price_pair_map) <= 3: return # 取出的key列表后按照时间大小排列 time_list = price_pair_map.keys() time_list.sort() rise_map = dict() rise_info = None key = "ts-rr-" + str(item['day']) cache_value = self.redis_conn.hget(key, sid) refresh = False # 已经存在则判断[start_time, last_time]对应的最高价 >= high, 是则更新其时间 # 连续拉升结束后, 超过5min后的再次拉升作为一个新的拉升波段, 根据起始时间存储多个拉升波段 if cache_value: rise_map = json.loads(cache_value) for rise_start_time, rise_info in rise_map.items(): now_time = rise_info['now_time'] diff_sec = self.get_diff_time(start_time, now_time) # 5min 以内作为一个新的波段持续 if diff_sec >= 0 and diff_sec <= 60 * 5: (rise_info, refresh) = self.refresh_rapid(sid, rise_info, start_time, True) if refresh: self.redis_conn.hmset(key, {sid: json.dumps(rise_map)}) self.logger.info(format_log("ts_refresh_rapid_rise", rise_info)) break return try: index = max(time_list.index(start_time), 2) except ValueError: self.logger.warning("err=time_not_exist sid=%d start_time=%d", sid, start_time) else: while index >= 2 and index < len(time_list): now_time = time_list[index] past_time = time_list[index-2] index += 1 # 对当前时间的最高价 减去 2分钟前的最低价,若涨幅比例超过1.6%,则认为存在快速拉升的可能 cur_high_price = price_pair_map[now_time][0] past_low_price = price_pair_map[past_time][1] vary_portion = round((cur_high_price - past_low_price) / past_low_price * 100, 2) if (past_low_price >= 3.0 and vary_portion >= 1.6) or (past_low_price < 3.0 and vary_portion >= 2.5): rise_info = {'start_time': past_time, 'now_time': now_time, 'low': past_low_price, 'high': cur_high_price, 'vary_portion': vary_portion} rise_info['duration'] = self.get_diff_time(rise_info['now_time'], rise_info['start_time']) break #print rise_info, now_time if rise_info: if now_time < last_time and index < len(time_list): (rise_info, refresh) = self.refresh_rapid(sid, rise_info, time_list[index], True) rise_map[rise_info['start_time']] = rise_info self.redis_conn.hmset(key, {sid: json.dumps(rise_map)}) rise_info['sid'] = sid rise_info['day'] = item['day'] self.logger.info(format_log("ts_new_rapid_rise", rise_info))
def parse_stock_daily(self, line): line = line.strip("\r\n") parts = line.split("=") #print line, parts stock_code = parts[0].replace("var hq_str_gb_", "").upper() content = parts[1].strip('"') #print content fields = content.split(",") #print fields if len(fields) < 20: line_str = safestr(line) self.logger.error(format_log("daily_lack_fields", {'line': line_str, 'content': content})) return None # 当日停牌则不能存入 open_price = float(fields[5]) close_price = float(fields[1]) if open_price == 0.0 or close_price == 0.0: return None item = dict() try: item['name'] = safestr(fields[0]) item['code'] = stock_code item['sid'] = int(self.datamap['code2id'][stock_code]) item['day'] = self.day item['last_close_price'] = float(fields[26]) item['open_price'] = open_price item['high_price'] = float(fields[6]) item['low_price'] = float(fields[7]) item['close_price'] = close_price # 当前时刻, 格式为HHMMSS, 这里新浪返回的时间是错的, 改成自己计算, 可能出现不实时的情况 #time_parts = fields[3].split(" ") #item['time'] = time_parts[1].replace(":", "") item['time'] = str(get_timenumber(3)) item['vary_price'] = float(fields[4]) item['vary_portion'] = float(fields[2]) # 成交量单位为股 item['volume'] = int(fields[10]) item['predict_volume'] = get_predict_volume(item['volume'], item['time'], self.location) # 成交额转化为万元 item['amount'] = 0 # 总股本 item['out_capital'] = float(fields[19]) # 总市值 item['cap'] = float(fields[12]) # 计算换手率 if item['out_capital'] > 0: item['exchange_portion'] = item['volume'] / item['out_capital'] * 100 item['swing'] = (item['high_price'] - item['low_price']) / item['last_close_price'] * 100 except Exception as e: traceback.print_exc() self.logger.exception("err=parse_daily_ex code=%s line=%s", stock_code, line) return None if item['out_capital'] > 0: capital = item['out_capital'] / 10000 self.logger.debug("op=update_sql sql={update t_stock set capital=%.2f, out_capital=%.2f where id = %d;}", capital, capital, item['sid']) return item
def core(self, item): sid = item[0] scode = item[1] url = "http://data.gtimg.cn/flashdata/hushen/minute/" + scode + ".js?maxage=10&" + str( random.random()) #print scode, url try: response = urllib2.urlopen(url, timeout=1) content = response.read() except urllib2.HTTPError as e: self.logger.warning( "err=get_stock_realtime sid=%d scode=%s code=%s", sid, scode, str(e.code)) return None except urllib2.URLError as e: self.logger.warning( "err=get_stock_realtime sid=%d scode=%s reason=%s", sid, scode, str(e.reason)) return None content = content.strip(' ;"\n').replace("\\n\\", "") lines = content.split("\n") #print lines date_info = lines[1].split(":") data_day = int("20" + date_info[1]) hq_item = list() last_time = 0 if sid in self.time_map: last_time = self.time_map[sid] new_time = last_time for line in lines[2:]: fields = line.split(" ") # 直接用小时+分组成的时间, 格式为HHMM time = int(fields[0]) if time <= last_time: continue data_item = dict() data_item['time'] = time data_item['price'] = float(fields[1]) data_item['volume'] = int(fields[2]) if data_item['volume'] <= 0: continue hq_item.append(data_item) new_time = max(time, new_time) # 表示当天所有的成交量都为0, 当天停牌 if len(hq_item) == 0: return # 更新last_time self.time_map[sid] = new_time self.conn.rpush( "realtime-queue", json.dumps({ 'sid': sid, 'day': data_day, 'items': hq_item })) #print format_log("fetch_realtime", {'sid': sid, 'scode': scode, 'time': hq_item[len(hq_item) - 1]['time'], 'price': hq_item[len(hq_item) - 1]['price']}) self.logger.info( format_log( "fetch_realtime", { 'sid': sid, 'scode': scode, 'time': hq_item[len(hq_item) - 1]['time'], 'price': hq_item[len(hq_item) - 1]['price'] }))
portfolio_list = manager.get_portfolio(PortfolioManager.STATE_ALL) print portfolio_list # 平仓下单 close_item = dict() close_item['sid'] = sid close_item['code'] = code close_item['day'] = day close_item['time'] = 950 close_item['op'] = MinuteTrend.OP_SHORT close_item['close_price'] = 22.00 close_result = manager.close(sid, close_item) # 平仓订单成交 close_event = dict() close_event['order_id'] = 10013 close_event['code'] = code close_event['op'] = MinuteTrend.OP_SHORT close_event['quantity'] = 80 close_event['price'] = 22.00 close_event['cost'] = close_event['quantity'] * close_event['price'] close_event['time'] = 1030 manager.fill_order(close_event) for sid, order_info in manager.order_stock.items(): print format_log("order_info", order_info) records = manager.get_trade_records(sid) for record in records: print format_log("trade_record", record)
def close_position(self, location, day, cur_timenumber, sid, item): if sid not in self.stock_map or self.stock_map[sid]['state'] >= PortfolioManager.STATE_WAIT_CLOSE: self.logger.debug("desc=stock_closed_already location=%d sid=%d day=%d", location, sid, day); return stock_order = self.stock_map[sid] daily_item = self.get_stock_currentinfo(sid, day) if daily_item is None: return current_price = daily_item['close_price'] stock_time = daily_item['time'] vary_portion = (current_price - stock_order['open_price']) / stock_order['open_price'] * 100 if stock_order['op'] == MinuteTrend.OP_SHORT: vary_portion = -1 * vary_portion need_close = False reason = "" # 尝试获利平仓 if vary_portion > 0: for close_portion_item in self.chance_config[location]['close_portion_cond']: (hour_time, hour_portion) = close_portion_item if stock_time >= hour_time and vary_portion >= hour_portion: need_close = True reason = "profit" break # 止损平仓: 越过止损位 if not need_close and (stock_order['op'] == MinuteTrend.OP_LONG and current_price <= stock_order['stop_price']) or (stock_order['op'] == MinuteTrend.OP_SHORT and current_price >= stock_order['stop_price']): reason = "stop" need_close = True # 结合最近30min趋势来分析是否平仓 if not need_close: trend_key = "trend-" + str(sid) + "-" + str(day) suggest_op = MinuteTrend.OP_WAIT latest_trend_value = self.redis_conn.lindex(trend_key, -1) trend_node = json.loads(latest_trend_value) if latest_trend_value else None if trend_node: suggest_op = MinuteTrend.OP_MAP[trend_node['trend'][0]] elif item is not None: suggest_op = item['trend_detail']['op'] if item['trend_detail']['changed']: need_close = True reason = "pivot" if suggest_op != MinuteTrend.OP_WAIT and suggest_op != stock_order['op']: need_close = True reason = "pivot" # 超过指定时间平仓 if not need_close and stock_time >= self.chance_config[location]['close_deadline_time']: reason = "time" need_close = True item_json = "" if item is None else json.dumps(item) self.logger.info("%s need_close=%s reason=%s location=%d day=%d time=%d current_price=%.2f vary_portion=%.2f item=%s", format_log("close_detail", stock_order), str(need_close), reason, location, day, stock_time, current_price, abs(vary_portion), item_json) #TODO: 调用订单平仓, 这里需要注意下单平仓后, 成交之前重复下单 if need_close: close_item = {'sid': sid, 'day': day, 'code': daily_item['code'], 'time': cur_timenumber, 'price': current_price} close_item['op'] = MinuteTrend.OP_SHORT if stock_order['op'] == MinuteTrend.OP_LONG else MinuteTrend.OP_LONG self.portfolio.close(sid, close_item)