def history_exists(c): return False if c.offers_at: first_date_before = c.offers_at + timedelta(days=7) q = Quote.query_one({ 'exchange': c.exchange, 'symbol': c.symbol, 'quote_type': '1d', 'quote_at': { '$lte': first_date_before } }) count = Quote.count({ 'exchange': c.exchange, 'symbol': c.symbol, 'quote_type': '1d' }) past_days = (datetime.utcnow() - c.offers_at).days if past_days <= 2: return True trades_ratio = 1. * count / past_days if q and trades_ratio > 3 / 7.: return True return False
def sync_collections(): from ybk.config import setup_config from ybk.models import Collection as C1 from ybk.models import Quote as Q1 setup_config() for c in C1.query(): print(c.exchange, c.symbol) td = Q1.count({'exchange': c.exchange, 'symbol': c.symbol, 'type_': '1d'}) + 1 if td == 1: if not c.offers_at: # 没录入过, 基本上会挂 continue # 如果K线不存在, 可能是交易行情无法获取, 直接用估算数字 td = (datetime.utcnow() - c.offers_at).days - 1 c2 = Collection({ 'exchange': c.exchange, 'symbol': c.symbol, 'name': c.name, 'trade_day': td, }) c2.upsert()
def realtime(site): conf = get_conf(site) exchange = conf['abbr'] url = conf['quote']['realtime']['url'] type_ = conf['quote']['realtime']['type'] today = datetime.utcnow().replace( minute=0, second=0, microsecond=0) + timedelta(hours=8) if not url: log.warning('{}尚未配置实时行情url'.format(exchange)) return if today.hour < 9 or today.hour > 22: log.warning('不在9点到22点之间, 不做解析') return today = today.replace(hour=0) text = session.get(url, timeout=(3, 7)).text quotes = parse_quotes(type_, text) saved = 0 for q in quotes: Collection.update_one({'exchange': exchange, 'symbol': q['symbol'].strip()}, {'$set': {'name': q['name']}}, upsert=True) q['exchange'] = exchange q['quote_type'] = '1d' q['quote_at'] = today if q['open_'] in ['—', '-', None, '']: continue else: # 找到上一个交易日的数据, 如果和lclose不符则舍弃 # 需要保证数据每天更新/不足时需要把日线补足才能正常显示 lq = Quote.query_one({'exchange': exchange, 'symbol': q['symbol'].strip(), 'quote_type': '1d', 'quote_at': {'$lt': today}}, sort=[('quote_at', -1)]) if not lq or abs(lq.close - q['lclose']) < 0.01: Quote(q).upsert() saved += 1 log.info('{} 导入 {}/{} 条实时交易记录'.format(exchange, saved, len(quotes)))
def history_exists(c): return False if c.offers_at: first_date_before = c.offers_at + timedelta(days=7) q = Quote.query_one({'exchange': c.exchange, 'symbol': c.symbol, 'quote_type': '1d', 'quote_at': {'$lte': first_date_before}}) count = Quote.count({'exchange': c.exchange, 'symbol': c.symbol, 'quote_type': '1d'}) past_days = (datetime.utcnow() - c.offers_at).days if past_days <= 2: return True trades_ratio = 1. * count / past_days if q and trades_ratio > 3 / 7.: return True return False
def save_quotes(q, c, first_quote=False): if first_quote: # 看一下需不需要补齐申购数据 if not c.name.endswith('指数'): if not c.offers_at or abs((c.offers_at - q['quote_at']).days) >= 7: c.offers_at = q['quote_at'] - timedelta(days=2) c.offer_price = q['high'] / 1.3 if 'quantity' in q: # 假定申购是全部数量的1/10 c.offer_quantity = q['quantity'] // 10 log.info('补齐数据:{}_{}, 申购价:{}, 申购量:{}, 时间:{}' ''.format(c.exchange, c.symbol, c.offer_price, c.offer_quantity, c.offers_at)) # 然而实际上不补齐 # c.upsert() Quote(q).upsert()
def collection(): nav = 'collection' search = request.args.get('search', '') exchange = request.args.get('exchange', '') page = int(request.args.get('page', 1) or 1) limit = 25 skip = limit * (page - 1) cond = {} if exchange: cond['exchange'] = exchange total = Collection.count(cond) pagination = Pagination(page, limit, total) collections = list( Collection.query(cond, sort=[('offers_at', -1)], skip=skip, limit=limit)) for c in collections: lp = Quote.latest_price(c.exchange, c.symbol) if lp and c.offer_price: c.total_increase = lp / c.offer_price - 1 return render_template('frontend/collection.html', **locals())
def collection_list(): exchange = request.args.get('exchange', '') search = request.args.get('search', '') sort = request.args.get('sort', 'offers_at') order = request.args.get('order', 'desc') limit = int(request.args.get('limit', 25)) offset = int(request.args.get('offset', 0)) if sort in ['offers_at', 'exchange', 'name', 'symbol', 'offer_price', 'offer_quantity']: dbsort = [(sort, 1 if order == 'asc' else -1)] else: dbsort = None cond = {} if exchange: cond['exchange'] = exchange if search: cond['$or'] = [ {'exchange': {'$regex': search}}, {'name': {'$regex': search}}, {'symbol': {'$regex': search}}, ] total = Collection.count(cond) qs = Collection.find(cond) if dbsort: qs = [Collection(c) for c in qs.sort(dbsort).skip(offset).limit(limit)] rows = [{ 'offers_at': c.offers_at, 'exchange': c.exchange, 'name': c.name, 'symbol': c.symbol, 'offer_price': c.offer_price, 'offer_quantity': c.offer_quantity, 'offer_cash_ratio': c.offer_cash_ratio, 'offer_cash': c.offer_cash, 'result_ratio_cash': c.result_ratio_cash, } for c in qs] for d in rows: d['total_increase'] = None lp = Quote.latest_price(d['exchange'], d['symbol']) if lp and d['offer_price']: d['total_increase'] = lp / d['offer_price'] - 1 if not dbsort: rows = sorted(rows, key=lambda x: x.get(sort) or 0, reverse=order == 'desc') rows = rows[offset:offset + limit] for d in rows: d['offers_at'] = d['offers_at'].strftime( '%Y-%m-%d') if d['offers_at'] else None if d['offer_price']: d['offer_price'] = '{:.2f}'.format(d['offer_price']) if d['offer_cash_ratio']: d['offer_cash_ratio'] = '{:.0f}%'.format( d['offer_cash_ratio'] * 100) if d['offer_cash']: d['offer_cash'] = '{:.1f}'.format(d['offer_cash']) if d['result_ratio_cash']: d['result_ratio_cash'] = '{:.3f}%'.format( d['result_ratio_cash'] * 100) if d['total_increase']: d['total_increase'] = '{:.1f}%'.format( 100 * (d['total_increase'])) return jsonify(total=total, rows=rows)
def accounting(user): """ 对账号进行自动记账(如果用户已配置) """ operated_at = datetime.utcnow() + timedelta(hours=8) def add_transaction(type_, exchange, symbol, price, quantity): t = Transaction({ 'user': user._id, 'type_': type_, 'operated_at': operated_at, 'exchange': exchange, 'symbol': symbol, 'price': price, 'quantity': quantity, }) t.save() if not Position.do_op(t): t.remove() if user.auto_accounting: op = Position.user_position(user._id) p2pairs = {} for ta in TradeAccount.query({'user': user._id}): for p in ta.position or []: pair = (ta.exchange, p.symbol) if pair not in p2pairs: p2pairs[pair] = p else: pp = p2pairs[pair] amount = p.average_price * p.quantity + \ pp.average_price * pp.quantity p.quantity += pp.quantity if p.quantity > 0: p.average_price = amount / p.quantity p2pairs[pair] = p p1pairs = {(p1['exchange'], p1['symbol']): p1 for p1 in op} # 检查更改项 for p1 in op: pair = p1['exchange'], p1['symbol'] if pair[0] in ['湖南文交所', '海西文交所', '上海邮币卡', '上海文交所', '中俄邮币卡']: continue if pair in p2pairs: p2 = p2pairs[pair] quantity = p2.quantity - p1['quantity'] if quantity != 0: type_ = 'buy' if quantity > 0 else 'sell' if type_ == 'buy': price = (p2.average_price * p2.quantity - p1['avg_buy_price'] * p1['quantity']) / quantity else: price = p2.price if price < 0.01: price = Quote.latest_price(p1['exchange'], p1['symbol']) price = int(price * 100) / 100. add_transaction( type_, pair[0], pair[1], price, abs(quantity)) else: # 按现价计算已卖出 if p1['quantity'] > 0: price = Quote.latest_price(p1['exchange'], p1['symbol']) price = int(price * 100) / 100. add_transaction( 'sell', pair[0], pair[1], price, p1['quantity']) # 检查新增项 for pair, p2 in p2pairs.items(): if pair[0] in ['湖南文交所', '海西文交所', '上海邮币卡', '上海文交所', '中俄邮币卡']: continue if pair not in p1pairs and p2.quantity > 0: price = int(p2.average_price * 100) / 100. add_transaction('buy', pair[0], pair[1], price, p2.quantity)
def trade_quote_history(): """ K线数据 """ exchange = request.args.get('exchange', '') symbol = request.args.get('symbol', '') c = Collection.query_one({'exchange': exchange, 'symbol': symbol}) period = request.args.get('period', '1d') # chart1 qs = list(Quote.cached(3600).query({'exchange': exchange, 'symbol': symbol, 'quote_type': period})) name = '{}({}_{})'.format(Collection.get_name(exchange, symbol), exchange, symbol) xdata = [q.quote_at.strftime('%Y/%m/%d') for q in qs] sdata = [ (q.open_, q.close, q.low, q.high,) for q in qs] adata = [int(q.amount / 10000) for q in qs] num_prefix = 100 - len(xdata) if num_prefix > 0: xdata = xdata + [''] * num_prefix sdata = sdata + [()] * num_prefix adata = adata + [0] * num_prefix option1 = { 'title': { 'text': name }, 'xAxis': [{ 'type': 'category', 'boundaryGap': True, 'axisTick': {'onGap': False}, 'splitLine': {'show': False}, 'data': xdata, }], 'legend': { 'data': [name, '成交额(万)'] }, 'dataZoom': { 'show': False, 'realtime': True, 'start': (len(adata) - 100) * 100 // len(adata), 'end': 100 }, 'series': [ {'name': '{}({}_{})'.format(c.name, c.exchange, c.symbol), 'type': 'k', 'data': sdata, }, {'name': '成交额(万)', 'type': 'bar', 'symbol': 'none', 'data': [], }, ], } # chart2 option2 = { 'title': { 'text': '', }, 'toolbox': { 'y': -30, 'show': True, 'feature': { 'mark': {'show': True}, 'dataZoom': {'show': True}, 'dataView': {'show': True, 'readOnly': False}, 'magicType': {'show': True, 'type': ['line', 'bar']}, 'restore': {'show': True}, 'saveAsImage': {'show': True} } }, 'tooltip': { 'trigger': 'axis', 'showDelay': 0, }, 'legend': { 'y': -30, 'data': ['成交额(万)'], }, 'xAxis': [ { 'type': 'category', 'position': 'top', 'boundaryGap': True, 'axisLabel': {'show': False}, 'axisTick': {'onGap': False}, 'splitLine': {'show': False}, 'data': xdata, } ], 'yAxis': { 'splitNumber': 3, }, 'series': [ { 'name': '成交额(万)', 'type': 'bar', 'symbol': 'none', 'data': adata, } ], 'grid': { 'x': 80, 'y': 5, 'x2': 20, 'y2': 40 }, 'dataZoom': { 'show': True, 'realtime': True, 'start': (len(adata) - 100) * 100 // len(adata), 'end': 100 }, } return jsonify(status=200, option1=option1, option2=option2)
def trade_quote_realtime(): """ 实时数据/列表 """ # 全部 -> all # 指数 -> index # 持仓 -> position # 自选 -> diy category = request.args.get('category', '').strip() search = request.args.get('search', '').strip() sort = request.args.get('sort', '').strip() order = request.args.get('order', 'asc').strip() today = Quote.cached(3600).query_one({'quote_type': '1d'}, sort=[('quote_at', -1)]).quote_at cond = {'quote_type': '1d', 'quote_at': today} colls = set() symbols = [] if search: pairs = Collection.search(search) symbols = [p[1] for p in pairs] colls = set(pairs) cond['symbol'] = {'$in': symbols} elif category == 'all': pass elif category == 'index': cs = list(Collection.cached(3600).query({'name': {'$regex': '指数$'}})) colls = set((c.exchange, c.symbol) for c in cs) symbols = [c.symbol for c in cs] cond['symbol'] = {'$in': symbols} elif category == 'position': position = Position.user_position(current_user._id) colls = set((p['exchange'], p['symbol']) for p in position) symbols = [p['symbol'] for p in position] cond['symbol'] = {'$in': symbols} elif category == 'diy': raise NotImplementedError qs = [q for q in Quote.query(cond) if (not colls) or ((q.exchange, q.symbol) in colls)] qs = [{ 'open_': q.open_, 'high': q.high, 'low': q.low, 'close': q.close, 'lclose': q.lclose, 'volume': q.volume, 'amount': q.amount, 'increase': 0 if not q.lclose else q.close / q.lclose - 1, 'exchange': q.exchange, 'symbol': q.symbol, 'name': Collection.get_name(q.exchange, q.symbol), } for q in qs] # sort if sort: qs = sorted(qs, key=lambda x: x[sort], reverse=order == 'desc') # format for q in qs: if q['lclose']: q['lclose'] = '{:.2f}'.format(q['lclose']) q['open_'] = '{:.2f}'.format(q['open_']) q['high'] = '{:.2f}'.format(q['high']) q['low'] = '{:.2f}'.format(q['low']) q['close'] = '{:.2f}'.format(q['close']) q['amount'] = '{:.1f}万'.format(q['amount'] / 10000) q['increase'] = '{:.1f}%'.format(q['increase'] * 100) # add no result symbols exist_pairs = set((q['exchange'], q['symbol']) for q in qs) for exchange, symbol in (colls - exist_pairs): qs.append({ 'open_': '-', 'high': '-', 'low': '-', 'close': '-', 'lclose': '-', 'volume': '-', 'amount': '-', 'increase': '-', 'exchange': exchange, 'symbol': symbol, 'name': Collection.get_name(exchange, symbol), }) return jsonify(status=200, total=len(qs), rows=qs)
def accounting(user): """ 对账号进行自动记账(如果用户已配置) """ operated_at = datetime.utcnow() + timedelta(hours=8) def add_transaction(type_, exchange, symbol, price, quantity): t = Transaction({ 'user': user._id, 'type_': type_, 'operated_at': operated_at, 'exchange': exchange, 'symbol': symbol, 'price': price, 'quantity': quantity, }) t.save() if not Position.do_op(t): t.remove() if user.auto_accounting: op = Position.user_position(user._id) p2pairs = {} for ta in TradeAccount.query({'user': user._id}): for p in ta.position or []: pair = (ta.exchange, p.symbol) if pair not in p2pairs: p2pairs[pair] = p else: pp = p2pairs[pair] amount = p.average_price * p.quantity + \ pp.average_price * pp.quantity p.quantity += pp.quantity if p.quantity > 0: p.average_price = amount / p.quantity p2pairs[pair] = p p1pairs = {(p1['exchange'], p1['symbol']): p1 for p1 in op} # 检查更改项 for p1 in op: pair = p1['exchange'], p1['symbol'] if pair[0] in ['湖南文交所', '海西文交所', '上海邮币卡', '上海文交所', '中俄邮币卡']: continue if pair in p2pairs: p2 = p2pairs[pair] quantity = p2.quantity - p1['quantity'] if quantity != 0: type_ = 'buy' if quantity > 0 else 'sell' if type_ == 'buy': price = ( p2.average_price * p2.quantity - p1['avg_buy_price'] * p1['quantity']) / quantity else: price = p2.price if price < 0.01: price = Quote.latest_price(p1['exchange'], p1['symbol']) price = int(price * 100) / 100. add_transaction(type_, pair[0], pair[1], price, abs(quantity)) else: # 按现价计算已卖出 if p1['quantity'] > 0: price = Quote.latest_price(p1['exchange'], p1['symbol']) price = int(price * 100) / 100. add_transaction('sell', pair[0], pair[1], price, p1['quantity']) # 检查新增项 for pair, p2 in p2pairs.items(): if pair[0] in ['湖南文交所', '海西文交所', '上海邮币卡', '上海文交所', '中俄邮币卡']: continue if pair not in p1pairs and p2.quantity > 0: price = int(p2.average_price * 100) / 100. add_transaction('buy', pair[0], pair[1], price, p2.quantity)
def trade_quote_history(): """ K线数据 """ exchange = request.args.get('exchange', '') symbol = request.args.get('symbol', '') c = Collection.query_one({'exchange': exchange, 'symbol': symbol}) period = request.args.get('period', '1d') # chart1 qs = list( Quote.cached(3600).query({ 'exchange': exchange, 'symbol': symbol, 'quote_type': period })) name = '{}({}_{})'.format(Collection.get_name(exchange, symbol), exchange, symbol) xdata = [q.quote_at.strftime('%Y/%m/%d') for q in qs] sdata = [( q.open_, q.close, q.low, q.high, ) for q in qs] adata = [int(q.amount / 10000) for q in qs] num_prefix = 100 - len(xdata) if num_prefix > 0: xdata = xdata + [''] * num_prefix sdata = sdata + [()] * num_prefix adata = adata + [0] * num_prefix option1 = { 'title': { 'text': name }, 'xAxis': [{ 'type': 'category', 'boundaryGap': True, 'axisTick': { 'onGap': False }, 'splitLine': { 'show': False }, 'data': xdata, }], 'legend': { 'data': [name, '成交额(万)'] }, 'dataZoom': { 'show': False, 'realtime': True, 'start': (len(adata) - 100) * 100 // len(adata), 'end': 100 }, 'series': [ { 'name': '{}({}_{})'.format(c.name, c.exchange, c.symbol), 'type': 'k', 'data': sdata, }, { 'name': '成交额(万)', 'type': 'bar', 'symbol': 'none', 'data': [], }, ], } # chart2 option2 = { 'title': { 'text': '', }, 'toolbox': { 'y': -30, 'show': True, 'feature': { 'mark': { 'show': True }, 'dataZoom': { 'show': True }, 'dataView': { 'show': True, 'readOnly': False }, 'magicType': { 'show': True, 'type': ['line', 'bar'] }, 'restore': { 'show': True }, 'saveAsImage': { 'show': True } } }, 'tooltip': { 'trigger': 'axis', 'showDelay': 0, }, 'legend': { 'y': -30, 'data': ['成交额(万)'], }, 'xAxis': [{ 'type': 'category', 'position': 'top', 'boundaryGap': True, 'axisLabel': { 'show': False }, 'axisTick': { 'onGap': False }, 'splitLine': { 'show': False }, 'data': xdata, }], 'yAxis': { 'splitNumber': 3, }, 'series': [{ 'name': '成交额(万)', 'type': 'bar', 'symbol': 'none', 'data': adata, }], 'grid': { 'x': 80, 'y': 5, 'x2': 20, 'y2': 40 }, 'dataZoom': { 'show': True, 'realtime': True, 'start': (len(adata) - 100) * 100 // len(adata), 'end': 100 }, } return jsonify(status=200, option1=option1, option2=option2)
def trade_quote_realtime(): """ 实时数据/列表 """ # 全部 -> all # 指数 -> index # 持仓 -> position # 自选 -> diy category = request.args.get('category', '').strip() search = request.args.get('search', '').strip() sort = request.args.get('sort', '').strip() order = request.args.get('order', 'asc').strip() today = Quote.cached(3600).query_one({ 'quote_type': '1d' }, sort=[('quote_at', -1)]).quote_at cond = {'quote_type': '1d', 'quote_at': today} colls = set() symbols = [] if search: pairs = Collection.search(search) symbols = [p[1] for p in pairs] colls = set(pairs) cond['symbol'] = {'$in': symbols} elif category == 'all': pass elif category == 'index': cs = list(Collection.cached(3600).query({'name': {'$regex': '指数$'}})) colls = set((c.exchange, c.symbol) for c in cs) symbols = [c.symbol for c in cs] cond['symbol'] = {'$in': symbols} elif category == 'position': position = Position.user_position(current_user._id) colls = set((p['exchange'], p['symbol']) for p in position) symbols = [p['symbol'] for p in position] cond['symbol'] = {'$in': symbols} elif category == 'diy': raise NotImplementedError qs = [ q for q in Quote.query(cond) if (not colls) or ((q.exchange, q.symbol) in colls) ] qs = [{ 'open_': q.open_, 'high': q.high, 'low': q.low, 'close': q.close, 'lclose': q.lclose, 'volume': q.volume, 'amount': q.amount, 'increase': 0 if not q.lclose else q.close / q.lclose - 1, 'exchange': q.exchange, 'symbol': q.symbol, 'name': Collection.get_name(q.exchange, q.symbol), } for q in qs] # sort if sort: qs = sorted(qs, key=lambda x: x[sort], reverse=order == 'desc') # format for q in qs: if q['lclose']: q['lclose'] = '{:.2f}'.format(q['lclose']) q['open_'] = '{:.2f}'.format(q['open_']) q['high'] = '{:.2f}'.format(q['high']) q['low'] = '{:.2f}'.format(q['low']) q['close'] = '{:.2f}'.format(q['close']) q['amount'] = '{:.1f}万'.format(q['amount'] / 10000) q['increase'] = '{:.1f}%'.format(q['increase'] * 100) # add no result symbols exist_pairs = set((q['exchange'], q['symbol']) for q in qs) for exchange, symbol in (colls - exist_pairs): qs.append({ 'open_': '-', 'high': '-', 'low': '-', 'close': '-', 'lclose': '-', 'volume': '-', 'amount': '-', 'increase': '-', 'exchange': exchange, 'symbol': symbol, 'name': Collection.get_name(exchange, symbol), }) return jsonify(status=200, total=len(qs), rows=qs)
def open_at(exchange): q = Quote.query_one({"exchange": exchange}, sort=[("quote_at", 1)]) d = q.quote_at if q else None return d.strftime("%Y年%m月") if d else "尚未"
def open_at(exchange): q = Quote.query_one({'exchange': exchange}, sort=[('quote_at', 1)]) d = q.quote_at if q else None return d.strftime('%Y年%m月') if d else '尚未'