自用策略初始提交

This commit is contained in:
cls
2025-11-06 10:26:02 +08:00
commit 8a7583f111
90 changed files with 1053065 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
import abc
import datetime
import re,json
import time
from typing import List
import requests
from log import logger
from misc import parse_cookies_str
class BaseFollower(metaclass=abc.ABCMeta):
"""
slippage: 滑点,取值范围为 [0, 1]
"""
LOGIN_PAGE = ""
LOGIN_API = ""
TRANSACTION_API = ""
CMD_CACHE_FILE = "cmd_cache.pk"
WEB_REFERER = ""
WEB_ORIGIN = ""
def __init__(self):
#self.trade_queue = queue.Queue()
#self.expired_cmds = set()
self.s = requests.Session()
self.s.verify = False
self.slippage: float = 0.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)
cookie_dict = parse_cookies_str(cookies)
self.s.cookies.update(cookie_dict)
#extract_strategy_name(self, 'ZH1332574')
logger.info("登录成功")
def _generate_headers(self):
headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.8",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.100 Safari/537.36",
"Referer": self.WEB_REFERER,
"X-Requested-With": "XMLHttpRequest",
"Origin": self.WEB_ORIGIN,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
}
return headers
def check_login_success(self, rep):
"""检查登录状态是否成功
:param rep: post login 接口返回的 response 对象
:raise 如果登录失败应该抛出 NotLoginError """
pass
def create_login_params(self, user, password, **kwargs) -> dict:
"""生成 post 登录接口的参数
:param user: 用户名
:param password: 密码
:return dict 登录参数的字典
"""
return {}
def extract_strategy_name(self, strategy_url):
"""
抽取 策略名,主要用于日志打印,便于识别
:param strategy_url:
:return: str 策略名
"""
pass
#新增
def _query_zh_(self,userid):
url = 'https://im.xueqiu.com/im-comet/v2/sessions/1681984443-0/messages.json?user_id=%s&limit=%d' %(userid, 1)
chat_history = self.s.get(url).json()
for chat_record in chat_history:
new=chat_record['messageId']#获取messageId
messageType=chat_record['messageType']#获取messageId
starte='1'
if messageType=='VIEW':
view1 = json.loads(chat_record['view'])#策略代号
starte=view1['url'][21:] #策略代号
time.sleep(1)
return new,starte
#非开盘时间调仓,补充
def minutes_2(self, strategys, userid, assets,**kwargs):
dayOfWeek = datetime.datetime.now().weekday()
time_n=datetime.datetime.now()
hour=str(time_n)[11:13]
minute=int(str(time_n)[14:16])
print(time_n)
if dayOfWeek<=4 and ((hour=='09' and minute<32 and minute>=30) or (hour=='13' and minute<2)) :
#if dayOfWeek<=6 or (hour=='11' or minute<=59):
for j in range(0,len(strategys)):
print('开盘自动监测中......',strategys[j])
try:
transactions = self.query_strategy_transaction(strategys[j], assets[j],**kwargs)
if len(transactions)>0:
return transactions
except Exception as e:
#logger.exception("无法获取策略 %s 调仓信息, 错误: %s, 跳过此次调仓查询", name, e)
time.sleep(3)
if j < len(strategys)-1:
continue
else:
return []
else:
return []
def track_strategy_worker(self, strategys, userid,users, assets, interval=10, **kwargs):
"""跟踪下单worker
:param strategy: 策略id
:param name: 策略名字
:param interval: 轮询策略的时间间隔,单位为秒"""
old,starte=self._query_zh_(userid)
transactions=[]
k=0
while True:
k=k+1
print(k)
if k>=100:
return []
#前两分钟补充,非开盘调仓
minutes_2_data=self.minutes_2( strategys, userid, assets,**kwargs)
if not minutes_2_data is None and len(minutes_2_data)>0:
return minutes_2_data
try:
new,starte_n=self._query_zh_(userid)#新增
except:
time.sleep(5)
for j in range(0,len(strategys)):
print('自动监测中......',strategys[j])
if new != old and starte_n==strategys[j]:
#if new == old:
old=new
try:
transactions = self.query_strategy_transaction(strategys[j], assets[j],**kwargs)
if len(transactions)>0:
k=5
return transactions
if k==5:
break
except Exception as e:
logger.exception("无法获取策略 %s 调仓信息, 错误: %s, 跳过此次调仓查询", e)
time.sleep(3)
continue
def query_strategy_transaction(self, strategy,assets, **kwargs):
params = self.create_query_transaction_params(strategy)
rep = self.s.get(self.TRANSACTION_API, params=params)
history = rep.json()
transactions = self.extract_transactions(history)
return self.project_transactions(transactions,assets, **kwargs)
def extract_transactions(self, history) -> List[str]:
"""
抽取接口返回中的调仓记录列表
:param history: 调仓接口返回信息的字典对象
:return: [] 调参历史记录的列表
"""
return []
def create_query_transaction_params(self, strategy) -> dict:
"""
生成用于查询调参记录的参数
:param strategy: 策略 id
:return: dict 调参记录参数
"""
return {}
def project_transactions(self, transactions, **kwargs):
"""
修证调仓记录为内部使用的统一格式
:param transactions: [] 调仓记录的列表
:return: [] 修整后的调仓记录
"""
pass
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import logging
logger = logging.getLogger("easytrader")
logger.setLevel(logging.INFO)
logger.propagate = False
fmt = logging.Formatter(
"%(asctime)s [%(levelname)s] %(filename)s %(lineno)s: %(message)s"
)
ch = logging.StreamHandler()
ch.setFormatter(fmt)
logger.handlers.append(ch)
+31
View File
@@ -0,0 +1,31 @@
# coding:utf-8
import json
def parse_cookies_str(cookies):
"""
parse cookies str to dict
:param cookies: cookies str
:type cookies: str
:return: cookie dict
:rtype: dict
"""
cookie_dict = {}
for record in cookies.split(";"):
key, value = record.strip().split("=", 1)
cookie_dict[key] = value
return cookie_dict
def file2dict(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
def grep_comma(num_str):
return num_str.replace(",", "")
def str2num(num_str, convert_type="float"):
num = float(grep_comma(num_str))
return num if convert_type == "float" else int(num)
+7
View File
@@ -0,0 +1,7 @@
cookies,"acw_tc=1a0c640d17531935616981938e006ef252a0559897fab0bba4f9d895a29e9a; cookiesu=511753193562165; device_id=5e4e14f7b544ff2991451c65d5126572; smidV2=202507222212422c69577824fd4da6831e70eaef7c9a0100997f287400fde20; s=aj11k1j1hi; __utma=1.531231151.1753193728.1753193728.1753193728.1; __utmz=1.1753193728.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmb=1.1.10.1753193728; Hm_lvt_1db88642e346389874251b5a1eded6e3=1753193563,1753193898; HMACCOUNT=D1422B20F0A5BD81; remember=1; xq_a_token=3b113cc4b2ac7d22e86c8eb73d3c8f3fd2d1536b; xqat=3b113cc4b2ac7d22e86c8eb73d3c8f3fd2d1536b; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjMxMDU0NjU2MDgsImlzcyI6InVjIiwiZXhwIjoxNzU0NzAyNDg3LCJjdG0iOjE3NTMxOTM5MTExMzEsImNpZCI6ImQ5ZDBuNEFadXAifQ.LdPrdejNMPM-ajyiurRu4Ibp4ONJ7E_pJ2YpG1yUnXMmQfDjC6ZHZm7YyIuWd1Ly7NZTYK4VrWo2A5nZtQ5pP-4u2wMCCiihxqBcW3njMpvIBstPwxDC8UUy1cmWCp_r2vrKqt0tMIJ4znqZmcf0SAJIuLgfTV6EJ5bxkuPnmSuTHuEAMRX0trBUNmHw0k9pBv8tvquDF_eq-5yJn-VDYUCxXQ2wxJs9sxfJEh6hEkOKGiELhXCXd2s92ga8h9OmshH69nRr0dFsn-jmAz7K_g8ZcpeZqdp7JImuSBAH21ZlJ3C6tXm5PNQin1Guv-s9NzhZhClP_ttkjqmCWE2uDA; xq_r_token=0be44e56eb527c2082c3fdb0c025c13098c60dfd; xq_is_login=1; u=3105465608; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1753194584; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=ohr0r8Af2tz/pULn7oDoWuu08XH8sq/VO2dgPV6B0qsNcf60jDeYHGq9iVQhxZt7wr+fbtczD910dfZzePOeyw%3D%3D; ssxmod_itna=iqGxgDuD90KrG0D27DBGDRDUx4eLqiK4GHDyxWF50CGDLxn4KGdYRaGwb8DcGi=jMbN4gnGo5D/mjkeDZDG9dDqx0ErXKWhAeP8YjkEepkQYOm+33itDwxm7/FxEx5Yo75o8gqlpNZSXKgYDU4GnD06QKE+rDYYfDBYD74G+DDeDixGmteDStxD9DGPdglTi2eDEDYpbxiU4arcFxDLTjebHhDDBzfiDKTpQoDDlzBxq8CDq8PD+3ncT/6Te8F4ojeDMWxGX9hk4jCyUVIDbkUSLHPoYxB69xBQiXpuspoTTq1ax5B6xKhY7GrMio/A4KADdBsYA4a757Dxl0hgixrCqq7D3BsIOwDCeDDfCiDue0rQDUQ4yPXgafqOXP2NVhGyB5m0e0iDXmToieei41nwsAhqFhQt+1i5=8m5S29YD; ssxmod_itna2=iqGxgDuD90KrG0D27DBGDRDUx4eLqiK4GHDyxWF50CGDLxn4KGdYRaGwb8DcGi=jMbN4gnGOeDAK=ARA37fD7pQT2GoYD/+hYfhEYn+rrWob/f6CnxT5A1l8eGs=Id+9X9PPwz18ud=q6FuQefixnl+GEmD=PaC54iuQPwdjAAltUWW4Mnb33pkQPhbtQWlPom8DKr1j7l8DQ8QfHlfu7A==o7DdMlSP=+bqPnDL4nwt0F7r908vG5YOjW7Xx4Uuhw85BDdAEjPQII3Xt5DFBQ0BGyES10S3cAWFTLfg24Iyl9LbQIjOBvuOA4ffTknx6DyMAczQam0TPmbPQTOQi2SHWAT2+9pf4FmDKFud9Y4CveFP6n0cDqPtc5yy3EFUl2Of0EbgOQ56z9O+h4lb44FOFpKmut/PvYvCfg4cDGfD8B4xETmjohDwLtKVm+Yhwj9c4yvx1cxLnHrPC8qYup3syFVBBQFz+xgmaCF7b2bdgF79qfbBqcohRnQp0tejK+n9l+49FWfGx3Q8Yw9CB7ejmB7plkIpweCeN5uzGmkICOtezIl6ewflR5oDLWiYHIIygdi0spdvtTsX7p93K3A5yf1Mm7Ajw1Gq16zAHOy7Dh+4YGjmk9lh5=qx2m5pL79vj4EorL5qVn653LwjtatMnSyLLUZOR66QAtwyM+f15GCi0qYq2nwPY146Pxd/muB87wrGYFib3q3Kh3tKhwGYY4oW5ShsAEjGAOxZ03CxPD",
序号,组合,指定资金
1,ZH1741493,50000
2,ZH3379035,40000
3,ZH2120024,50000
4,ZH2001065,20000
5,,
1 cookies acw_tc=1a0c640d17531935616981938e006ef252a0559897fab0bba4f9d895a29e9a; cookiesu=511753193562165; device_id=5e4e14f7b544ff2991451c65d5126572; smidV2=202507222212422c69577824fd4da6831e70eaef7c9a0100997f287400fde20; s=aj11k1j1hi; __utma=1.531231151.1753193728.1753193728.1753193728.1; __utmz=1.1753193728.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmb=1.1.10.1753193728; Hm_lvt_1db88642e346389874251b5a1eded6e3=1753193563,1753193898; HMACCOUNT=D1422B20F0A5BD81; remember=1; xq_a_token=3b113cc4b2ac7d22e86c8eb73d3c8f3fd2d1536b; xqat=3b113cc4b2ac7d22e86c8eb73d3c8f3fd2d1536b; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjMxMDU0NjU2MDgsImlzcyI6InVjIiwiZXhwIjoxNzU0NzAyNDg3LCJjdG0iOjE3NTMxOTM5MTExMzEsImNpZCI6ImQ5ZDBuNEFadXAifQ.LdPrdejNMPM-ajyiurRu4Ibp4ONJ7E_pJ2YpG1yUnXMmQfDjC6ZHZm7YyIuWd1Ly7NZTYK4VrWo2A5nZtQ5pP-4u2wMCCiihxqBcW3njMpvIBstPwxDC8UUy1cmWCp_r2vrKqt0tMIJ4znqZmcf0SAJIuLgfTV6EJ5bxkuPnmSuTHuEAMRX0trBUNmHw0k9pBv8tvquDF_eq-5yJn-VDYUCxXQ2wxJs9sxfJEh6hEkOKGiELhXCXd2s92ga8h9OmshH69nRr0dFsn-jmAz7K_g8ZcpeZqdp7JImuSBAH21ZlJ3C6tXm5PNQin1Guv-s9NzhZhClP_ttkjqmCWE2uDA; xq_r_token=0be44e56eb527c2082c3fdb0c025c13098c60dfd; xq_is_login=1; u=3105465608; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1753194584; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=ohr0r8Af2tz/pULn7oDoWuu08XH8sq/VO2dgPV6B0qsNcf60jDeYHGq9iVQhxZt7wr+fbtczD910dfZzePOeyw%3D%3D; ssxmod_itna=iqGxgDuD90KrG0D27DBGDRDUx4eLqiK4GHDyxWF50CGDLxn4KGdYRaGwb8DcGi=jMbN4gnGo5D/mjkeDZDG9dDqx0ErXKWhAeP8YjkEepkQYOm+33itDwxm7/FxEx5Yo75o8gqlpNZSXKgYDU4GnD06QKE+rDYYfDBYD74G+DDeDixGmteDStxD9DGPdglTi2eDEDYpbxiU4arcFxDLTjebHhDDBzfiDKTpQoDDlzBxq8CDq8PD+3ncT/6Te8F4ojeDMWxGX9hk4jCyUVIDbkUSLHPoYxB69xBQiXpuspoTTq1ax5B6xKhY7GrMio/A4KADdBsYA4a757Dxl0hgixrCqq7D3BsIOwDCeDDfCiDue0rQDUQ4yPXgafqOXP2NVhGyB5m0e0iDXmToieei41nwsAhqFhQt+1i5=8m5S29YD; ssxmod_itna2=iqGxgDuD90KrG0D27DBGDRDUx4eLqiK4GHDyxWF50CGDLxn4KGdYRaGwb8DcGi=jMbN4gnGOeDAK=ARA37fD7pQT2GoYD/+hYfhEYn+rrWob/f6CnxT5A1l8eGs=Id+9X9PPwz18ud=q6FuQefixnl+GEmD=PaC54iuQPwdjAAltUWW4Mnb33pkQPhbtQWlPom8DKr1j7l8DQ8QfHlfu7A==o7DdMlSP=+bqPnDL4nwt0F7r908vG5YOjW7Xx4Uuhw85BDdAEjPQII3Xt5DFBQ0BGyES10S3cAWFTLfg24Iyl9LbQIjOBvuOA4ffTknx6DyMAczQam0TPmbPQTOQi2SHWAT2+9pf4FmDKFud9Y4CveFP6n0cDqPtc5yy3EFUl2Of0EbgOQ56z9O+h4lb44FOFpKmut/PvYvCfg4cDGfD8B4xETmjohDwLtKVm+Yhwj9c4yvx1cxLnHrPC8qYup3syFVBBQFz+xgmaCF7b2bdgF79qfbBqcohRnQp0tejK+n9l+49FWfGx3Q8Yw9CB7ejmB7plkIpweCeN5uzGmkICOtezIl6ewflR5oDLWiYHIIygdi0spdvtTsX7p93K3A5yf1Mm7Ajw1Gq16zAHOy7Dh+4YGjmk9lh5=qx2m5pL79vj4EorL5qVn653LwjtatMnSyLLUZOR66QAtwyM+f15GCi0qYq2nwPY146Pxd/muB87wrGYFib3q3Kh3tKhwGYY4oW5ShsAEjGAOxZ03CxPD
2 序号 组合 指定资金
3 1 ZH1741493 50000
4 2 ZH3379035 40000
5 3 ZH2120024 50000
6 4 ZH2001065 20000
7 5
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from __future__ import division, print_function, unicode_literals
from datetime import datetime
from follower import BaseFollower
from log import logger
from misc import parse_cookies_str
import pandas as pd
class XueQiuFollower(BaseFollower):
LOGIN_PAGE = "https://www.xueqiu.com"
LOGIN_API = "https://xueqiu.com/snowman/login"
TRANSACTION_API = "https://xueqiu.com/cubes/rebalancing/history.json"
PORTFOLIO_URL = "https://xueqiu.com/p/"
WEB_REFERER = "https://www.xueqiu.com"
def __init__(self):
super().__init__()
self._adjust_sell = None
self._users = None
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)
cookie_dict = parse_cookies_str(cookies)
self.s.cookies.update(cookie_dict)
#extract_strategy_name(self, 'ZH1332574')
logger.info("登录成功")
def extract_strategy_name(self, strategy_url):
base_url = "https://xueqiu.com/cubes/nav_daily/all.json?cube_symbol={}"
url = base_url.format(strategy_url)
rep = self.s.get(url)
info_index = 0
return rep.json()[info_index]["name"]
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
def create_query_transaction_params(self, strategy):
params = {"cube_symbol": strategy, "page": 1, "count": 1}
return params
# noinspection PyMethodOverriding
def none_to_zero(self, data):
if data is None:
return 0
return data
# noinspection PyMethodOverriding
def project_transactions(self, transactions, assets):
pa = pd.DataFrame(index=[],columns=[])
for transaction in transactions:
weight_diff = self.none_to_zero(transaction["weight"]) - self.none_to_zero(
transaction["prev_weight"]
)
#print(33333,weight_diff,assets,transaction["price"])
initial_amount = abs(weight_diff) / 100 * assets / transaction["price"]
transaction["datetime"] = datetime.fromtimestamp(
transaction["created_at"] // 1000
)
transaction["stock_code"] = transaction["stock_symbol"].lower()
transaction["action"] = "buy" if weight_diff > 0 else "sell"
if str(transaction["stock_code"][2:4])=='11' or str(transaction["stock_code"][2:4])=='12':
shou=1
else:
shou=2
transaction["amount"] = int(round(initial_amount, -shou))
pa.loc[transaction["stock_code"],"price"]=transaction["price"]
pa.loc[transaction["stock_code"],"amount"]=transaction["amount"]
pa.loc[transaction["stock_code"],"datetime"]=transaction["datetime"]
pa.loc[transaction["stock_code"],"action"]=transaction["action"]
current_time = datetime.now()
diff = (current_time - transaction["datetime"]).total_seconds()
if int(diff)<20:
return pa
else:
return []
+219
View File
@@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
import abc
import datetime
import re,json
import time
from typing import List
import requests
from log import logger
from misc import parse_cookies_str
class BaseFollower(metaclass=abc.ABCMeta):
"""
slippage: 滑点,取值范围为 [0, 1]
"""
LOGIN_PAGE = ""
LOGIN_API = ""
TRANSACTION_API = ""
CMD_CACHE_FILE = "cmd_cache.pk"
WEB_REFERER = ""
WEB_ORIGIN = ""
def __init__(self):
#self.trade_queue = queue.Queue()
#self.expired_cmds = set()
self.s = requests.Session()
self.s.verify = False
self.slippage: float = 0.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)
cookie_dict = parse_cookies_str(cookies)
self.s.cookies.update(cookie_dict)
#extract_strategy_name(self, 'ZH1332574')
logger.info("登录成功")
def _generate_headers(self):
headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.8",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.100 Safari/537.36",
"Referer": self.WEB_REFERER,
"X-Requested-With": "XMLHttpRequest",
"Origin": self.WEB_ORIGIN,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
}
return headers
def check_login_success(self, rep):
"""检查登录状态是否成功
:param rep: post login 接口返回的 response 对象
:raise 如果登录失败应该抛出 NotLoginError """
pass
def create_login_params(self, user, password, **kwargs) -> dict:
"""生成 post 登录接口的参数
:param user: 用户名
:param password: 密码
:return dict 登录参数的字典
"""
return {}
def extract_strategy_name(self, strategy_url):
"""
抽取 策略名,主要用于日志打印,便于识别
:param strategy_url:
:return: str 策略名
"""
pass
#新增
def _query_zh_(self,userid):
url = 'https://im.xueqiu.com/im-comet/v2/sessions/1681984443-0/messages.json?user_id=%s&limit=%d' %(userid, 1)
chat_history = self.s.get(url).json()
for chat_record in chat_history:
new=chat_record['messageId']#获取messageId
messageType=chat_record['messageType']#获取messageId
starte='1'
if messageType=='VIEW':
view1 = json.loads(chat_record['view'])#策略代号
starte=view1['url'][21:] #策略代号
time.sleep(1)
return new,starte
#非开盘时间调仓,补充
def minutes_2(self, strategys, userid, assets,**kwargs):
dayOfWeek = datetime.datetime.now().weekday()
time_n=datetime.datetime.now()
hour=str(time_n)[11:13]
minute=int(str(time_n)[14:16])
print(time_n)
if dayOfWeek<=4 and ((hour=='09' and minute<32 and minute>=30) or (hour=='13' and minute<2)) :
#if dayOfWeek<=6 or (hour=='11' or minute<=59):
for j in range(0,len(strategys)):
print('开盘自动监测中......',strategys[j])
try:
transactions = self.query_strategy_transaction(strategys[j], assets[j],**kwargs)
if len(transactions)>0:
return transactions
except Exception as e:
#logger.exception("无法获取策略 %s 调仓信息, 错误: %s, 跳过此次调仓查询", name, e)
time.sleep(3)
if j < len(strategys)-1:
continue
else:
return []
else:
return []
def track_strategy_worker(self, strategys, userid,users, assets, interval=10, **kwargs):
"""跟踪下单worker
:param strategy: 策略id
:param name: 策略名字
:param interval: 轮询策略的时间间隔,单位为秒"""
old,starte=self._query_zh_(userid)
transactions=[]
k=0
while True:
k=k+1
print(k)
if k>=100:
return []
#前两分钟补充,非开盘调仓
minutes_2_data=self.minutes_2( strategys, userid, assets,**kwargs)
if not minutes_2_data is None and len(minutes_2_data)>0:
return minutes_2_data
try:
new,starte_n=self._query_zh_(userid)#新增
except:
time.sleep(5)
for j in range(0,len(strategys)):
print('自动监测中......',strategys[j])
if new != old and starte_n==strategys[j]:
#if new == old:
old=new
try:
transactions = self.query_strategy_transaction(strategys[j], assets[j],**kwargs)
if len(transactions)>0:
k=5
return transactions
if k==5:
break
except Exception as e:
logger.exception("无法获取策略 %s 调仓信息, 错误: %s, 跳过此次调仓查询", e)
time.sleep(3)
continue
def query_strategy_transaction(self, strategy,assets, **kwargs):
params = self.create_query_transaction_params(strategy)
rep = self.s.get(self.TRANSACTION_API, params=params)
history = rep.json()
transactions = self.extract_transactions(history)
return self.project_transactions(transactions,assets, **kwargs)
def extract_transactions(self, history) -> List[str]:
"""
抽取接口返回中的调仓记录列表
:param history: 调仓接口返回信息的字典对象
:return: [] 调参历史记录的列表
"""
return []
def create_query_transaction_params(self, strategy) -> dict:
"""
生成用于查询调参记录的参数
:param strategy: 策略 id
:return: dict 调参记录参数
"""
return {}
def project_transactions(self, transactions, **kwargs):
"""
修证调仓记录为内部使用的统一格式
:param transactions: [] 调仓记录的列表
:return: [] 修整后的调仓记录
"""
pass
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import logging
logger = logging.getLogger("easytrader")
logger.setLevel(logging.INFO)
logger.propagate = False
fmt = logging.Formatter(
"%(asctime)s [%(levelname)s] %(filename)s %(lineno)s: %(message)s"
)
ch = logging.StreamHandler()
ch.setFormatter(fmt)
logger.handlers.append(ch)
+31
View File
@@ -0,0 +1,31 @@
# coding:utf-8
import json
def parse_cookies_str(cookies):
"""
parse cookies str to dict
:param cookies: cookies str
:type cookies: str
:return: cookie dict
:rtype: dict
"""
cookie_dict = {}
for record in cookies.split(";"):
key, value = record.strip().split("=", 1)
cookie_dict[key] = value
return cookie_dict
def file2dict(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
def grep_comma(num_str):
return num_str.replace(",", "")
def str2num(num_str, convert_type="float"):
num = float(grep_comma(num_str))
return num if convert_type == "float" else int(num)
@@ -0,0 +1,28 @@
cookies,"cookiesu=191750772541614; device_id=1aa02c03552abceadecb2739bbb22753; smidV2=2025062421422684a173893a01b0f3aab647b1593749cd00b3382a030dcc480; s=bu15p9id4j; bid=2c83e23d12700bb5d8115404894461ae_mckhq3o8; __utmz=1.1751372094.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=1.1565912137.1751372094.1751383237.1751889781.3; remember=1; xq_a_token=8360cc2ee2662e4910fc5e8d28fd9616f5a25c01; xqat=8360cc2ee2662e4910fc5e8d28fd9616f5a25c01; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjcxNjA4MzgwNTgsImlzcyI6InVjIiwiZXhwIjoxNzUzOTY0MDQ5LCJjdG0iOjE3NTIwNjg1MjYxMzQsImNpZCI6ImQ5ZDBuNEFadXAifQ.hK59hAbbz3eK455XzoM2VLOZ8sBYYuimg8YjOF9eeWQM4vfy7gv2Ky56WAhRVLp6JtZtn_XpKPFiLGFZ_9rYWjmlkX7wm62y6S-vsCZbjIgyVhwNr66QK_B7v4xn3EhVSqo0rdgBJGQIaI33YB6MR0WFgkCRLy-fOq0YVNcv_Uh2-9CCgbagB3gcuDl158u68Vhhjlhu38-TLywFBUjbkA8mIYpra24XTRK-qG9sQbgpo3BkNsDZza_Yehwfvtr-1qAybYLaYsXoygxrxTtnpiwIrNaPe3aB8ztA45rPaE6W_b46m8i_agESPOflQ4c3_IJhJOUdzH5Aj8wMEnWr_w; xq_r_token=e0d4e05a6a3a9fec5b78bb2ea3c0f6a78204065d; xq_is_login=1; u=7160838058; acw_tc=1a0c660417523885271697993e00717dbfc46117e29830504427d58eff25fb; is_overseas=0; Hm_lvt_1db88642e346389874251b5a1eded6e3=1751383193,1751889683,1752068495,1752388529; HMACCOUNT=EB45621A3BC707DA; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1752388597; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=su1FaOpXw2ndOH3TZPPLCCcKcT+Imtl21eeDD4joyCH+yUOAF6YPtknsHMr/g6/6ugDA8ioiS2cn5x6cI8Wlew%3D%3D; ssxmod_itna=Wqjx2DyD0DRG0QG0QGCirKqQK0QwFq0dGMD3qiQGgDYq7=GF7DCgrKO=QOGgxDv/xWYDGK4342GitDlO7iYDSxD6HDK4GThz=jyrqeFQ0EhItA7DOxP=5tC=wxM8/000pLsLI6Mp0=tv4K4EviDB3DbqDyziiqeeGGU4GwDGoD34DiDDpLD03Db4D+/lrD7xTNPpWQpjeDQ4GyDit=3xTDm4DfDDdQsixw9WxD0Txwk7DPzDGW0Qnqp6e=DGU54cDq0eDMbxGXKAkP0eDBlKCPt=WL2zgpmeF7xBQD7u9NTWqO9QcrsCgi/cb/aYt4qYnDIwxSxPCuqY4DVp4AG44RqzitY0qiGq8uqrwHY7YDDWYxxhlYqgdhB1RLz7SzgkiY2tQ7YNANdhDZ7oNrQ37xVnDGW5lB5xE5NAYhYY4OYkC5hmxWiDD; ssxmod_itna2=Wqjx2DyD0DRG0QG0QGCirKqQK0QwFq0dGMD3qiQGgDYq7=GF7DCgrKO=QOGgxDv/xWYDGK4342GiYDiP37UxYK47PqNzQ=YD/ziO3aeqn/8AHKz5QW27qZ08irc4wMO7SLzaaYt9nBQ8kQKR8fLKwfx3lhqdj0hn3tMQnUBYGD6Q70BzB2QlTrQceWqA8u5oDhMGkn5bQKfBWd+W=Nq50U79C2x+MpeD6Da5Q9hARHoHlx4UudqO0c8sj7hxc4aT1bqF9Hj53mw4gk2NRGC4wvs4R6+KX1PeD",
ÐòºÅ,×éºÏ,Ö¸¶¨×ʽð
1,ZH2120024,25000
2,ZH1741493,25000
3,ZH2001065,20000
4,,
5,,
6,,
7,,
8,,
9,,
10,,
11,,
12,,
13,,
14,,
15,,
16,,
17,,
18,,
19,,
20,,
21,,
22,,
23,,
24,,
25,,
26,,
1 cookies cookiesu=191750772541614; device_id=1aa02c03552abceadecb2739bbb22753; smidV2=2025062421422684a173893a01b0f3aab647b1593749cd00b3382a030dcc480; s=bu15p9id4j; bid=2c83e23d12700bb5d8115404894461ae_mckhq3o8; __utmz=1.1751372094.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=1.1565912137.1751372094.1751383237.1751889781.3; remember=1; xq_a_token=8360cc2ee2662e4910fc5e8d28fd9616f5a25c01; xqat=8360cc2ee2662e4910fc5e8d28fd9616f5a25c01; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjcxNjA4MzgwNTgsImlzcyI6InVjIiwiZXhwIjoxNzUzOTY0MDQ5LCJjdG0iOjE3NTIwNjg1MjYxMzQsImNpZCI6ImQ5ZDBuNEFadXAifQ.hK59hAbbz3eK455XzoM2VLOZ8sBYYuimg8YjOF9eeWQM4vfy7gv2Ky56WAhRVLp6JtZtn_XpKPFiLGFZ_9rYWjmlkX7wm62y6S-vsCZbjIgyVhwNr66QK_B7v4xn3EhVSqo0rdgBJGQIaI33YB6MR0WFgkCRLy-fOq0YVNcv_Uh2-9CCgbagB3gcuDl158u68Vhhjlhu38-TLywFBUjbkA8mIYpra24XTRK-qG9sQbgpo3BkNsDZza_Yehwfvtr-1qAybYLaYsXoygxrxTtnpiwIrNaPe3aB8ztA45rPaE6W_b46m8i_agESPOflQ4c3_IJhJOUdzH5Aj8wMEnWr_w; xq_r_token=e0d4e05a6a3a9fec5b78bb2ea3c0f6a78204065d; xq_is_login=1; u=7160838058; acw_tc=1a0c660417523885271697993e00717dbfc46117e29830504427d58eff25fb; is_overseas=0; Hm_lvt_1db88642e346389874251b5a1eded6e3=1751383193,1751889683,1752068495,1752388529; HMACCOUNT=EB45621A3BC707DA; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1752388597; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=su1FaOpXw2ndOH3TZPPLCCcKcT+Imtl21eeDD4joyCH+yUOAF6YPtknsHMr/g6/6ugDA8ioiS2cn5x6cI8Wlew%3D%3D; ssxmod_itna=Wqjx2DyD0DRG0QG0QGCirKqQK0QwFq0dGMD3qiQGgDYq7=GF7DCgrKO=QOGgxDv/xWYDGK4342GitDlO7iYDSxD6HDK4GThz=jyrqeFQ0EhItA7DOxP=5tC=wxM8/000pLsLI6Mp0=tv4K4EviDB3DbqDyziiqeeGGU4GwDGoD34DiDDpLD03Db4D+/lrD7xTNPpWQpjeDQ4GyDit=3xTDm4DfDDdQsixw9WxD0Txwk7DPzDGW0Qnqp6e=DGU54cDq0eDMbxGXKAkP0eDBlKCPt=WL2zgpmeF7xBQD7u9NTWqO9QcrsCgi/cb/aYt4qYnDIwxSxPCuqY4DVp4AG44RqzitY0qiGq8uqrwHY7YDDWYxxhlYqgdhB1RLz7SzgkiY2tQ7YNANdhDZ7oNrQ37xVnDGW5lB5xE5NAYhYY4OYkC5hmxWiDD; ssxmod_itna2=Wqjx2DyD0DRG0QG0QGCirKqQK0QwFq0dGMD3qiQGgDYq7=GF7DCgrKO=QOGgxDv/xWYDGK4342GiYDiP37UxYK47PqNzQ=YD/ziO3aeqn/8AHKz5QW27qZ08irc4wMO7SLzaaYt9nBQ8kQKR8fLKwfx3lhqdj0hn3tMQnUBYGD6Q70BzB2QlTrQceWqA8u5oDhMGkn5bQKfBWd+W=Nq50U79C2x+MpeD6Da5Q9hARHoHlx4UudqO0c8sj7hxc4aT1bqF9Hj53mw4gk2NRGC4wvs4R6+KX1PeD
2 ÐòºÅ ×éºÏ Ö¸¶¨×ʽð
3 1 ZH2120024 25000
4 2 ZH1741493 25000
5 3 ZH2001065 20000
6 4
7 5
8 6
9 7
10 8
11 9
12 10
13 11
14 12
15 13
16 14
17 15
18 16
19 17
20 18
21 19
22 20
23 21
24 22
25 23
26 24
27 25
28 26
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from __future__ import division, print_function, unicode_literals
from datetime import datetime
from follower import BaseFollower
from log import logger
from misc import parse_cookies_str
import pandas as pd
class XueQiuFollower(BaseFollower):
LOGIN_PAGE = "https://www.xueqiu.com"
LOGIN_API = "https://xueqiu.com/snowman/login"
TRANSACTION_API = "https://xueqiu.com/cubes/rebalancing/history.json"
PORTFOLIO_URL = "https://xueqiu.com/p/"
WEB_REFERER = "https://www.xueqiu.com"
def __init__(self):
super().__init__()
self._adjust_sell = None
self._users = None
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)
cookie_dict = parse_cookies_str(cookies)
self.s.cookies.update(cookie_dict)
#extract_strategy_name(self, 'ZH1332574')
logger.info("登录成功")
def extract_strategy_name(self, strategy_url):
base_url = "https://xueqiu.com/cubes/nav_daily/all.json?cube_symbol={}"
url = base_url.format(strategy_url)
rep = self.s.get(url)
info_index = 0
return rep.json()[info_index]["name"]
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
def create_query_transaction_params(self, strategy):
params = {"cube_symbol": strategy, "page": 1, "count": 1}
return params
# noinspection PyMethodOverriding
def none_to_zero(self, data):
if data is None:
return 0
return data
# noinspection PyMethodOverriding
def project_transactions(self, transactions, assets):
pa = pd.DataFrame(index=[],columns=[])
for transaction in transactions:
weight_diff = self.none_to_zero(transaction["weight"]) - self.none_to_zero(
transaction["prev_weight"]
)
#print(33333,weight_diff,assets,transaction["price"])
initial_amount = abs(weight_diff) / 100 * assets / transaction["price"]
transaction["datetime"] = datetime.fromtimestamp(
transaction["created_at"] // 1000
)
transaction["stock_code"] = transaction["stock_symbol"].lower()
transaction["action"] = "buy" if weight_diff > 0 else "sell"
if str(transaction["stock_code"][2:4])=='11' or str(transaction["stock_code"][2:4])=='12':
shou=1
else:
shou=2
transaction["amount"] = int(round(initial_amount, -shou))
pa.loc[transaction["stock_code"],"price"]=transaction["price"]
pa.loc[transaction["stock_code"],"amount"]=transaction["amount"]
pa.loc[transaction["stock_code"],"datetime"]=transaction["datetime"]
pa.loc[transaction["stock_code"],"action"]=transaction["action"]
current_time = datetime.now()
diff = (current_time - transaction["datetime"]).total_seconds()
if int(diff)<20:
return pa
else:
return []
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.