diff --git a/.gitignore b/.gitignore index b77292f..f6bff8a 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,12 @@ venv.bak/ # vscode .vscode/ + +# history plugin +.history/ + +# My test +tests/test_main.py + +# MacOS +.DS_Store \ No newline at end of file diff --git a/pysnowball/__init__.py b/pysnowball/__init__.py index 8dc5549..23f833e 100644 --- a/pysnowball/__init__.py +++ b/pysnowball/__init__.py @@ -20,7 +20,7 @@ from pysnowball.token import (get_token,set_token) -from pysnowball.user import(watch_list, watch_stock) +from pysnowball.user import(watch_list, watch_stock, watch_funds) from pysnowball.cube import(nav_daily, rebalancing_history, rebalancing_current, quote_current) @@ -34,6 +34,6 @@ from pysnowball.fund import (fund_detail, fund_info, fund_growth, fund_nav_history, fund_derived, fund_asset, - fund_manager, fund_achievement, fund_trade_date) + fund_manager, fund_achievement, fund_trade_date, fund_yield, fund_search, fund_exist, funds_add, funds_remove) from pysnowball.suggest import suggest_stock \ No newline at end of file diff --git a/pysnowball/api_ref.py b/pysnowball/api_ref.py index 693a43e..331f637 100644 --- a/pysnowball/api_ref.py +++ b/pysnowball/api_ref.py @@ -43,6 +43,7 @@ # user watch_list = "https://stock.xueqiu.com/v5/stock/portfolio/list.json?system=true" watch_stock = "https://stock.xueqiu.com/v5/stock/portfolio/stock/list.json?size=1000&category=1&pid=" +watch_funds = "https://stock.xueqiu.com/v5/stock/portfolio/stock/list.json?size=1000&category=2&pid=" # cube nav_daily = "https://xueqiu.com/cubes/nav_daily/all.json?cube_symbol=" @@ -64,24 +65,34 @@ # fund # param is fund code -fund_detail = "https://danjuanapp.com/djapi/fund/detail/%s" +fund_detail = "https://danjuanfunds.com/djapi/fund/detail/%s" # param is fund code -fund_info = "https://danjuanapp.com/djapi/fund/%s" +fund_info = "https://danjuanfunds.com/djapi/fund/%s" # first param is fund code, second is 'ty' -fund_growth = "https://danjuanapp.com/djapi/fund/growth/%s?day=%s" +fund_growth = "https://danjuanfunds.com/djapi/fund/growth/%s?day=%s" # first param is fund code -fund_nav_history = "https://danjuanapp.com/djapi/fund/nav/history/%s?page=%s&size=%s" +fund_nav_history = "https://danjuanfunds.com/djapi/fund/nav/history/%s?page=%s&size=%s" # param is fund code -fund_achievement = "https://danjuanapp.com/djapi/fundx/base/fund/achievement/%s" +fund_achievement = "https://danjuanfunds.com/djapi/fundx/base/fund/achievement/%s" # 基金持仓:param is fund code -fund_asset = "https://danjuanapp.com/djapi/fundx/base/fund/record/asset/percent?fund_code=%s" +fund_asset = "https://danjuanfunds.com/djapi/fundx/base/fund/record/asset/percent?fund_code=%s" # 基金管理人: param is fund code -fund_manager = "https://danjuanapp.com/djapi/fundx/base/fund/record/manager/list?fund_code=%s&post_status=%s" -# https://danjuanapp.com/djapi/fund/base/quote/data/index/analysis/008975 +fund_manager = "https://danjuanfunds.com/djapi/fundx/base/fund/record/manager/list?fund_code=%s&post_status=%s" +# https://danjuanfunds.com/djapi/fund/base/quote/data/index/analysis/008975 # param is fund code -fund_trade_date = "https://danjuanapp.com/djapi/fund/order/v2/trade_date?fd_code=%s" +fund_trade_date = "https://danjuanfunds.com/djapi/fund/order/v2/trade_date?fd_code=%s" # param is fund code -fund_derived = "https://danjuanapp.com/djapi/fund/derived/%s" +fund_derived = "https://danjuanfunds.com/djapi/fund/derived/%s" +# Yield info: param is fund code +fund_yield = "https://danjuanfunds.com/djapi/fundx/autoinvest/quote/yield/list?fd_code=%s" +# Fund search: params are keyword and cookie +fund_search = "https://danjuanfunds.com/djapi/v2/search?key=%s&xq_access_token=&source=index" +# Fund does exist in watchlist: param is fund code +fund_exist = "https://danjuanfunds.com/xqstock/v5/stock/portfolio/stock/hasexist.json?symbol=%s" +# POST: Add to watchlist +funds_add = "https://danjuanfunds.com/xqstock/v5/stock/portfolio/stock/add.json" +# POST: Remove from watchlist +funds_remove = "https://danjuanfunds.com/xqstock/v5/stock/portfolio/stock/cancel.json" # suggest suggest_stock = "https://xueqiu.com/query/v1/suggest_stock.json?q=" diff --git a/pysnowball/fund.py b/pysnowball/fund.py index a27e953..ac93ff0 100644 --- a/pysnowball/fund.py +++ b/pysnowball/fund.py @@ -11,6 +11,10 @@ def fund_info(fund_code): def fund_growth(fund_code, day='ty'): + ''' + Args: + day: 'ty' (default) - this year,'1m','3m','6m','1y','2y','3y','5y','all' + ''' return utls.fetch_danjuan_fund(api_ref.fund_growth % (fund_code, day)) @@ -36,3 +40,38 @@ def fund_trade_date(fund_code): def fund_derived(fund_code): return utls.fetch_danjuan_fund(api_ref.fund_derived % fund_code) + +def fund_yield(fund_code): + return utls.fetch_danjuan_fund(api_ref.fund_yield % fund_code) + +def fund_search(keywords): + return utls.fetch_danjuan_fund(api_ref.fund_search % keywords) + +def fund_exist(fund_code): + return utls.fetch_danjuan_fund(api_ref.fund_exist % (fund_code if fund_code.startswith('F') else 'F'+fund_code)) + +def funds_add(fund_codes): + ''' Add fund(s) to watch list + Args: + fund_codes: list of fund codes, e.g. ['F019918','F161039'] + ''' + payload = "symbols=" + for code in fund_codes: + if not code.startswith('F'): + code = 'F' + code + payload += code + ',' + payload = payload if not payload.endswith(',') else payload[:-1] + return utls.post_danjuan_fund(api_ref.funds_add, payload) + +def funds_remove(fund_codes): + ''' Remove fund(s) from watch list + Args: + fund_codes: list of fund codes, e.g. ['F019918','F161039'] + ''' + payload = "symbols=" + for code in fund_codes: + if not code.startswith('F'): + code = 'F' + code + payload += code + ',' + payload = payload if not payload.endswith(',') else payload[:-1] + return utls.post_danjuan_fund(api_ref.funds_remove, payload) \ No newline at end of file diff --git a/pysnowball/realtime.py b/pysnowball/realtime.py index 85b8b6f..519b27c 100644 --- a/pysnowball/realtime.py +++ b/pysnowball/realtime.py @@ -17,6 +17,12 @@ def pankou(symbol): url = api_ref.realtime_pankou+symbol return utls.fetch(url) - -def kline(symbol,period='day',count=284): - return utls.fetch(api_ref.kline.format(symbol, int(time.time()*1000), period, count)) +def kline(symbol,begin=int(time.time()*1000),period='day',count=284): + ''' Get kline data + Args: + symbol: stock symbol, e.g. 'SZ000001' + begain: timestamp in milliseconds = int(time.time()*1000), default current time + count: number of data points to fetch, default 284 (fetch all available data) + period: '1m', '5m', '15m', '30m', '60m', 'day', 'week', 'month', 'quarter','year' + ''' + return utls.fetch(api_ref.kline.format(symbol, begin, period, count)) diff --git a/pysnowball/token.py b/pysnowball/token.py index f4aff15..f6b0cd8 100644 --- a/pysnowball/token.py +++ b/pysnowball/token.py @@ -7,6 +7,16 @@ def get_token(): else: return os.environ['XUEQIUTOKEN'] -def set_token(token): +def get_danjuan_token(): + if os.environ.get('DANJUANTOKEN') is None: + raise Exception(cons.NOTOKEN_ERROR_MSG) + else: + return os.environ['DANJUANTOKEN'] + +def set_token(token,danjuantoken=None): os.environ['XUEQIUTOKEN'] = token + if danjuantoken: + os.environ['DANJUANTOKEN'] = danjuantoken + else: + os.environ['DANJUANTOKEN'] = token return os.environ['XUEQIUTOKEN'] diff --git a/pysnowball/user.py b/pysnowball/user.py index 40c8fa9..fa8fc16 100644 --- a/pysnowball/user.py +++ b/pysnowball/user.py @@ -7,4 +7,8 @@ def watch_list(): def watch_stock(id): url = api_ref.watch_stock + str(id) + return utls.fetch(url) + +def watch_funds(id): + url = api_ref.watch_funds + str(id) return utls.fetch(url) \ No newline at end of file diff --git a/pysnowball/utls.py b/pysnowball/utls.py index e45e2cb..ae7587f 100644 --- a/pysnowball/utls.py +++ b/pysnowball/utls.py @@ -114,10 +114,14 @@ def fetch_hkc(url, txt_date=None): return response.content -def fetch_danjuan_fund(url): - fund_header = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36', - } +def fetch_danjuan_fund(url,host="danjuanfunds.com"): + fund_header = {'Host': host, + 'Accept': 'application/json', + 'Cookie': token.get_danjuan_token(), + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36', + 'Accept-Language': 'zh-Hans-CN;q=1, ja-JP;q=0.9', + 'Accept-Encoding': 'br, gzip, deflate', + 'Connection': 'keep-alive'} response = requests.request(method="GET", url=url, headers=fund_header) @@ -125,3 +129,20 @@ def fetch_danjuan_fund(url): raise Exception(response.content) return response.json() + +def post_danjuan_fund(url, payload, host="danjuanfunds.com"): + fund_header = {'Host': host, + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': token.get_danjuan_token(), + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36', + 'Accept-Language': 'zh-Hans-CN;q=1, ja-JP;q=0.9', + 'Accept-Encoding': 'br, gzip, deflate', + 'Connection': 'keep-alive'} + + response = requests.post(url, headers=fund_header, data=payload) + + if response.status_code != 200: + raise Exception(response.content) + + return response.json() \ No newline at end of file