Example #1
0
class BankAccount(EmbeddedDocument):
    """ 银行账号 """

    bank = StringField(required=True)  # 建设银行/...
    number = StringField(required=True)
    front = BinaryField()
    back = BinaryField()
Example #2
0
class User(Document):
    """ 跌零用户 """
    class Meta:
        idf = IDFormatter('{mobile}')
        idx1 = Index('mobile', unique=True)
        idx2 = Index('username')

    mobile = StringField(required=True)
    username = StringField(required=True)
    password = StringField(required=True)
    paid = FloatField(default=0)
    total_money = FloatField(default=0)
    total_capital = FloatField(default=0)
    total_profit = FloatField(default=0)
    _is_admin = BooleanField(default=False)

    def get_id(self):
        return self._id

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def is_authenticated(self):
        return True

    def is_admin(self):
        return self._is_admin
Example #3
0
class Transaction(Document):
    """ 用户交易信息 """
    class Meta:
        idx1 = Index(['user', 'type_', 'exchange', 'symbol'])
        idx2 = Index(['user', 'operated_at'])

    exchange = StringField(required=True)  # 交易所ID(简称)
    symbol = StringField(required=True)  # 交易代码

    user = StringField(required=True)
    type_ = StringField(required=True)  # buy/sell
    price = FloatField(required=True)
    quantity = IntField(required=True)
    operated_at = DateTimeField(created=True)

    @classmethod
    def user_total_transactions(cls, user):
        """ 用户总操作次数 """
        return cls.count({'user': user})

    @classmethod
    def user_recent_transactions(cls, user, offset=0, limit=None):
        """ 用户最近的操作 """
        qs = cls.query({'user': user}, sort=[('operated_at', -1)])
        if offset:
            qs.skip(offset)
        if limit:
            qs.limit(limit)
        return list(qs)
Example #4
0
class Account(Document):
    """ 跌零用户的账号 """
    class Meta:
        idf = IDFormatter('{user_id}_{login_name}')

    user_id = StringField(required=True)
    login_name = StringField(required=True)
    login_password = StringField(required=True)
Example #5
0
class MyPosition(EmbeddedDocument):
    """ 持仓 """
    name = StringField(required=True)
    symbol = StringField(required=True)
    average_price = FloatField(required=True)
    quantity = IntField(required=True)
    price = FloatField(required=True)
    sellable = IntField()
    profit = FloatField()
Example #6
0
class Order(EmbeddedDocument):
    """ 成交订单汇总 """
    type_ = StringField(required=True)
    name = StringField(required=True)
    symbol = StringField(required=True)
    price = FloatField(required=True)  # 成本价
    current_price = FloatField(required=True)  # 成交价
    quantity = IntField(required=True)
    commision = FloatField(required=True)
    profit = FloatField(required=True)
Example #7
0
class Investor(Document):
    """ 投资人 """
    class Meta:
        idf = IDFormatter('{id_number}')
        idx1 = Index(['user', 'order'], unique=True)

    user = StringField(required=True)  # 用户
    order = IntField(required=True)  # 顺序
    name = StringField(required=True)
    id_type = StringField(required=True)  # 身份证/..
    id_number = StringField(required=True)  # 号码
    id_front = BinaryField()  # 照片
    id_back = BinaryField()
    mobile = StringField(required=True)
    province = StringField(required=True)
    city = StringField(required=True)
    address = StringField(required=True)
    bank_accounts = ListField(EmbeddedField(BankAccount))

    @classmethod
    def get_user_order(cls, user):
        i = cls.query_one({'user': user}, sort=[('order', -1)], limit=1)
        if not i:
            return 1
        else:
            return i.order + 1

    @classmethod
    def user_investors(cls, user):
        return list(cls.query({'user': user}, sort=[('order', 1)]))
Example #8
0
File: wechat.py Project: maocis/ybk
class WechatEvent(Document):
    """ 微信事件

    暂时只保存, 不处理
    """
    xml = StringField()  # xml格式数据主题
    updated_at = DateTimeField(modified=True)
Example #9
0
File: wechat.py Project: maocis/ybk
class WechatAccessToken(Document):
    """ 微信ACCESS_TOKEN """
    class Meta:
        idf = IDFormatter('{access_token}')
        idx1 = Index('expires_at', expireAfterSeconds=7200)

    access_token = StringField(required=True)
    expires_in = IntField()
    expires_at = DateTimeField(required=True)
    updated_at = DateTimeField(modified=True)

    @classmethod
    def get_access_token(cls):
        instance = cls.query_one()
        if instance:
            return instance['access_token']
        else:
            appid = ybk.config.conf.get('wechat_appid')
            appsecret = ybk.config.conf.get('wechat_appsecret')
            grant_type = 'client_credential'
            url = 'https://api.weixin.qq.com/cgi-bin/token'
            params = {
                'grant_type': grant_type,
                'appid': appid,
                'secret': appsecret,
            }
            j = requests.get(url, params=params, timeout=(3, 7)).json()
            expires_at = datetime.utcnow() + timedelta(seconds=j['expires_in'])
            cls({
                'access_token': j['access_token'],
                'expires_in': j['expires_in'],
                'expires_at': expires_at
            }).save()
            return j['access_token']
Example #10
0
class MyPosition(EmbeddedDocument):
    """ 持仓汇总 """

    name = StringField(required=True)
    symbol = StringField(required=True)
    average_price = FloatField(required=True)
    quantity = IntField(required=True)
    price = FloatField(required=True)
    sellable = IntField()
    profit = FloatField()

    @property
    def increase(self):
        if self.price > 0:
            return '{:4.2f}%'.format(
                (self.price / self.average_price - 1) * 100)
        else:
            return '0%'
Example #11
0
class Position(Document):
    """ 当日持仓汇总 """
    class Meta:
        idf = IDFormatter('{user_id}_{date}')
        idx1 = Index(['user_id', 'date'], unique=True)

    user_id = StringField(required=True)
    date = DateTimeField(required=True)
    position_list = ListField(EmbeddedField(MyPosition))
Example #12
0
class Announcement(Document):

    """ 公告信息 """
    class Meta:
        idf = IDFormatter('{url}')
        idx1 = Index('url', unique=True)
        idx2 = Index(['updated_at', 'type_'])
        idx3 = Index(['published_at', 'type_'])
        idx4 = Index(['exchange', 'type_'])

    exchange = StringField(required=True)       # 交易所简称(ID)
    type_ = StringField(required=True)          # 申购("offer")/中签("result")
    url = StringField(required=True)            # 公告链接
    title = StringField()                       # 公告标题
    html = StringField()                        # 原始html
    published_at = DateTimeField()              # 交易所发布时间
    updated_at = DateTimeField(modified=True)
    parsed = BooleanField(default=False)
Example #13
0
class Status(Document):
    """ 当日挂单汇总 """
    class Meta:
        idf = IDFormatter('{user_id}_{date}')
        idx1 = Index(['user_id', 'date'], unique=True)

    user_id = StringField(required=True)
    date = DateTimeField(required=True)
    status_list = ListField(EmbeddedField(MyStatus))
Example #14
0
class Code(Document):
    """ 短信验证码 """
    class Meta:
        idx1 = Index(['mobile', 'send_at'])

    mobile = StringField(required=True)
    code = StringField(required=True)
    text = StringField(required=True)
    sent_at = DateTimeField(created=True)

    @classmethod
    def can_create(cls, mobile, type_):
        if not re.compile('^\d{11}$').match(mobile):
            return False, '手机号码格式不正确'

        if type_ == 'register':
            u = User.query_one({'mobile': mobile})
            if u and u.is_active():
                return False, '该手机已注册'

        c = cls.query_one({'mobile': mobile}, sort=[('sent_at', -1)])
        if c and c.sent_at >= datetime.utcnow() - timedelta(seconds=89):
            return False, '发送验证码间隔太频繁'

        return True, ''

    @classmethod
    def create_code(cls, mobile):
        code = '{:06d}'.format(random.randint(0, 999999))
        text = '【{company}】您的验证码是{code}。如非本人操作,请忽略本短信'
        text = text.format(company='邮币卡369', code=code)
        c = cls({'mobile': mobile, 'code': code, 'text': text})
        c.save()
        return c

    @classmethod
    def verify(cls, mobile, code):
        c = cls.query_one({'mobile': mobile}, sort=[('sent_at', -1)])
        if c:
            if c.sent_at < datetime.utcnow() - timedelta(seconds=60 * 15):
                return False, '验证码超时'
            return c.code == code, '验证码(不)匹配'
        return False, '验证码未发送'
Example #15
0
class UserAgent(Document):
    """User-agent
    :param name: UA name
    :param version: UA version
    :param os: os system
    :param hardwaretype: hardware type
    :param popularity: frequency, such as Very common
    :param createat: create time(localtime)
    """
    class Meta:
        idf = IDFormatter(next_id)
        idx1 = Index('name')

    name = StringField(required=True)
    version = StringField()
    os = StringField()
    hardwaretype = StringField()
    popularity = StringField()
    createat = DateTimeField(created=True)
Example #16
0
class Collection(Document):
    class Meta:
        idf = IDFormatter('{exchange}_{symbol}')

    exchange = StringField(required=True)
    symbol = StringField(required=True)
    name = StringField(required=True)
    trade_day = IntField(required=True)  # 现在是开始交易的第几个交易日?
    buy_price = FloatField(default=0, required=True)  # 平均买入价
    quantity = IntField(default=0, required=True)  # 总持有数量
    accounts = ListField(StringField)  # 持有这个品种的账号
    users = ListField(StringField)  # 持有这个品种的用户
    updated_at = DateTimeField(modified=True)

    @property
    def num_users(self):
        return len(self.users)

    @property
    def num_accounts(self):
        return len(self.accounts)
Example #17
0
class Account(Document):
    """ 抢单用户的账号 """
    class Meta:
        idf = IDFormatter('{user}_{exchange}_{login_name}')

    user = StringField(required=True)
    exchange = StringField(required=True)
    login_name = StringField(required=True)
    login_password = StringField(required=True)
    money_password = StringField()
    bank_password = StringField()

    collections = ListField(StringField, default=[])
    position = ListField(EmbeddedField(MyPosition))
    orders = ListField(EmbeddedField(MyOrder))
    order_status = ListField(EmbeddedField(MyStatus))
    money = FloatField()
    capital = FloatField()
    profit = FloatField()
    earned = FloatField()  # 交易收入
    lost = FloatField()  # 交易亏损

    @property
    def mobile(self):
        user = User.cached(60).query_one({'_id': self.user})
        return user.mobile

    @property
    def username(self):
        user = User.cached(60).query_one({'_id': self.user})
        return user.username
Example #18
0
class User(Document):
    """ 抢单用户 """
    class Meta:
        idf = IDFormatter('{mobile}')
        idx1 = Index('mobile', unique=True)
        idx2 = Index('username')

    mobile = StringField(required=True)
    username = StringField(required=True)
    password = StringField(required=True)
    owning = FloatField(default=0)  # 欠款
    paid = FloatField(default=0)  # 已结清
    num_exchanges = IntField()
    num_accounts = IntField()
    profit = FloatField()
    money = FloatField()
    capital = FloatField()
    earned = FloatField()  # 交易收入
    lost = FloatField()  # 交易亏损
    position = ListField(EmbeddedField(MyPosition))
    orders = ListField(EmbeddedField(MyOrder))
    order_status = ListField(EmbeddedField(MyStatus))

    _is_admin = BooleanField()

    def get_id(self):
        return self._id

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def is_authenticated(self):
        return True

    def is_admin(self):
        return self._is_admin
Example #19
0
class Summary(Document):
    """ 各种组合的汇总信息

    总资金/总持仓/总浮盈
    """
    class Meta:
        idf = IDFormatter('{exchange}_{user}_{collection}')
        idx1 = Index('exchange')
        idx2 = Index('user')
        idx3 = Index('collection')

    exchange = StringField(default='', required=True)
    user = StringField(default='', required=True)
    collection = StringField(default='', required=True)

    position = ListField(EmbeddedField(MyPosition))
    orders = ListField(EmbeddedField(MyOrder))
    order_status = ListField(EmbeddedField(MyStatus))
    money = FloatField()
    capital = FloatField()
    profit = FloatField()
    earned = FloatField()  # 交易收入
    lost = FloatField()  # 交易亏损
Example #20
0
class Exchange(Document):
    """ 交易所 """
    class Meta:
        idf = IDFormatter('{name}')

    name = StringField(required=True)
    num_users = IntField()
    num_accounts = IntField()
    position = ListField(EmbeddedField(MyPosition))
    orders = ListField(EmbeddedField(MyOrder))
    order_status = ListField(EmbeddedField(MyStatus))
    money = FloatField()
    capital = FloatField()
    profit = FloatField()
    earned = FloatField()  # 交易收入
    lost = FloatField()  # 交易亏损
Example #21
0
class DailyTrading(Document):
    """ 当日账号汇总 """
    class Meta:
        idf = IDFormatter('{account}_{date}')
        idx1 = Index(['account', 'date'], unique=True)

    date = DateTimeField(required=True)
    account = StringField(required=True)
    position = ListField(EmbeddedField(MyPosition))
    orders = ListField(EmbeddedField(MyOrder))
    order_status = ListField(EmbeddedField(MyStatus))
    profit = FloatField()
    money = FloatField()
    capital = FloatField()
    earned = FloatField()  # 交易收入
    lost = FloatField()  # 交易亏损
Example #22
0
class MyStatus(EmbeddedDocument):
    """ 挂单情况 """

    order = StringField(required=True)
    order_at = StringField(required=True)
    type_ = StringField(required=True)
    name = StringField(required=True)
    symbol = StringField(required=True)
    price = FloatField(required=True)
    quantity = IntField(required=True)
    pending_quantity = IntField(required=True)
    status = StringField(required=True)
Example #23
0
class TradeAccount(Document):
    """ 交易账号 """
    class Meta:
        idf = IDFormatter('{exchange}_{login_name}')
        idx1 = Index(['investor', 'exchange'], unique=True)
        idx2 = Index(['exchange', 'login_name'], unique=True)
        idx3 = Index('user')

    user = StringField(required=True)  # 所属用户
    investor = StringField(required=True)  # 投资人
    bank = StringField(required=True)  # 工商银行/...
    exchange = StringField(required=True)  # 交易所简称
    login_name = StringField(required=True)  # 账号
    login_password = StringField(required=False)  # 密码
    money_password = StringField(required=False)  # 资金密码

    verify_message = StringField(default='请先更新状态', required=False)  # 验证失败的原因
    verified = BooleanField(default=False)  # 验证通过
    grab_buy_on = BooleanField(default=False)  # 抢单买
    grab_sell_on = BooleanField(default=False)  # 抢单卖

    position = ListField(EmbeddedField(MyPosition), default=[])
    money = EmbeddedField(MyMoney, default={})
    orders = ListField(EmbeddedField(Order), default=[])
    order_status = ListField(EmbeddedField(OrderStatus), default=[])

    @classmethod
    def user_accounts(cls, user):
        return list(cls.query({'user': user}))

    @property
    def investor_name(self):
        investors = list(Investor.cached(5).query({'user': self.user}))
        for i in investors:
            if i._id == self.investor:
                return i.name
        else:
            return '未找到'

    @property
    def money_position(self):
        return sum(p.price * p.quantity for p in self.position)
Example #24
0
class Position(Document):
    """ 用户持仓信息 """
    class Meta:
        idx1 = Index(['user', 'exchange', 'symbol'], unique=True)

    exchange = StringField(required=True)  # 交易所ID(简称)
    symbol = StringField(required=True)  # 交易代码

    user = StringField(required=True)
    quantity = FloatField(required=True, default=0)

    @classmethod
    def num_exchanges(cls, user):
        """ 用户持有多少个交易所的持仓 """
        return len(
            set(p.exchange
                for p in cls.query({'user': user}, {'exchange': 1})))

    @classmethod
    def num_collections(cls, user):
        """ 用户持有多少不同的藏品 """
        return len(
            list(cls.query({
                'user': user,
                'quantity': {
                    '$gt': 0
                }
            }, {'_id': 1})))

    @classmethod
    def num_sold(cls, user):
        """ 用户已卖出过多少个藏品 """
        return len(
            set('{exchange}_{symbol}'.format(**t)
                for t in Transaction.find({
                    'user': user,
                    'type_': 'sell'
                }, {
                    'exchange': 1,
                    'symbol': 1
                })))

    @classmethod
    def user_position(cls, user):
        """ 目前持仓概况, cached """
        cachekey = 'pcache_{}'.format(user)
        if not hasattr(cls, cachekey):
            setattr(cls, cachekey, {})

        now = time.time()
        cache = getattr(cls, cachekey)
        if 'position' not in cache or \
                cache.get('time', now) < now - 5:
            cache['position'] = cls._user_position(user)
            cache['time'] = time.time()
        return copy.deepcopy(cache['position'])

    @classmethod
    def _user_position(cls, user):
        """ 目前持仓概况 """
        collections = {}
        for p in cls.query({'user': user}):
            pair = (p.exchange, p.symbol)
            collections.setdefault(pair, 0)
            collections[pair] += p.quantity
        buy_info = {}
        for t in Transaction.query({
                'user': user,
                'type_': 'buy'
        },
                                   sort=[('operated_at', 1)]):
            pair = (t.exchange, t.symbol)
            if pair in collections:
                buy_info.setdefault(pair, [0, 0, None])
                buy_info[pair][0] += t.quantity
                buy_info[pair][1] += t.quantity * t.price
                if not buy_info[pair][2]:
                    buy_info[pair][2] = t.operated_at
        sell_info = {}
        for t in Transaction.query({
                'user': user,
                'type_': 'sell'
        },
                                   sort=[('operated_at', -1)]):
            pair = (t.exchange, t.symbol)
            if pair in collections:
                sell_info.setdefault(pair, [0, 0, None])
                sell_info[pair][0] += t.quantity
                sell_info[pair][1] += t.quantity * t.price
                if not sell_info[pair][2]:
                    sell_info[pair][2] = t.operated_at
        position = []
        for pair, quantity_amount in buy_info.items():
            exchange, symbol = pair
            quantity, amount, first_buy_at = quantity_amount
            if quantity:
                avg_buy_price = amount / quantity
                realized_profit = 0
                last_sell_at = datetime.utcnow()
                if pair in sell_info:
                    quantity2, amount2, last_sell_at = sell_info[pair]
                    quantity -= quantity2
                    realized_profit = amount2 - avg_buy_price * quantity2

                if quantity != collections[pair]:
                    cls.update_one(
                        {
                            'exchange': pair[0],
                            'symbol': pair[1],
                            'user': user
                        }, {'$set': {
                            'quantity': quantity
                        }})
                latest_price = Quote.latest_price(exchange, symbol)
                increase = Quote.increase(exchange, symbol)
                if not latest_price:
                    latest_price = avg_buy_price
                unrealized_profit = (latest_price - avg_buy_price) * quantity
                past_days = max(1, (last_sell_at - first_buy_at).days)
                annual_profit = (365 / past_days) * \
                    (unrealized_profit + realized_profit)
                position.append({
                    'exchange':
                    exchange,
                    'symbol':
                    symbol,
                    'name':
                    Collection.get_name(exchange, symbol),
                    'avg_buy_price':
                    avg_buy_price,
                    'quantity':
                    quantity,
                    'latest_price':
                    latest_price,
                    'increase':
                    increase,
                    'total_increase':
                    latest_price / avg_buy_price -
                    1 if avg_buy_price > 0 else 100,
                    'realized_profit':
                    realized_profit,
                    'unrealized_profit':
                    unrealized_profit,
                    'annual_profit':
                    annual_profit,
                })
        return sorted(position, key=lambda x: x['exchange'])

    @classmethod
    def average_increase(cls, user):
        """ 平均涨幅 """
        position = [p for p in cls.user_position(user) if p['quantity'] > 0]
        if len(position):
            return sum(p['total_increase'] for p in position) / len(position)

    @classmethod
    def market_value(cls, user):
        """ 持仓总市值 """
        position = cls.user_position(user)
        return sum(p['latest_price'] * p['quantity'] for p in position)

    @classmethod
    def unrealized_profit(cls, user):
        """ 未实现收益(浮盈) """
        return sum(p['unrealized_profit'] for p in cls.user_position(user))

    @classmethod
    def realized_profit(cls, user):
        """ 已实现收益 """
        return sum(p['realized_profit'] for p in cls.user_position(user))

    @classmethod
    def annual_profit(cls, user):
        """ 年化收益 """
        return sum(p['annual_profit'] for p in cls.user_position(user))

    @classmethod
    def do_op(cls, t, reverse=False):
        c = cls.query_one({
            'user': t.user,
            'exchange': t.exchange,
            'symbol': t.symbol
        })
        if not c:
            if reverse:
                return False
            elif t.type_ == 'buy':
                c = cls({
                    'user': t.user,
                    'exchange': t.exchange,
                    'symbol': t.symbol,
                    'quantity': t.quantity
                })
            else:
                return False
        elif t.type_ == 'buy':
            c.quantity += t.quantity
        elif t.type_ == 'sell':
            c.quantity -= t.quantity

        if c.quantity >= 0:
            c.upsert()
            return True
        else:
            return False
Example #25
0
class Collection(Document):

    """ 收藏品: Stamp/Coin/Card """
    class Meta:
        idf = IDFormatter('{exchange}_{symbol}')
        idx1 = Index(['exchange', 'symbol'], unique=True)
        idx2 = Index('from_url')

    from_url = StringField()        # 来自哪个公告
    exchange = StringField(required=True)        # 交易所ID(简称)
    symbol = StringField(required=True)          # 交易代码
    name = StringField()                        # 交易名
    type_ = StringField(default="邮票")           # "邮票"/"钱币"/"卡片"
    status = StringField(default="申购中")          # "申购中"/"已上市"

    issuer = StringField()          # 发行机构
    texture = StringField()         # 材质
    price_forsale = FloatField()    # 挂牌参考价
    quantity_all = IntField()       # 挂牌总数量
    quantity_forsale = IntField()   # 限售总数

    offer_fee = FloatField(default=0.001)        # 申购手续费
    offer_quantity = IntField()     # 供申购数量 *
    offer_price = FloatField()      # 申购价格 *
    offer_accmax = IntField()       # 单账户最大中签数 *
    offer_overbuy = BooleanField()  # 是否可超额申购 *
    offer_cash_ratio = FloatField()  # 资金配售比例 *

    change_min = FloatField()       # 最小价格变动单位
    change_limit_1 = FloatField()   # 首日涨跌幅
    change_limit = FloatField()     # 正常日涨跌幅
    pickup_min = IntField()         # 最小提货量
    trade_limit = FloatField()      # 单笔最大下单量

    offers_at = DateTimeField()     # 申购日 *
    draws_at = DateTimeField()      # 抽签日 *
    trades_at = DateTimeField()     # 上市交易日 *

    invest_mv = FloatField()       # 申购市值(Market Value)
    invest_cash = FloatField()     # 申购资金 *
    invest_cash_return_ratio = FloatField()   # 资金中签率, 和上面那个二选一 *

    updated_at = DateTimeField(modified=True)

    @classmethod
    def get_name(cls, exchange, symbol):
        if not hasattr(cls, 'cache'):
            setattr(cls, 'cache', {})
        pair = (exchange, symbol)
        if not cls.cache or cls.cache.get('time', time.time()) < 3600:
            cls.cache = {(c.exchange, c.symbol): c.name
                         for c in cls.query({},
                                            {'exchange': 1,
                                             'symbol': 1,
                                             'name': 1,
                                             '_id': 0})}
            cls.cache['time'] = time.time()
        return cls.cache.get(pair)

    @classmethod
    def search(cls, name_or_abbr, limit=100):
        # warm cache
        cls.get_name('', '')
        na = name_or_abbr.upper()
        pairs = []
        exchanges = set()

        def add_name(name, pair):
            initials = py.get_initials(name, '')
            sna = set(na)
            sname = set(name)
            sinit = set(initials)

            if name.startswith(na) or initials.startswith(na):
                distance = min(max(len(sna - sname), len(sname - sna)),
                               max(len(sna - sinit), len(sinit - sna)))
                pairs.append((distance, pair))

        def add_exchange(name):
            initials = py.get_initials(name, '')
            sna = set(na)
            sname = set(name)
            sinit = set(initials)

            if name.startswith(na) or initials.startswith(na):
                distance = min(max(len(sna - sname), len(sname - sna)),
                               max(len(sna - sinit), len(sinit - sna)))
                for pair in cls.cache.keys():
                    if pair[0] == name:
                        pairs.append((distance, pair))

        for pair in cls.cache.keys():
            if pair == 'time':
                continue
            exchanges.add(pair[0])
            name = cls.get_name(*pair)
            add_name(name, pair)

        for name in exchanges:
            add_exchange(name)

        return [p[1] for p in sorted(pairs)][:limit]

    @property
    def abbr(self):
        return py.get_initials(self.name, '')

    @property
    def offer_mv(self):
        """ 申购市值配额 """
        if self.offer_quantity:
            return self.offer_quantity * self.offer_price * \
                (1 - self.offer_cash_ratio)

    @property
    def offer_cash(self):
        """ 申购资金配额 """
        try:
            return self.offer_quantity * self.offer_price * \
                self.offer_cash_ratio
        except:
            pass

    @property
    def offer_max_invest(self):
        """ 最大申购资金 """
        if self.offer_overbuy:
            return float('inf')
        else:
            return self.offer_mv + \
                self.offer_fee * self.offer_accmax

    @property
    def invest_cash_real(self):
        """ 申购资金(含估算) """
        if self.invest_cash:
            return self.invest_cash
        elif self.invest_cash_return_ratio and self.offer_cash:
            return self.offer_cash / self.invest_cash_return_ratio

    @property
    def result_ratio_cash(self):
        """ 资金中签率 """
        if self.invest_cash_return_ratio:
            return self.invest_cash_return_ratio
        if self.status == "已上市" and self.invest_cash:
            try:
                return self.offer_cash / self.invest_cash
            except:
                pass

    @property
    def result_ratio_mv(self):
        """ 市值中签率 """
        if self.status == "已上市" and self.invest_mv:
            return self.offer_mv * (1 - self.offer_cash_ratio) \
                / self.invest_mv

    @property
    def offer_min_invest(self):
        """ 必中最低申购资金 """
        if self.status == "已上市":
            if self.invest_cash or self.invest_cash_return_ratio:
                magnitude = math.ceil(math.log10(1 / self.result_ratio_cash))
                return self.offer_price * 10 ** magnitude \
                    * (1 + self.offer_fee)

    @property
    def cashout_at(self):
        """ 出金日期 """
        c = get_conf(self.exchange)
        return self.offset(c['cashout'])

    def offset(self, incr):
        c = get_conf(self.exchange)
        notrade = [int(x) for x in str(c['notrade'] or '').split(',') if x]
        delta = 1 if incr > 0 else -1
        odate = self.offers_at
        while incr != 0:
            incr -= delta
            odate += timedelta(days=delta)
            while odate.weekday() in notrade:
                odate += timedelta(days=delta)
        return odate

    @property
    def total_offer_cash(self):
        return sum(c.offer_cash or 0
                   for c in Collection.cached(300)
                   .query({'exchange': self.exchange,
                           'offers_at': self.offers_at}))

    @property
    def expected_invest_cash(self):
        ex = Exchange.find_exchange(self.exchange)
        if ex.expected_invest_cash and self.total_offer_cash:
            return (self.offer_cash or 0) / self.total_offer_cash \
                * ex.expected_invest_cash

    @property
    def expected_invest_mv(self):
        ex = Exchange.find_exchange(self.exchange)
        if ex.total_market_value:
            return ex.total_market_value

    @property
    def expected_result_mv_ratio(self):
        """ 预期市值中签率 """
        if not self.result_ratio_cash:
            if self.expected_invest_mv and self.offer_mv:
                return self.offer_mv / self.expected_invest_mv

    @property
    def expected_annual_profit(self):
        """ 预期年化收益率 """
        ex = Exchange.find_exchange(self.exchange)
        if ex.expected_invest_cash and ex.median_increase:
            return self.expected_result_cash_ratio * ex.median_increase

    @property
    def expected_result_cash_ratio(self):
        """ 预期资金中签率 """
        if not self.result_ratio_cash:
            ex = Exchange.find_exchange(self.exchange)
            if ex.expected_invest_cash:
                return self.total_offer_cash / ex.expected_invest_cash
        else:
            return self.result_ratio_cash
Example #26
0
class Exchange(Document):

    """ 交易所 """
    class Meta:
        idf = IDFormatter('{abbr}')
        idx1 = Index('abbr', unique=True)

    name = StringField(required=True)
    abbr = StringField(required=True)  # 简称
    url = StringField(required=True)
    updated_at = DateTimeField(modified=True)

    @classmethod
    def _all(cls):
        return list(Exchange.cached(300).query())

    @classmethod
    def find_exchange(cls, exchange):
        for ex in cls._all():
            if ex.abbr == exchange:
                return ex

    @cached_property_ttl(300)
    def num_collections(self):
        """ 交易品种数 """
        return Collection.count({'exchange': self.abbr})

    @cached_property_ttl(300)
    def offers_per_month(self):
        """ 月均申购次数 """
        months = set()
        days = set()
        for c in Collection.cached(300).query({'exchange': self.abbr}):
            if c.offers_at:
                days.add(c.offers_at.strftime('%Y%m%d'))
                months.add(c.offers_at.strftime('%Y%m'))
        if len(days) > 0:
            return len(days) / len(months)

    @cached_property_ttl(300)
    def average_increase(self):
        """ 平均涨幅 """
        result = 0
        count = 0
        for c in Collection.cached(300).query({'exchange': self.abbr}):
            lprice = Quote.latest_price(c.exchange, c.symbol)
            price = c.offer_price
            if lprice and price:
                result += lprice / price - 1
                count += 1
        if count > 0:
            return result / count

    @cached_property_ttl(300)
    def median_increase(self):
        """ 中位数涨幅(半个月-3个月新品) """
        incs = []
        for c in Collection.cached(300).query({'exchange': self.abbr}):
            if offers_between(c, 15, 90):
                lprice = Quote.latest_price(c.exchange, c.symbol)
                price = c.offer_price
                if lprice and price:
                    incs.append(lprice / price - 1)
        if incs:
            return sorted(incs)[len(incs) // 2]

    @cached_property_ttl(300)
    def average_trading_days(self):
        """ 平均上市时间 """
        days = []
        for c in Collection.cached(300).query({'exchange': self.abbr}):
            quotes = Quote.cached(300).query({'exchange': c.exchange,
                                              'symbol': c.symbol,
                                              'quote_type': '1d'})
            days.append(len(quotes))
        if len(days) > 0:
            return sum(days) / len(days)

    def _get_colls_by_day(self):
        colls = Collection.cached(300).query({'exchange': self.abbr})
        colls = [c for c in colls if c.offers_at]
        colls = sorted(colls,
                       key=lambda x: x.offers_at,
                       reverse=False)
        return groupby(colls, lambda x: x.offers_at)

    @cached_property_ttl(300)
    def invest_cash_history(self):
        """ 申购资金历史记录 """
        history = []
        for offers_at, colls in self._get_colls_by_day():
            colls = list(colls)
            invest_cash = sum(c.invest_cash_real or 0
                              for c in colls)
            if invest_cash:
                history.append({
                    'date': colls[0].offers_at,
                    'invest_cash': invest_cash,
                    'total_cash': sum(c.invest_cash_real or 0
                                      for c in Collection.cached(300)
                                      .query({'offers_at':
                                              colls[0].offers_at}))
                })
        return history

    @cached_property_ttl(300)
    def offer_cash_history(self):
        """ 申购资金市值历史记录 """
        history = []
        for offers_at, colls in self._get_colls_by_day():
            colls = list(colls)
            offer_cash = sum(c.offer_cash or 0
                             for c in colls)
            if offer_cash:
                history.append({
                    'date': colls[0].offers_at,
                    'offer_cash': offer_cash,
                })
        return history

    @cached_property_ttl(300)
    def result_ratio_history(self):
        """ 中签率历史记录 """
        history = []
        for offers_at, colls in self._get_colls_by_day():
            colls = list(colls)
            total_cash = sum(c.offer_cash or 0
                             for c in colls)
            total_invest = sum(c.invest_cash_real or 0
                               for c in colls)
            if total_invest:
                result_ratio = total_cash / total_invest
                history.append({
                    'date': colls[0].offers_at,
                    'result_ratio': result_ratio,
                })
        return history

    @cached_property_ttl(300)
    def increase_history(self):
        """ 涨幅的历史记录 """
        colls = Collection.cached(300).query({'exchange': self.abbr})

        symbols = [c.symbol for c in colls
                   if offers_between(c, 15, 90)]
        hdict = defaultdict(list)
        for q in Quote.cached(300).query({'exchange': self.abbr,
                                          'symbol': {'$in': symbols},
                                          'quote_type': '1d'},
                                         sort=[('quote_at', 1)]):
            if q.close:
                hdict[q.symbol].append(q.close)
        for symbol, values in hdict.items():
            for i in range(1, len(values)):
                values[i] = values[i] / values[0] * 1.3 - 1
            values[0] = 0.3
        return hdict

    @cached_property_ttl(300)
    def average_result_cash_ratio(self):
        """ 近3次平均中签率 """
        total_invest = 0
        total_offer = 0
        for hi, ho in zip(self.invest_cash_history[-3:],
                          self.offer_cash_history[-3:]):
            total_invest += hi['invest_cash'] or 0
            total_offer += ho['offer_cash'] or 0
        if total_invest > 0 and total_offer > 0:
            return total_offer / total_invest

    @cached_property_ttl(300)
    def average_invest_cash(self):
        """ 平均申购资金 """
        total_invest = 0
        count = 0
        for h in self.invest_cash_history[-3:]:
            total_invest += h['invest_cash'] or 0
            count += 1
        if count > 0 and total_invest > 0:
            return total_invest / 1e8 / count

    @cached_property_ttl(300)
    def expected_invest_cash(self):
        if not self.invest_cash_history:
            return None

        exp = 0
        for i, h in enumerate(reversed(self.invest_cash_history[-3:])):
            exp += 0.7 * (0.3 ** i) * h['invest_cash']
        exp += 0.3 * (0.3 ** i) * h['invest_cash']
        return exp

    @cached_property_ttl(300)
    def rating(self):
        """ 评分 """
        if self.average_result_cash_ratio and self.median_increase:
            return int(self.average_result_cash_ratio *
                       self.median_increase / 0.003)

    @cached_property_ttl(300)
    def total_market_value(self):
        mv = 0
        for c in Collection.query({'exchange': self.abbr}):
            lprice = Quote.latest_price(c.exchange, c.symbol)
            if c.offer_quantity and lprice:
                mv += c.offer_quantity * lprice * 10
        return mv
Example #27
0
class Quote(Document):
    """ 交易行情信息 """
    class Meta:
        idf = IDFormatter('{exchange}_{symbol}_{quote_type}_{quote_at}')
        idx1 = Index(['exchange', 'symbol', 'quote_type', 'quote_at'],
                     unique=True)
        idx2 = Index(['exchange', 'quote_at'])

    exchange = StringField(required=True)  # 交易所ID(简称)
    symbol = StringField(required=True)  # 交易代码

    # 1m/5m/15m/30m/1h/4h/1d/1w/1m
    quote_type = StringField(required=True)
    quote_at = DateTimeField(required=True)  # 交易时间

    lclose = FloatField()  # 上次收盘价
    open_ = FloatField(required=True)  # 周期开盘价
    high = FloatField(required=True)  # 周期最高价
    low = FloatField(required=True)  # 周期最低价
    close = FloatField(required=True)  # 周期收盘价
    mean = FloatField()  # 周期均价
    volume = IntField(required=True)  # 周期成交量
    amount = FloatField(required=True)  # 周期成交额

    @classmethod
    def latest_price(cls, exchange, symbol):
        """ 获得品种的最新成交价

        从日线数据中取就可以了, 实时交易价格也会保存在日线中
        """
        today = datetime.utcnow() + timedelta(hours=8)
        today = today.replace(hour=0, minute=0, second=0, microsecond=0)
        q = cls.cached(300).query_one(
            {
                'exchange': exchange,
                'symbol': symbol,
                'quote_type': '1d',
                'quote_at': {
                    '$lte': today
                }
            }, {'close': 1},
            sort=[('quote_at', -1)])
        if q:
            return q.close

    @classmethod
    def increase(cls, exchange, symbol):
        """ 获得品种的今日涨幅 """
        now = datetime.utcnow() + timedelta(hours=8)
        if now.hour < 9 and now.minute < 30:
            now -= timedelta(days=1)
        date = now.replace(hour=0, minute=0, second=0, microsecond=0)
        q = cls.cached(300).query_one(
            {
                'exchange': exchange,
                'symbol': symbol,
                'quote_type': '1d',
                'quote_at': date
            }, {
                'close': 1,
                'lclose': 1
            })
        if q and q.lclose:
            return q.close / q.lclose - 1
Example #28
0
class User(Document):
    """ 用户 """
    class Meta:
        idf = IDFormatter('{mobile}')
        idx1 = Index('mobile', unique=True)
        idx2 = Index('_is_admin')
        idx3 = Index('_is_active')
        idx4 = Index('username', unique=True)

    mobile = StringField(required=True)
    username = StringField()
    password = StringField()  # bcrypt hashed
    invited_by = StringField()  # 邀请人id
    ymoney = IntField(default=1000)  # Y币
    reserved_ymoney = IntField(default=0)  # 预扣Y币
    created_at = DateTimeField(created=True)
    last_login_at = DateTimeField(modified=True)
    auto_accounting = BooleanField(default=False)

    _is_active = BooleanField(default=False)  # 通过验证
    _is_admin = BooleanField(default=False)

    def add_to_admin(self):
        self._is_admin = True
        self.upsert()
        return True

    def activate(self):
        self._is_active = True
        self.upsert()

    def is_admin(self):
        return self._is_admin

    def is_active(self):
        return self._is_active

    def is_authenticated(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        return self.mobile

    @classmethod
    def check_available(cls, mobile=None, username=None):
        u1 = cls.query_one({'mobile': mobile})
        u2 = cls.query_one({'username': username})
        if u1 or u2:
            return False
        else:
            return True

    @classmethod
    def create_user(cls, mobile, username, password, invited_by):
        if not cls.find_one({'_id': invited_by}):
            raise ValueError('请输入正确的邀请人手机号码')
        if not cls.check_available(mobile, username):
            raise ValueError('手机号码或用户名已被使用')
        u = cls({
            'mobile': mobile,
            'username': username,
            'password': cls.create_password(password),
            'invited_by': invited_by,
        })
        u.save()
        return u

    @classmethod
    def create_password(cls, password):
        return bcrypt.hashpw(password.encode('utf-8'),
                             bcrypt.gensalt()).decode('utf-8')

    def change_password(self, password):
        self.password = self.create_password(password)
        self.upsert()

    @classmethod
    def check_login(cls, mobile, password):
        u = cls.query_one({'mobile': mobile})
        password = password.encode('utf-8')
        if u:
            hashed = u.password.encode('utf-8')
            if bcrypt.hashpw(password, hashed) == hashed:
                return u
        return None
Example #29
0
class ProfitLog(Document):
    """ 用户的收益日志 """
    class Meta:
        idx1 = Index(['user', 'date'], unique=True)

    user = StringField(required=True)
    date = DateTimeField(required=True)
    profit = FloatField()

    @classmethod
    def profits(cls, user):
        """ 获得收益日志 """
        return [{
            'date': pl.date,
            'profit': pl.profit
        } for pl in cls.query({'user': user}, sort=[('date', 1)])]

    @classmethod
    def ensure_all_profits(cls):
        for u in User.query():
            cls.ensure_profits(u._id)

    @classmethod
    def ensure_profits(cls, user):
        """ 确保生成收益日志 """
        log.info('为用户{}生成收益日志'.format(user))
        ts = list(reversed(Transaction.user_recent_transactions(user)))
        if ts:
            today = datetime.utcnow() + timedelta(hours=8)
            today = today.replace(hour=0, minute=0, second=0, microsecond=0)
            di = 0
            date = ts[di].operated_at
            positions = {}
            realized_profits = defaultdict(float)
            while date <= today:
                profit = 0
                # include new transactions
                while di < len(ts) and ts[di].operated_at <= date:
                    t = ts[di]
                    op = 1 if t.type_ == 'buy' else -1
                    pair = (t.exchange, t.symbol)
                    if pair not in positions:
                        positions[pair] = (t.price, t.quantity * op)
                    else:
                        pv = positions[pair]
                        amount = pv[0] * pv[1] + t.price * t.quantity * op
                        quantity = pv[1] + t.quantity * op
                        if quantity == 0:
                            realized_profits[pair] += pv[1] * (t.price - pv[0])
                            del positions[pair]
                        else:
                            positions[pair] = (amount / quantity, quantity)
                    di += 1

                # calculate profit
                for pair in positions:
                    q = Quote.query_one(
                        {
                            'exchange': pair[0],
                            'symbol': pair[1],
                            'quote_type': '1d',
                            'quote_at': {
                                '$lte': date
                            }
                        },
                        sort=[('quote_at', -1)])
                    if q:
                        pv = positions[pair]
                        profit += (q.close - pv[0]) * pv[1]

                profit += sum(realized_profits.values())

                # update profit
                cls.update_one({
                    'date': date,
                    'user': user
                }, {'$set': {
                    'profit': profit
                }},
                               upsert=True)
                date += timedelta(days=1)