Ejemplo n.º 1
0
    def get(self, control_id: int) -> List[Dict]:
        grid = self._get_grid(control_id)

        # ctrl+s 保存 grid 内容为 xls 文件
        self._set_foreground(
            grid)  # setFocus buggy, instead of SetForegroundWindow
        grid.type_keys("^s", set_foreground=False)
        count = 10
        while count > 0:
            if self._trader.is_exist_pop_dialog():
                break
            self._trader.wait(0.2)
            count -= 1

        temp_path = tempfile.mktemp(suffix=".csv")
        logger.info("temp_path-->" + temp_path)
        self._set_foreground(self._trader.app.top_window())

        if (platform.system() == 'Windows'):
            tmp_file_path = temp_path
        else:
            tmp_file_path = self.normalize_path(temp_path)

            # alt+s保存,alt+y替换已存在的文件
        self._trader.app.top_window().Edit1.set_edit_text(tmp_file_path)
        self._trader.wait(0.1)
        self._trader.app.top_window().type_keys("%{s}%{y}",
                                                set_foreground=False)
        # Wait until file save complete otherwise pandas can not find file
        self._trader.wait(0.2)
        if self._trader.is_exist_pop_dialog():
            self._trader.app.top_window().Button2.click()
            self._trader.wait(0.2)

        return self._format_grid_data(temp_path)
Ejemplo n.º 2
0
    def record_trades(self, trade_record_path):
        time.sleep(0.5)
        trades_list = self.today_trades
        index_name = self._config.TRADE_RECORD_INDEX_NAME
        if len(trades_list) > 0:
            df_td = pd.DataFrame.from_dict(trades_list, orient='columns')
            df_td["日期"] = pd.to_datetime(date.today())
            df_td.set_index(["日期", index_name], drop=True, inplace=True)
        else:
            df_td = pd.DataFrame([])

        if df_td.shape[0] > 0:
            if trade_record_path.exists():
                df_his = pd.read_csv(trade_record_path,
                                     dtype={
                                         index_name: str,
                                         "证券代码": str
                                     },
                                     parse_dates=["日期"])
                df_his.set_index(["日期", index_name], drop=True, inplace=True)
                for ind in df_td.index:
                    df_his.loc[ind, :] = df_td.loc[ind, :]
                df_his.to_csv(trade_record_path,
                              header=True,
                              index=True,
                              float_format="%.3f")
                logger.info("append the trades to the trades record file.")
            else:
                df_his = df_td
                df_his.to_csv(trade_record_path,
                              header=True,
                              index=True,
                              float_format="%.3f")
                logger.info("append the trades to the trades record file.")
Ejemplo n.º 3
0
    def _get_clipboard_data(self) -> str:
        if Copy._need_captcha_reg:
            if (
                self._trader.app.top_window()
                .window(class_name="Static", title_re="验证码")
                .exists(timeout=1)
            ):
                file_path = "tmp.png"
                count = 5
                found = False
                while count > 0:
                    self._trader.app.top_window().window(
                        control_id=0x965, class_name="Static"
                    ).capture_as_image().save(
                        file_path
                    )  # 保存验证码

                    captcha_num = captcha_recognize(file_path)  # 识别验证码
                    logger.info("captcha result-->" + captcha_num)
                    if len(captcha_num) == 4:
                        self._trader.app.top_window().window(
                            control_id=0x964, class_name="Edit"
                        ).set_text(
                            captcha_num
                        )  # 模拟输入验证码

                        self._trader.app.top_window().set_focus()
                        pywinauto.keyboard.SendKeys("{ENTER}")  # 模拟发送enter,点击确定
                        try:
                            logger.info(
                                self._trader.app.top_window()
                                .window(control_id=0x966, class_name="Static")
                                .window_text()
                            )
                        except Exception as ex:  # 窗体消失
                            logger.exception(ex)
                            found = True
                            break
                    count -= 1
                    self._trader.wait(0.1)
                    self._trader.app.top_window().window(
                        control_id=0x965, class_name="Static"
                    ).click()
                if not found:
                    self._trader.app.top_window().Button2.click()  # 点击取消
            else:
                Copy._need_captcha_reg = False
        count = 5
        while count > 0:
            try:
                return pywinauto.clipboard.GetData()
            # pylint: disable=broad-except
            except Exception as e:
                count -= 1
                logger.exception("%s, retry ......", e)
Ejemplo n.º 4
0
def get_all_transactions():
    all_trans = []
    for num in range(1, 50):
        try:
            tmp = xq_follower.custom_query_strategy_transaction(
                strategy='ZH974199', page=num)
            print(num, len(tmp))
        except Exception as e:
            logger.info(e)
            continue
        all_trans.extend(tmp)
    write_txt(all_trans, 'all_trans.txt')
    return all_trans
Ejemplo n.º 5
0
    def extract_transactions(self, history):
        if history["count"] <= 0:
            return []
        rebalancing_index = 0
        raw_transactions = history["list"][rebalancing_index][
            "rebalancing_histories"]
        transactions = []
        for transaction in raw_transactions:
            if transaction["price"] is None:
                logger.info("该笔交易无法获取价格,疑似未成交,跳过。交易详情: %s", transaction)
                continue
            transactions.append(transaction)

        return transactions
Ejemplo n.º 6
0
    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()
Ejemplo n.º 7
0
    def _adjust_buy_amount(self, transaction, stock_code, amount, price, assets):
        if amount == 0:
            return amount
        price = price * (1 + self.slippage)
        stock_code = stock_code[-6:]
        # 511880这个是货币基金,买入这个相当于无效调仓
        if stock_code == "511880":
            return 0

        # user = self._users[0]

        # weight是5的倍数,且diff小于3.5,则认为是无效调仓
        # weight不是5的倍数
        # 进行下面的判断
        #  判断账户的仓位realWeight是否大于weight,
        #  如果realWeight >= weight,无效调仓
        #  如果realWeight < weight,则weight先往上靠,得到新的5的倍数仓位adjustWeight,实际调仓为adjustWeight - realWeight
        weight = self.none_to_zero(transaction['weight'])
        prevWeight = self.none_to_zero(transaction["prev_weight_adjusted"])
        startWeight = round(self.none_to_zero(transaction["prev_weight"]))
        diff = weight - prevWeight
        # 新开仓, 直接半仓买入
        if startWeight == 0:
            amount = round(assets / 2 / price, -2)
            logger.info("买:指令时间:%s 股票名称 %s: 策略从 %s -> %s,改为买入半仓,数量 %s, 金额为 %s",
                        transaction["datetime"], transaction["stock_name"], prevWeight, weight, amount,
                        round(amount * price))
            return amount
        return 0
        # diff小于3.5,且不是调仓到5的整数倍,大概率就是微调,直接放弃
        if weight % 5 == 0 and diff < 3.5:
            # logger.info("买:指令时间:%s 股票名称 %s: 策略调仓从 %s 调到 %s, 调仓幅度 %s, 小于3.5丢弃。", transaction["datetime"], transaction["stock_name"], prevWeight,
            #             weight, diff)
            return 0

        # 0 -> 1, 5 -> 6
        if startWeight % 5 == 0 and weight % 5 != 0 and diff < 4.5:
            diff = 5
            amount = round(diff / 100 * assets / price, -2)
            logger.info("买:指令时间:%s 股票名称 %s: 策略从 %s -> %s 改为调仓5,调整后的买入数量为:%s, 买入金额为 %s",
                        transaction["datetime"], transaction["stock_name"], prevWeight, weight, amount, round(amount * price))
            return amount

        if weight % 5 != 0 and startWeight % 5 != 0:
            logger.info("买:指令时间:%s 股票名称 %s: 策略调仓从 %s -> %s, 放弃调仓",
                        transaction["datetime"], transaction["stock_name"], prevWeight,
                        weight)
            return 0

        # 0 -> 5, 11 -> 15, 11 -> 20
        if weight % 5 == 0 and diff >= 4:
            logger.info("买:指令时间:%s 股票名称 %s: 策略从 %s -> %s, 按实际值进行调仓",
                        transaction["datetime"], transaction["stock_name"], prevWeight, weight)

        return amount
Ejemplo n.º 8
0
 def track_strategy_worker(self, strategy, name, interval=10, **kwargs):
     """跟踪下单worker
     :param strategy: 策略id
     :param name: 策略名字
     :param interval: 轮询策略的时间间隔,单位为秒"""
     while True:
         try:
             transactions = self.query_strategy_transaction(
                 strategy, **kwargs
             )
         # pylint: disable=broad-except
         except Exception as e:
             logger.exception("无法获取策略 %s 调仓信息, 错误: %s, 跳过此次调仓查询", name, e)
             time.sleep(3)
             continue
         for transaction in transactions:
             trade_cmd = {
                 "strategy": strategy,
                 "strategy_name": name,
                 "action": transaction["action"],
                 "stock_code": transaction["stock_code"],
                 "amount": transaction["amount"],
                 "price": transaction["price"],
                 "datetime": transaction["datetime"],
             }
             if self.is_cmd_expired(trade_cmd):
                 continue
             if trade_cmd["amount"] != 0:
                 logger.info(
                     "策略 [%s] 发送指令到交易队列, 股票: %s 动作: %s 数量: %s 价格: %s 信号产生时间: %s",
                     name,
                     trade_cmd["stock_code"],
                     trade_cmd["action"],
                     trade_cmd["amount"],
                     trade_cmd["price"],
                     trade_cmd["datetime"],
                 )
             self.trade_queue.put(trade_cmd)
             self.add_cmd_to_expired_cmds(trade_cmd)
         try:
             for _ in range(interval):
                 time.sleep(1)
         except KeyboardInterrupt:
             logger.info("程序退出")
             break
Ejemplo n.º 9
0
    def record_position(self, position_record_path):
        time.sleep(0.5)
        posi_list = self.position
        if len(posi_list) > 0:
            df_td = pd.DataFrame.from_dict(posi_list)
            df_td["日期"] = pd.to_datetime(date.today())
            df_td_copy = df_td.copy().set_index("证券代码")
            dict_td = df_td_copy[
                self._config.POSITION_RECORD_COMPARE_ITEMS].to_dict(
                    orient="index")
            df_td.set_index(["日期", "证券代码"], drop=True, inplace=True)
        else:
            df_td = pd.DataFrame([])
            dict_td = {}

        if df_td.shape[0] > 0:
            if position_record_path.exists():
                df_his = pd.read_csv(position_record_path,
                                     dtype={"证券代码": str},
                                     parse_dates=["日期"])
                max_dt = max(df_his["日期"])
                df_his_last = df_his[df_his["日期"] == max_dt]
                df_his_last.set_index("证券代码", inplace=True)
                dict_his = df_his_last[
                    self._config.POSITION_RECORD_COMPARE_ITEMS].to_dict(
                        orient="index")
                df_his.set_index(["日期", "证券代码"], drop=True, inplace=True)
                if dict_his != dict_td:
                    for ind in df_td.index:
                        df_his.loc[ind, :] = df_td.loc[ind, :]
                    df_his.to_csv(position_record_path,
                                  header=True,
                                  index=True,
                                  float_format="%.3f")
                    logger.info(
                        "append the positions to the position record file.")
            else:
                df_his = df_td
                df_his.to_csv(position_record_path,
                              header=True,
                              index=True,
                              float_format="%.3f")
                logger.info(
                    "append the positions to the position record file.")
Ejemplo n.º 10
0
    def login(self, user=None, password=None, **kwargs):
        """
        登陆接口
        :param user: 用户名
        :param password: 密码
        :param kwargs: 其他参数
        :return:
        """
        headers = self._generate_headers()
        self.s.headers.update(headers)

        # init cookie
        self.s.get(self.LOGIN_PAGE)

        # post for login
        params = self.create_login_params(user, password, **kwargs)
        rep = self.s.post(self.LOGIN_API, data=params)

        self.check_login_success(rep)
        logger.info("登录成功")
Ejemplo n.º 11
0
    def extract_transactions(self, history):
        if history["count"] <= 0:
            return []
        rebalancing_index = 0
        raw_transactions = history["list"][rebalancing_index]["rebalancing_histories"]
        transactions = []
        for transaction in raw_transactions:
            if transaction["price"] is None:
                logger.info("该笔交易无法获取价格,疑似未成交,跳过。交易详情: %s", transaction)
                continue
            trans_date = datetime.fromtimestamp(
                transaction["created_at"] // 1000
            )
            now = datetime.now()
            expire = (now - trans_date).total_seconds()
            if expire > self._expire_seconds:
                continue
            transactions.append(transaction)

        return transactions
Ejemplo n.º 12
0
    def login(self, user=None, password=None, **kwargs):
        """
        雪球登陆, 需要设置 cookies
        :param cookies: 雪球登陆需要设置 cookies, 具体见
            https://smalltool.github.io/2016/08/02/cookie/
        :return:
        """
        cookies = kwargs.get("cookies")
        if cookies is None:
            raise TypeError("雪球登陆需要设置 cookies, 具体见"
                            "https://smalltool.github.io/2016/08/02/cookie/")
        headers = self._generate_headers()
        self.s.headers.update(headers)

        self.s.get(self.LOGIN_PAGE)
        self._cookies = cookies
        cookie_dict = parse_cookies_str(cookies)
        self.s.cookies.update(cookie_dict)

        logger.info("xq_follower登录成功")
Ejemplo n.º 13
0
    def _adjust_sell_amount(self, stock_code, amount):
        """
        根据实际持仓值计算雪球卖出股数
          因为雪球的交易指令是基于持仓百分比,在取近似值的情况下可能出现不精确的问题。
        导致如下情况的产生,计算出的指令为买入 1049 股,取近似值买入 1000 股。
        而卖出的指令计算出为卖出 1051 股,取近似值卖出 1100 股,超过 1000 股的买入量,
        导致卖出失败
        :param stock_code: 证券代码
        :type stock_code: str
        :param amount: 卖出股份数
        :type amount: int
        :return: 考虑实际持仓之后的卖出股份数
        :rtype: int
        """
        # stock_code = stock_code[-6:]
        user = self._users[0]

        position = user.position
        # logger.info(position)
        # logger.info(stock_code)
        try:
            stock = next(s for s in position
                         if s["stock_code"].upper() == stock_code.upper())
        except StopIteration:
            logger.info("根据持仓调整 %s 卖出额,发现未持有股票 %s, 不做任何调整", stock_code,
                        stock_code)
            return amount

        available_amount = stock["enable_amount"]
        if available_amount >= amount:
            return amount

        adjust_amount = available_amount // 100 * 100
        logger.info(
            "股票 %s 实际可用余额 %s, 指令卖出股数为 %s, 调整为 %s",
            stock_code,
            available_amount,
            amount,
            adjust_amount,
        )
        return adjust_amount
Ejemplo n.º 14
0
    def follow(
        self,
        users,
        run_id,
        track_interval=1,
        trade_cmd_expire_seconds=120,
        cmd_cache=True,
        entrust_prop="limit",
        send_interval=0,
    ):
        """跟踪ricequant对应的模拟交易,支持多用户多策略
        :param users: 支持easytrader的用户对象,支持使用 [] 指定多个用户
        :param run_id: ricequant 的模拟交易ID,支持使用 [] 指定多个模拟交易
        :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)
        run_ids = self.warp_list(run_id)

        if cmd_cache:
            self.load_expired_cmd_cache()

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

        workers = []
        for id_ in run_ids:
            strategy_name = self.extract_strategy_name(id_)
            strategy_worker = Thread(
                target=self.track_strategy_worker,
                args=[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()
Ejemplo n.º 15
0
    def record_balance(self, balance_record_path):
        time.sleep(0.5)
        dict_td = self.balance
        df_td = pd.DataFrame.from_dict(dict_td, orient="index").T
        index_td = pd.to_datetime(date.today())
        df_td.index = [index_td]
        df_td.loc[index_td,
                  "记录时间"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        if balance_record_path.exists():
            df_his = pd.read_csv(balance_record_path,
                                 dtype={"证券代码": str},
                                 parse_dates=["日期", "记录时间"])
            df_his.set_index("日期", inplace=True)
            df_his.loc[index_td, :] = df_td.loc[index_td, :]
        else:
            df_his = df_td

        df_his.to_csv(balance_record_path,
                      header=True,
                      index=True,
                      float_format="%.2f")
        logger.info("updated today's balance to the balance record file.")
Ejemplo n.º 16
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,
                )
Ejemplo n.º 17
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)
Ejemplo n.º 18
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()
Ejemplo n.º 19
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
Ejemplo n.º 20
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":
                "-",
            }]
Ejemplo n.º 21
0
    def _adjust_sell_amount(self, stock_code, amount, transaction, assets):
        """
        根据实际持仓值计算雪球卖出股数
          因为雪球的交易指令是基于持仓百分比,在取近似值的情况下可能出现不精确的问题。
        导致如下情况的产生,计算出的指令为买入 1049 股,取近似值买入 1000 股。
        而卖出的指令计算出为卖出 1051 股,取近似值卖出 1100 股,超过 1000 股的买入量,
        导致卖出失败
        :param stock_code: 证券代码
        :type stock_code: str
        :param amount: 卖出股份数
        :type amount: int
        :return: 考虑实际持仓之后的卖出股份数
        :rtype: int
        """
        return 0
        if amount == 0:
            return 0
        stock_code = stock_code[-6:]
        if stock_code == "511880":
            return 0
        weight = self.none_to_zero(transaction['weight'])
        prevWeight = self.none_to_zero(transaction["prev_weight_adjusted"])
        # startWeight = round(self.none_to_zero(transaction["prev_weight"]))
        diff = prevWeight - weight
        # 微小调仓,忽略 5.5 -> 5, 6 -> 5
        if weight % 5 == 0 and diff < 3.5:
            return 0

        try:
            user = self._users[0]
            position = user.position
            stock = next(s for s in position if s["证券代码"] == stock_code)
        except StopIteration:
            logger.info("卖:指令时间:%s 根据持仓调整 %s 卖出额,发现未持有股票 %s, 调仓无效。", transaction["datetime"], transaction["stock_name"],
                        transaction["stock_name"])
            return 0
        except Exception as e:
            logger.info("未知错误:%s", e)
            return 0

        available_amount = stock["可用余额"]
        logger.info("卖:指令时间:%s 股票:%s 策略从 %s -> %s, 清空股票",
                    transaction["datetime"],
                    transaction["stock_name"],
                    transaction["prev_target_weight"],
                    transaction["weight"])
        return available_amount

        totalMoney = stock["市值"]
        # 策略调仓到0,那直接全卖

        if self.none_to_zero(transaction['weight']) == 0:
            # 当天不可卖仓位
            leftWeight = round(stock["冻结数量"] * stock["市价"] / assets, 2)
            logger.info("卖:指令时间:%s 股票:%s 策略从 %s -> %s, 清空股票, 账户当前股票仓位为 %s, 不可买仓位为 %s", transaction["datetime"],
                        transaction["stock_name"], transaction["prev_target_weight"], transaction["weight"],
                        round(totalMoney / assets * 100, 2), leftWeight)
            return available_amount
        if available_amount >= amount:
            currentWeight = round(totalMoney/assets * 100, 2)
            diff = self.none_to_zero(transaction["prev_target_weight"]) - self.none_to_zero(transaction["weight"])
            logger.info("卖:指令时间:%s 股票:%s 策略从 %s -> %s, 账户当前股票仓位为 %s, 卖出仓位 %s, 剩余仓位 %s",
                        transaction["datetime"],
                        transaction["stock_name"],
                        transaction["prev_target_weight"],
                        transaction["weight"],
                        currentWeight,
                        diff,
                        currentWeight - diff )
            return amount // 100 * 100
        adjust_amount = available_amount // 100 * 100
        logger.info(
            "卖:指令时间:%s 股票 %s 实际可用余额 %s, 指令卖出股数为 %s, 调整为 %s。",
            transaction["datetime"],
            transaction["stock_name"],
            available_amount,
            amount,
            adjust_amount
        )
        return adjust_amount