分类 理财 下的文章

思考 🤔

雪球网的 slogan 是「聪明的投资者都在这」。作为一个大笨蛋投资者,决定做一个简单的黑科技来抄作业,即根据雪球聪明的主理人的策略来调仓。
雪球里有很多主理人更新他们的投资组合,有的是实盘的,有的是模拟盘的。由于实盘组合在 PC 端是不展示的,比较之后,我决定选用模拟盘组合「迷踪一号」来测试程序可用性。

调试过程中,迫于雪球网使用「极验」验证码来进行登录二次验证(就是那种滑动拼图二维码),我使用微信扫码来登录,将登录状态的 Cookie 保存,并在调用获取历史调仓记录的 API 时带上这些 Cookie,从而正确获取雪球组合调仓数据。

代码 🌰

const puppeteer = require("puppeteer")
const axios = require("axios")
const fs = require("fs")

/**
 * @description 获取雪球 token,用户需要用微信扫码
 */
function refreshXueqiuToken() {
  return new Promise(async (resolve) => {
    const browser = await puppeteer.launch({
      headless: true,
    })
    const page = await browser.newPage()

    await page.setViewport({
      width: 1440,
      height: 1080,
    })
    await page.goto("https://xueqiu.com/p/ZH1794481")

    await page.click("#nav-login-link")
    browser.on("targetcreated", async (e) => {
      let wxPage = await e.page()
      const url = await wxPage.url()
      if (
        url &&
        url.includes("https://open.weixin.qq.com/connect/qrconnect?")
      ) {
        setTimeout(async () => {
          let img = await wxPage.$(".wrp_code img")
          let src = await img.getProperty("src")
          console.log("📱 请使用微信扫码:", src["_remoteObject"]["value"])
        }, 2000)
      }
    })

    browser.on("targetdestroyed", async (e) => {
      let pg = await e.page()
      const url = await pg.url()
      if (url.includes("service/wcconnect")) {
        console.log("扫码登录成功!正在写入最新 cookie...")
        setTimeout(async () => {
          let cookies = await page.cookies()
          fs.writeFileSync("./cookies.txt", JSON.stringify(cookies))
          console.log(
            new Date().toLocaleString() + " - Cookie 已写入 cookies.txt!"
          )
          resolve(cookies)
        }, 1000)
      }
    })
    setTimeout(async () => {
      await page.click("#weixin_login")
    }, 1000)
  })
}

/**
 * @description 获取雪球组合历史调仓
 */
async function gainTicketInfo(id = "ZH1794481") {
  let cookieStore = fs.readFileSync("./cookies.txt")
  let arr = JSON.parse(cookieStore)

  let cookie = ""
  for (let i = 0; i < arr.length; i++) {
    cookie = cookie + `${arr[i]["name"]}=${arr[i]["value"]};  `
  }

  let res = await axios.default({
    withCredentials: true,
    headers: {
      Cookie: cookie,
    },
    url: "https://xueqiu.com/cubes/rebalancing/history.json",
    params: {
      cube_symbol: id,
      page: 1,
      count: 20,
    },
    method: "GET",
  })
  return res.data
}

async function main() {
  console.log("即将执行雪球扫码登录...")
  // 执行登录
  await refreshXueqiuToken()
  console.log("正在获取某只股票调仓数据...")
  // 获取某一只股票组合历史调仓数据
  let ticketInfo = await gainTicketInfo("ZH1794481")
  console.log("雪球 ZH1794481 持仓数据:", ticketInfo)
}

main()

我们中国的 A 股是拿来解决债务问题的,从来不是让人赚钱的。A 股只要政体没变,我们的经济就还是房地产经济,很难真正转向资本市场。要做股票,就只能在跌全市场都没什么声息的时候建建仓,有喊牛市来的时候就做个短线退场,长期价值投资在中国很难的。


茅台就是一个很好的例子:近期,贵州茅台最近要发债 150 亿了,逻辑开始变了。现在贵州非常多城投债,都是资不抵债的状态。茅台还换了首席(传言是公安部的),再加上发债的动作,又说里面有多少比例是可以换成固定收益类产品的。怕不是在以「金融投资」的名义,通过蓝筹股来解决地方债务问题。

博傻理论 (greater fool theory),是指在资本市场中(如股票、期货市场):人们之所以完全不管某个东西的真实价值而愿意花高价购买,是因为他们预期会有一个更大的笨蛋会花更高的价格从他们那儿把它买走。博傻理论告诉人们的最重要的一个道理是:在这个世界上,傻不可怕,可怕的是做最后一个傻子。

搏傻能成功,往往是建立在行情呈上升趋势,多方力量雄厚的基础上。在震荡行情中,搏傻是下策。因此,在搏傻之前,需要对接下来的交易日行情走势有个判断。以股市为例:

  1. 接下来走势如何?(大盘、K 线技术面)
  2. 会有傻子进场吗?(换手率)
  3. 股票的逻辑是否还在,利好是否已兑现?(题材)

今天(20-09-16)对 002239 这只股判断有误。虽然这只股的逻辑挺硬核地打在了题材上(拿到了特斯拉大单,特斯拉昨日涨幅 7 个点)。然而大盘震荡,受 10 日线下行影响小幅回落,被轻套了。是为教训。

  1. 你能够亏损的最大范围,就是你能够投机的最大极限
  2. 应以退为进,先觉悟会亏损,才会有获利。不能将短期资金周转去买股票
  3. 资产运用最佳对策乃是:火力集中
  4. 选择投资标时要符合自己的性格
  5. 任何投资都要具备智慧的忍耐力
  6. 对于一件事具备兴趣,你就成功了一半
  7. 从事股票投资,至少会获得许多无形的收入
  8. 无论从事什么职业,先向身边亲友请教总没错
  9. 看 K 线重要,但公司未来的发展,与经营者的性格也不容忽视
  10. 判定成长或衰退,以其是否迎合时代潮流为标准
  11. 「安全至上」的人,最好远离股市
  12. 在不安定中寻找安定的人,最好只选购 3 - 5 只股票
  13. 长期投资依然以绩优股为主,不过至少一年重新评估一次
  14. 寻求潜力股是致富捷径
  15. 经济走势不是影响股价的唯一要素
  16. 长期过度热门的股票往往是陷阱
  17. 群众沸腾的时候,好的投资人更应冷静。投资股票也是一种精神修养
  18. 比起现在的一流公司,未来什么公司能一流更重要
  19. 在经济波动的浪潮中,选公司远比选业种更重要
  20. 选股票就像选对象,有时候要稍微眯上一眼

思路

通过聚宽平台提供的数据接口,跟踪北向资金出入数据,追涨来实现套利。代码获取了每日的北向流入额,然后用 150 日的流入额做出了一个布林带,当流入额大于等于布林带上轨的时候,策略会跟踪北向持仓中股票自身占比最大的 15 只股票,然后把他们买入,并且卖出自己持仓中不在这 15 只股票。当流入额小于等于布林带下轨时,清仓卖出。

代码

from jqdata import *
import random
import pandas as pd
import numpy as np
import datetime as dt

# 初始化函数,设定基准等等
def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 输出内容到日志 log.info()
    log.info('初始函数开始运行且全局只运行一次')
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')

    ### 股票相关设定 ###
    # 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5),
                   type='stock')
    g.max_stock_count = 15
    g.buy_pool = []
    g.sale_pool = []
    g.top_money_in = []
    g.window = 90
    g.stdev_n = 1
    g.mf, g.upper, g.lower = None, None, None
    run_daily(before_market_open, time='07:00')
    run_daily(reblance, '9:30')

def before_market_open(context):
    g.buy_pool = []
    g.sale_pool = []
    pre_date = (context.current_dt - datetime.timedelta(1)
                ).strftime('%Y-%m-%d')
    g.mf, g.upper, g.lower = get_boll(pre_date)
    log.info('北上资金均值:%.2f  北上资金上界:%.2f 北上资金下界:%.2f' % (g.mf, g.upper, g.lower))

def get_boll(end_date):
    """
    获取北向资金布林带
    """
    # 数据表:北上资金某日成交额
    table = finance.STK_ML_QUOTA
    # 查询:日期,每日额度,每日余额
    q = query(
        table.day, table.quota_daily, table.quota_daily_balance
    ).filter(
        table.link_id.in_(['310001', '310002']), table.day <= end_date
    ).order_by(table.day)
    money_df = finance.run_query(q)
    # 每日额度 - 每日余额
    money_df['net_amount'] = money_df['quota_daily'] - \
        money_df['quota_daily_balance']
    # 分组求和
    money_df = money_df.groupby('day')[['net_amount']].sum().iloc[-g.window:]
    mid = money_df['net_amount'].mean()
    stdev = money_df['net_amount'].std()
    upper = mid + 1*g.stdev_n * stdev
    lower = mid - 0.17*g.stdev_n * stdev
    mf = money_df['net_amount'].iloc[-1]
    return mf, upper, lower

def calc_change(context):
    # 数据表:北上资金持有股票数据
    table = finance.STK_HK_HOLD_INFO
    # 查询:日期,名称,代码,持股比例
    q = query(table.day, table.name, table.code, table.share_ratio).filter(
        table.link_id.in_(['310001', '310002']), table.day.in_([context.previous_date]))
    # 获取查询数据结果
    df = finance.run_query(q)
    # 将结果数据集根据持股比例进行降序排序,选出前x只股,x即为 g.max_stock_count 的值,返回结果
    return df.sort_values(by='share_ratio', ascending=False)[:g.max_stock_count]['code'].values

### 每日比较算法 ###
def reblance(context):
    if g.mf >= g.upper:
        s_change_rank = calc_change(context)
        final = list(s_change_rank)
        current_hold_funds_set = set(context.portfolio.positions.keys())
        if set(final) != current_hold_funds_set:
            need_buy = set(final).difference(current_hold_funds_set)
            need_sell = current_hold_funds_set.difference(final)
            cash_per_fund = context.portfolio.total_value/g.max_stock_count*0.99
            for fund in need_sell:
                # order_target(fund, 0)
                add_to_sale_pool(fund)
            for fund in need_buy:
                # order_value(fund, cash_per_fund)
                add_to_buy_pool(fund, cash_per_fund)
        

    elif g.mf <= g.lower:
        current_hold_funds_set = set(context.portfolio.positions.keys())
        if len(current_hold_funds_set) != 0:
            for fund in current_hold_funds_set:
                #log.info('买入 %s' % fund.name)
                # order_target(fund, 0)
                add_to_sale_pool(fund)
           
### 当天卖出队列 ###
def add_to_sale_pool(security_code):
    if len(g.sale_pool) <= g.max_stock_count:
        g.sale_pool.append(security_code)
    
### 当天买入队列 ###
def add_to_buy_pool(security_code, cash):
    if len(g.sale_pool) <= g.max_stock_count:
        g.buy_pool.append({'code': security_code, 'cash': cash})
        log.info(g.buy_pool)

### 买卖策略 ###
'''
测试时暂时空仓
'''
def handle_data(context):
    if len(g.buy_pool) > 0:
        length = len(g.buy_pool)
        idx = 0
        while length > 0:
            buy_item = g.buy_pool[idx]
            order_value(buy_item['code'], buy_item['cash'])
            del g.buy_pool[idx]
            length = length - 1

    elif len(g.sale_pool) > 0:
        length = len(g.sale_pool)
        idx = 0
        while length > 0:
            sale_code = g.sale_pool[idx]
            order_target(sale_code, 0)
            del g.sale_pool[idx]
            length = length - 1