自用策略初始提交
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,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,,
|
||||
|
@@ -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 []
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,,
|
||||
|
@@ -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.
Binary file not shown.
Reference in New Issue
Block a user