Exemple #1
0
 def read_config(self, path):
     try:
         self.account_config = file2dict(path)
     except ValueError:
         logger.error("配置文件格式有误,请勿使用记事本编辑,推荐 sublime text")
     for value in self.account_config:
         if isinstance(value, int):
             logger.warning("配置文件的值最好使用双引号包裹,使用字符串,否则可能导致不可知问题")
 def extract_day_trades(self, run_id):
     ret_json = self.client.get_day_trades(run_id)
     if ret_json["code"] != 200:
         logger.error(
             "fetch day trades from run_id %s fail, msg %s",
             run_id,
             ret_json["msg"],
         )
         raise RuntimeError(ret_json["msg"])
     return ret_json["resp"]["trades"]
 def extract_strategy_name(self, run_id):
     ret_json = self.client.get_positions(run_id)
     if ret_json["code"] != 200:
         logger.error(
             "fetch data from run_id %s fail, msg %s",
             run_id,
             ret_json["msg"],
         )
         raise RuntimeError(ret_json["msg"])
     return ret_json["resp"]["name"]
Exemple #4
0
 def check_login(self, sleepy=30):
     logger.setLevel(logging.ERROR)
     try:
         response = self.heartbeat()
         self.check_account_live(response)
     except requests.exceptions.ConnectionError:
         pass
     except requests.exceptions.RequestException as e:
         logger.setLevel(self.log_level)
         logger.error("心跳线程发现账户出现错误: %s %s, 尝试重新登陆", e.__class__, e)
         self.autologin()
     finally:
         logger.setLevel(self.log_level)
     time.sleep(sleepy)
    def follow(
        self,
        users,
        strategies,
        track_interval=1,
        trade_cmd_expire_seconds=120,
        cmd_cache=True,
        entrust_prop="limit",
        send_interval=0,
    ):
        """跟踪joinquant对应的模拟交易,支持多用户多策略
        :param users: 支持easytrader的用户对象,支持使用 [] 指定多个用户
        :param strategies: joinquant 的模拟交易地址,支持使用 [] 指定多个模拟交易,
            地址类似 https://www.joinquant.com/algorithm/live/index?backtestId=xxx
        :param track_interval: 轮训模拟交易时间,单位为秒
        :param trade_cmd_expire_seconds: 交易指令过期时间, 单位为秒
        :param cmd_cache: 是否读取存储历史执行过的指令,防止重启时重复执行已经交易过的指令
        :param entrust_prop: 委托方式, 'limit' 为限价,'market' 为市价, 仅在银河实现
        :param send_interval: 交易发送间隔, 默认为0s。调大可防止卖出买入时卖出单没有及时成交导致的买入金额不足
        """
        users = self.warp_list(users)
        strategies = self.warp_list(strategies)

        if cmd_cache:
            self.load_expired_cmd_cache()

        self.start_trader_thread(users, trade_cmd_expire_seconds, entrust_prop,
                                 send_interval)

        workers = []
        for strategy_url in strategies:
            try:
                strategy_id = self.extract_strategy_id(strategy_url)
                strategy_name = self.extract_strategy_name(strategy_url)
            except:
                logger.error("抽取交易id和策略名失败, 无效的模拟交易url: %s", strategy_url)
                raise
            strategy_worker = Thread(
                target=self.track_strategy_worker,
                args=[strategy_id, strategy_name],
                kwargs={"interval": track_interval},
            )
            strategy_worker.start()
            workers.append(strategy_worker)
            logger.info("开始跟踪策略: %s", strategy_name)
        for worker in workers:
            worker.join()
Exemple #6
0
    def _trade(self, security, price=0, amount=0, volume=0, entrust_bs="buy"):
        """
        调仓
        :param security:
        :param price:
        :param amount:
        :param volume:
        :param entrust_bs:
        :return:
        """
        stock = self._search_stock_info(security)
        balance = self.get_balance()[0]
        if stock is None:
            raise exceptions.TradeError(u"没有查询要操作的股票信息")
        if not volume:
            volume = int(float(price) * amount)  # 可能要取整数
        if balance["current_balance"] < volume and entrust_bs == "buy":
            raise exceptions.TradeError(u"没有足够的现金进行操作")
        if stock["flag"] != 1:
            raise exceptions.TradeError(u"未上市、停牌、涨跌停、退市的股票无法操作。")
        if volume == 0:
            raise exceptions.TradeError(u"操作金额不能为零")

        # 计算调仓调仓份额
        weight = volume / balance["asset_balance"] * 100
        weight = round(weight, 2)

        # 获取原有仓位信息
        position_list = self._get_position()

        # 调整后的持仓
        is_have = False
        for position in position_list:
            if position["stock_id"] == stock["stock_id"]:
                is_have = True
                position["proactive"] = True
                old_weight = position["weight"]
                if entrust_bs == "buy":
                    position["weight"] = weight + old_weight
                else:
                    if weight > old_weight:
                        raise exceptions.TradeError(u"操作数量大于实际可卖出数量")
                    else:
                        position["weight"] = old_weight - weight
                position["weight"] = round(position["weight"], 2)
        if not is_have:
            if entrust_bs == "buy":
                position_list.append({
                    "code": stock["code"],
                    "name": stock["name"],
                    "enName": stock["enName"],
                    "hasexist": stock["hasexist"],
                    "flag": stock["flag"],
                    "type": stock["type"],
                    "current": stock["current"],
                    "chg": stock["chg"],
                    "percent": str(stock["percent"]),
                    "stock_id": stock["stock_id"],
                    "ind_id": stock["ind_id"],
                    "ind_name": stock["ind_name"],
                    "ind_color": stock["ind_color"],
                    "textname": stock["name"],
                    "segment_name": stock["ind_name"],
                    "weight": round(weight, 2),
                    "url": "/S/" + stock["code"],
                    "proactive": True,
                    "price": str(stock["current"]),
                })
            else:
                raise exceptions.TradeError(u"没有持有要卖出的股票")

        if entrust_bs == "buy":
            cash = ((balance["current_balance"] - volume) /
                    balance["asset_balance"] * 100)
        else:
            cash = ((balance["current_balance"] + volume) /
                    balance["asset_balance"] * 100)
        cash = round(cash, 2)
        logger.info("weight:%f, cash:%f", weight, cash)

        data = {
            "cash": cash,
            "holdings": str(json.dumps(position_list)),
            "cube_symbol": str(self.account_config["portfolio_code"]),
            "segment": 1,
            "comment": "",
        }

        try:
            resp = self.s.post(self.config["rebalance_url"], data=data)
        # pylint: disable=broad-except
        except Exception as e:
            logger.warning("调仓失败: %s ", e)
            return None
        else:
            logger.info("调仓 %s%s: %d", entrust_bs, stock["name"],
                        resp.status_code)
            resp_json = json.loads(resp.text)
            if "error_description" in resp_json and resp.status_code != 200:
                logger.error("调仓错误: %s", resp_json["error_description"])
                return [{
                    "error_no": resp_json["error_code"],
                    "error_info": resp_json["error_description"],
                }]
            return [{
                "entrust_no":
                resp_json["id"],
                "init_date":
                self._time_strftime(resp_json["created_at"]),
                "batch_no":
                "委托批号",
                "report_no":
                "申报号",
                "seat_no":
                "席位编号",
                "entrust_time":
                self._time_strftime(resp_json["updated_at"]),
                "entrust_price":
                price,
                "entrust_amount":
                amount,
                "stock_code":
                security,
                "entrust_bs":
                "买入",
                "entrust_type":
                "雪球虚拟委托",
                "entrust_status":
                "-",
            }]
Exemple #7
0
    def adjust_weight(self, stock_code, weight):
        """
        雪球组合调仓, weight 为调整后的仓位比例
        :param stock_code: str 股票代码
        :param weight: float 调整之后的持仓百分比, 0 - 100 之间的浮点数
        """

        stock = self._search_stock_info(stock_code)
        if stock is None:
            raise exceptions.TradeError(u"没有查询要操作的股票信息")
        if stock["flag"] != 1:
            raise exceptions.TradeError(u"未上市、停牌、涨跌停、退市的股票无法操作。")

        # 仓位比例向下取两位数
        weight = round(weight, 2)
        # 获取原有仓位信息
        position_list = self._get_position()

        # 调整后的持仓
        for position in position_list:
            if position["stock_id"] == stock["stock_id"]:
                position["proactive"] = True
                position["weight"] = weight

        if weight != 0 and stock["stock_id"] not in [
                k["stock_id"] for k in position_list
        ]:
            position_list.append({
                "code": stock["code"],
                "name": stock["name"],
                "enName": stock["enName"],
                "hasexist": stock["hasexist"],
                "flag": stock["flag"],
                "type": stock["type"],
                "current": stock["current"],
                "chg": stock["chg"],
                "percent": str(stock["percent"]),
                "stock_id": stock["stock_id"],
                "ind_id": stock["ind_id"],
                "ind_name": stock["ind_name"],
                "ind_color": stock["ind_color"],
                "textname": stock["name"],
                "segment_name": stock["ind_name"],
                "weight": weight,
                "url": "/S/" + stock["code"],
                "proactive": True,
                "price": str(stock["current"]),
            })

        remain_weight = 100 - sum(i.get("weight") for i in position_list)
        cash = round(remain_weight, 2)
        logger.info("调仓比例:%f, 剩余持仓 :%f", weight, remain_weight)
        data = {
            "cash": cash,
            "holdings": str(json.dumps(position_list)),
            "cube_symbol": str(self.account_config["portfolio_code"]),
            "segment": "true",
            "comment": "",
        }

        try:
            resp = self.s.post(self.config["rebalance_url"], data=data)
        # pylint: disable=broad-except
        except Exception as e:
            logger.warning("调仓失败: %s ", e)
            return None
        logger.info("调仓 %s: 持仓比例%d", stock["name"], weight)
        resp_json = json.loads(resp.text)
        if "error_description" in resp_json and resp.status_code != 200:
            logger.error("调仓错误: %s", resp_json["error_description"])
            return [{
                "error_no": resp_json["error_code"],
                "error_info": resp_json["error_description"],
            }]
        logger.info("调仓成功 %s: 持仓比例%d", stock["name"], weight)
        return None
Exemple #8
0
    def _execute_trade_cmd(self, trade_cmd, users, expire_seconds,
                           entrust_prop, send_interval):
        """分发交易指令到对应的 user 并执行
        :param trade_cmd:
        :param users:
        :param expire_seconds:
        :param entrust_prop:
        :param send_interval:
        :return:
        """
        for user in users:
            # check expire
            now = datetime.datetime.now()
            expire = (now - trade_cmd["datetime"]).total_seconds()
            if expire > expire_seconds:
                logger.warning(
                    "策略 [%s] 指令(股票: %s 动作: %s 数量: %s 价格: %s)超时,指令产生时间: %s 当前时间: %s, 超过设置的最大过期时间 %s 秒, 被丢弃",
                    trade_cmd["strategy_name"],
                    trade_cmd["stock_code"],
                    trade_cmd["action"],
                    trade_cmd["amount"],
                    trade_cmd["price"],
                    trade_cmd["datetime"],
                    now,
                    expire_seconds,
                )
                break

            # check price
            price = trade_cmd["price"]
            if not self._is_number(price) or price <= 0:
                logger.warning(
                    "策略 [%s] 指令(股票: %s 动作: %s 数量: %s 价格: %s)超时,指令产生时间: %s 当前时间: %s, 价格无效 , 被丢弃",
                    trade_cmd["strategy_name"],
                    trade_cmd["stock_code"],
                    trade_cmd["action"],
                    trade_cmd["amount"],
                    trade_cmd["price"],
                    trade_cmd["datetime"],
                    now,
                )
                break

            # check amount
            if trade_cmd["amount"] <= 0:
                logger.warning(
                    "策略 [%s] 指令(股票: %s 动作: %s 数量: %s 价格: %s)超时,指令产生时间: %s 当前时间: %s, 买入股数无效 , 被丢弃",
                    trade_cmd["strategy_name"],
                    trade_cmd["stock_code"],
                    trade_cmd["action"],
                    trade_cmd["amount"],
                    trade_cmd["price"],
                    trade_cmd["datetime"],
                    now,
                )
                break

            actual_price = self._calculate_price_by_slippage(
                trade_cmd["action"], trade_cmd["price"])
            args = {
                "security": trade_cmd["stock_code"],
                "price": actual_price,
                "amount": trade_cmd["amount"],
                "entrust_prop": entrust_prop,
            }
            try:
                response = getattr(user, trade_cmd["action"])(**args)
            except exceptions.TradeError as e:
                trader_name = type(user).__name__
                err_msg = "{}: {}".format(type(e).__name__, e.args)
                logger.error(
                    "%s 执行 策略 [%s] 指令(股票: %s 动作: %s 数量: %s 价格(考虑滑点): %s 指令产生时间: %s) 失败, 错误信息: %s",
                    trader_name,
                    trade_cmd["strategy_name"],
                    trade_cmd["stock_code"],
                    trade_cmd["action"],
                    trade_cmd["amount"],
                    actual_price,
                    trade_cmd["datetime"],
                    err_msg,
                )
            else:
                logger.info(
                    "策略 [%s] 指令(股票: %s 动作: %s 数量: %s 价格(考虑滑点): %s 指令产生时间: %s) 执行成功, 返回: %s",
                    trade_cmd["strategy_name"],
                    trade_cmd["stock_code"],
                    trade_cmd["action"],
                    trade_cmd["amount"],
                    actual_price,
                    trade_cmd["datetime"],
                    response,
                )
Exemple #9
0
    def follow(  # type: ignore
            self,
            users,
            strategies,
            total_assets=10000,
            initial_assets=None,
            adjust_sell=False,
            adjust_buy=False,
            track_interval=10,
            trade_cmd_expire_seconds=120,
            cmd_cache=True,
            slippage: float = 0.0,
    ):
        """跟踪 joinquant 对应的模拟交易,支持多用户多策略
        :param users: 支持 easytrader 的用户对象,支持使用 [] 指定多个用户
        :param strategies: 雪球组合名, 类似 ZH123450
        :param total_assets: 雪球组合对应的总资产, 格式 [组合1对应资金, 组合2对应资金]
            若 strategies=['ZH000001', 'ZH000002'],
                设置 total_assets=[10000, 10000], 则表明每个组合对应的资产为 1w 元
            假设组合 ZH000001 加仓 价格为 p 股票 A 10%,
                则对应的交易指令为 买入 股票 A 价格 P 股数 1w * 10% / p 并按 100 取整
        :param adjust_sell: 是否根据用户的实际持仓数调整卖出股票数量,
            当卖出股票数大于实际持仓数时,调整为实际持仓数。目前仅在银河客户端测试通过。
            当 users 为多个时,根据第一个 user 的持仓数决定
        :type adjust_sell: bool
        :param initial_assets: 雪球组合对应的初始资产,
            格式 [ 组合1对应资金, 组合2对应资金 ]
            总资产由 初始资产 × 组合净值 算得, total_assets 会覆盖此参数
        :param track_interval: 轮训模拟交易时间,单位为秒
        :param trade_cmd_expire_seconds: 交易指令过期时间, 单位为秒
        :param cmd_cache: 是否读取存储历史执行过的指令,防止重启时重复执行已经交易过的指令
        :param slippage: 滑点,0.0 表示无滑点, 0.05 表示滑点为 5%
        """
        super().follow(
            users=users,
            strategies=strategies,
            track_interval=track_interval,
            trade_cmd_expire_seconds=trade_cmd_expire_seconds,
            cmd_cache=cmd_cache,
            slippage=slippage,
        )

        self._adjust_sell = adjust_sell
        self._adjust_buy = adjust_buy
        self._expire_seconds = trade_cmd_expire_seconds

        self._users = self.warp_list(users)

        strategies = self.warp_list(strategies)
        total_assets = self.warp_list(total_assets)
        initial_assets = self.warp_list(initial_assets)

        if cmd_cache:
            self.load_expired_cmd_cache()

        self.start_trader_thread(self._users, trade_cmd_expire_seconds)

        for strategy_url, strategy_total_assets, strategy_initial_assets in zip(
            strategies, total_assets, initial_assets
        ):
            assets = self.calculate_assets(
                strategy_url, strategy_total_assets, strategy_initial_assets
            )
            try:
                strategy_id = self.extract_strategy_id(strategy_url)
                strategy_name = self.extract_strategy_name(strategy_url)
            except:
                logger.error("抽取交易id和策略名失败, 无效模拟交易url: %s", strategy_url)
                raise
            strategy_worker = Thread(
                target=self.track_strategy_worker,
                args=[strategy_id, strategy_name],
                kwargs={"interval": track_interval, "assets": assets},
            )
            strategy_worker.start()
            logger.info("开始跟踪策略: %s", strategy_name)
Exemple #10
0
    def project_transactions(self, transactions, assets):

        logger.info("总额:" + str(assets))
        # 打开数据库连接
        db = pymysql.connect("localhost", "root", "sa123$", "xueqiu")

        sql_operation = ""
        for transaction in transactions:
            weight_diff = self.none_to_zero(
                transaction["target_weight"]) - self.none_to_zero(
                    transaction["prev_weight_adjusted"])

            start_amount = 0
            initial_amount = 0
            crrut_amount = 0
            if (abs(weight_diff) != 0):
                initial_amount = abs(
                    weight_diff) / 100 * assets / transaction["price"]
            if (self.none_to_zero(transaction["prev_weight_adjusted"]) != 0):
                start_amount = abs(
                    self.none_to_zero(transaction["prev_weight_adjusted"])
                ) / 100 * assets / transaction["price"]
            if (self.none_to_zero(transaction["target_weight"]) != 0):
                crrut_amount = abs(
                    self.none_to_zero(transaction["target_weight"])
                ) / 100 * assets / transaction["price"]

            transaction["datetime"] = datetime.fromtimestamp(
                transaction["updated_at"] // 1000)

            transaction["stock_code"] = transaction["stock_symbol"].lower()
            transaction["action"] = "买入" if weight_diff > 0 else "卖出"
            transaction["amount"] = int(round(initial_amount, -2))
            if transaction["action"] == "卖出" and self._adjust_sell:
                transaction["amount"] = self._adjust_sell_amount(
                    transaction["stock_code"], transaction["amount"])
            logger.info("动作:" + str(transaction["action"]))
            logger.info("股票类型:" + str(transaction["stock_label"]))
            logger.info("股票名称:" + str(transaction["stock_name"]))
            logger.info("股票号:" + str(transaction["stock_symbol"]))
            logger.info("调仓前百分比:" + str(transaction["prev_weight_adjusted"]))
            logger.info("调仓后百分比:" + str(transaction["target_weight"]))
            logger.info("百分比:" + str(abs(weight_diff)))
            logger.info("数量:" + str(int(round(initial_amount, -2))))
            logger.info("价格:" + str(transaction["price"]))
            logger.info("成本:" + str(transaction["price"] *
                                    float(round(initial_amount, -2))))
            logger.info("时间:" + str(transaction["datetime"]))
            # 使用cursor()方法获取操作游标
            cursor = db.cursor()
            # 类似于其他语言的 query 函数,execute 是 python 中的执行查询函数
            cursor.execute("SELECT * FROM xq_operation where STOCK_CODE = '" +
                           str(transaction["stock_symbol"]) +
                           "' and  OPERATION_TIME = '" +
                           str(transaction["datetime"]) + "' ")
            # 使用 fetchall 函数,将结果集(多维元组)存入 rows 里面
            rows = cursor.fetchall()
            if (len(rows) <= 0):
                sql_operation = """INSERT IGNORE  INTO xq_operation(
                ACCOUNT_ID,STOCK_NAME, STOCK_CODE, STOCK_OPERATION, STOCK_PRICE, STOCK_COUNT, COST,START_REPERTORY, END_REPERTORY, OPERATION_TIME, IS_DEL)
                VALUES ('1','{}','{}','{}',{},{},{},{},{},'{}','0')""".format(
                    str(transaction["stock_name"]),
                    str(transaction["stock_symbol"]),
                    str(transaction["action"]),
                    Decimal.from_float(transaction["price"]),
                    int(round(initial_amount, -2)),
                    transaction["price"] * int(round(initial_amount, -2)),
                    transaction["prev_weight_adjusted"],
                    transaction["target_weight"], str(transaction["datetime"]))
                try:
                    if (sql_operation != ""):
                        logger.info(sql_operation)
                        cursor.execute(sql_operation)  # 执行sql语句
                except Exception:
                    logger.error("发生异常1", Exception)
                    # db.rollback()  # 如果发生错误则回滚
            # 类似于其他语言的 query 函数,execute 是 python 中的执行查询函数
            cursor.execute("SELECT * FROM xq_history where STOCK_CODE = '" +
                           str(transaction["stock_symbol"]) + "' ")
            # 使用 fetchall 函数,将结果集(多维元组)存入 rows 里面
            rowss = cursor.fetchall()
            IS_HAS = 0
            if (str(transaction["target_weight"]) is "0.00"
                    or str(transaction["target_weight"]) is "0"
                    or str(transaction["target_weight"]) is "0.0"
                    or crrut_amount == 0):
                IS_HAS = 1

            if (len(rowss) > 0):
                # 计算平均价格
                cursor.execute(
                    "SELECT AVG(STOCK_PRICE) FROM xq_operation WHERE stock_code='"
                    + str(transaction["stock_symbol"]) + "' ")
                # 使用 fetchall 函数,将结果集(多维元组)存入 rows 里面
                rowsxq = cursor.fetchall()
                ave_price = 0
                for row in rowsxq:
                    ave_price = row[0]

                sql_xq_history = """update xq_history set  STOCK_COUNT = {},AVE_PRICE = {}, START_REPERTORY = {}, HISTORY_TIME = '{}', IS_HAS = '{}'  where STOCK_CODE = '{}' """.format(
                    decimal.Decimal(int(round(crrut_amount, -2))), ave_price,
                    str(transaction["target_weight"]),
                    str(transaction["datetime"]), IS_HAS,
                    str(transaction["stock_symbol"]))
                try:
                    if (sql_xq_history != ""):
                        # logger.info(sql_xq_history)
                        cursor.execute(sql_xq_history)  # 执行sql语句
                except Exception:
                    logger.error("发生异常2", Exception)
            else:
                sql_operation = """INSERT IGNORE  INTO xq_history(
                                ACCOUNT_ID,STOCK_NAME, STOCK_CODE, STOCK_COUNT, AVE_PRICE, START_REPERTORY, HISTORY_TIME, IS_HAS, IS_DEL)
                                VALUES ('1','{}','{}',{},{},'{}','{}',{},'0')""".format(
                    str(transaction["stock_name"]),
                    str(transaction["stock_symbol"]),
                    int(round(initial_amount, -2)),
                    transaction["price"], transaction["target_weight"],
                    str(transaction["datetime"]), IS_HAS, '0')
                try:
                    if (sql_operation != ""):
                        # logger.info(sql_operation)
                        cursor.execute(sql_operation)  # 执行sql语句
                except Exception:
                    logger.error("发生异常3", Exception)
                db.commit()  # 提交到数据库执行
        cursor.execute(
            "update xq_account set TOTAL_BALANCE = {} where  ACCOUNT_ID = '1' "
            .format(assets))
        # 使用 fetchall 函数,将结果集(多维元组)存入 rows 里面
        rows = cursor.fetchall()
        # 计算当前持仓盈亏
        cursor.execute("SELECT * FROM xq_history where IS_HAS = '0' ")
        xq_history_rows = cursor.fetchall()
        for row in xq_history_rows:

            # 查询股票当前价格
            self.account_config = {
                "cookies": self._cookies,
                "portfolio_code": row[3],
                "portfolio_market": "cn",
            }
            # logger.info("account_config:" + str(self.account_config))
            data = {
                "code": row[3],
                "size": "300",
                "key": "47bce5c74f",
                "market": self.account_config["portfolio_market"],
            }
            cur = self.s.get(self.SEARCH_STOCK_URL, params=data)
            stockss = json.loads(cur.text)
            stockss = stockss["stocks"]
            curstock = None
            if len(stockss) > 0:
                curstock = stockss[0]
                # logger.info("testsssssss:" + str(curstock["current"]))
                # logger.info("testsssssss:" + str(row[4]))
                update_xq_history = """update xq_history set  PROFIT_LOSS = {}  where STOCK_CODE = '{}' """.format(
                    Decimal(row[4] * curstock["current"]) -
                    Decimal(row[4] * row[10]), row[3])
                try:
                    if (update_xq_history != ""):
                        # logger.info(update_xq_history)
                        cursor.execute(update_xq_history)  # 执行sql语句
                except Exception:
                    logger.error("发生异常2", Exception)

        db.commit()  # 提交到数据库执行
        # 关闭数据库连接
        db.close()