diff --git a/README.md b/README.md index 2aa866b..b1fe4e7 100644 --- a/README.md +++ b/README.md @@ -54,18 +54,19 @@ For more detailed guide for price fetching, read (.*?)') + item_re = re.compile( + r'(\d{4}-\d{2}-\d{2})(.*?)(.*?)' + '(.*?)(.*?)(.*?)', + re.X) + header_match = re.compile( + r'单位净值累计净值日增长率' + '申购状态赎回状态.*?分红送配') + table = tr_re.findall(page) + if not header_match.match(table[0]): + raise UnsupportTickerError + try: + table = [(datetime.datetime.fromisoformat(t[0]). + replace(hour=15, tzinfo=TIMEZONE), Decimal(t[1])) + for t in map(lambda x: item_re.match(x).groups(), table[1:])] + except AttributeError: + return None + return table + + +def get_price_series(ticker: str, time_begin: datetime.datetime, time_end: datetime.datetime): + base_url = 'https://fundf10.eastmoney.com/F10DataApi.aspx' + time_delta_day = (time_end-time_begin).days+1 + pages = time_delta_day//30 + 1 + res = [] + for page in range(1, pages+1): + query = {'code': ticker, 'page': page, + 'sdate': time_begin.astimezone(TIMEZONE).date(), + 'edate': time_end.astimezone(TIMEZONE).date(), 'type': 'lsjz', 'per': 30} + response = requests.get(base_url, params=query, headers=headers) + if response.status_code != requests.codes.ok: + raise EastMoneyFundError( + f"Invalid response ({response.status_code}): {response.text}") + + price = parse_page(response.text) + if price is None and page == 1: + raise EastMoneyFundError( + f'Invalid ticker {ticker} or ' + f'search day {time_begin.date().isoformat()}~{time_end.date().isoformat()}') + if price is None: + break + res.extend(price) + return res + + +class Source(source.Source): + + def get_latest_price(self, ticker): + end_time = datetime.datetime.now(TIMEZONE) + begin_time = end_time - datetime.timedelta(days=10) + prices = get_price_series(ticker, begin_time, end_time) + last_price = prices[0] + return source.SourcePrice(last_price[1], last_price[0], CURRENCY) + + def get_historical_price(self, ticker, time): + prices = get_price_series( + ticker, time-datetime.timedelta(days=10), time) + last_price = prices[0] + return source.SourcePrice(last_price[1], last_price[0], CURRENCY) + + def get_prices_series(self, ticker, time_begin, time_end): + res = [source.SourcePrice(x[1], x[0], CURRENCY) + for x in get_price_series(ticker, time_begin, time_end)] + return sorted(res, key=lambda x: x.time) diff --git a/beanprice/sources/eastmoneyfund_test.py b/beanprice/sources/eastmoneyfund_test.py new file mode 100644 index 0000000..e445084 --- /dev/null +++ b/beanprice/sources/eastmoneyfund_test.py @@ -0,0 +1,84 @@ +import datetime +import unittest +from decimal import Decimal + +from unittest import mock +from dateutil import tz + +import requests + +import eastmoneyfund +from beanprice import source + + +contents = ''' +var apidata={ content:"
净值日期单位净值累计净值日增长率申购状态赎回状态分红送配
2020-10-095.18905.18904.11%开放申购开放赎回
2020-09-304.98404.98400.12%开放申购开放赎回
2020-09-294.97804.97801.14%开放申购开放赎回
2020-09-284.92204.92200.22%开放申购开放赎回
2020-09-254.91104.91100.88%开放申购开放赎回
2020-09-244.86804.8680-3.81%开放申购开放赎回
2020-09-235.06105.06102.41%开放申购开放赎回
2020-09-224.94204.9420-1.02%开放申购开放赎回
2020-09-214.99304.9930-1.29%开放申购开放赎回
2020-09-185.05805.05800.48%开放申购开放赎回
2020-09-175.03405.03400.60%开放申购开放赎回
2020-09-165.00405.0040-1.28%开放申购开放赎回
2020-09-155.06905.06901.06%开放申购开放赎回
2020-09-145.01605.01600.42%开放申购开放赎回
2020-09-114.99504.99503.39%开放申购开放赎回
2020-09-104.83104.8310-0.29%开放申购开放赎回
",records:16,pages:1,curpage:1};''' + +unsupport_content = ''' +var apidata={ content:"
净值日期每万份收益7日年化收益率(%)申购状态赎回状态分红送配
2020-09-100.42301.5730%开放申购开放赎回
",records:1,pages:1,curpage:1};''' + + +def response(contents, status_code=requests.codes.ok): + """Return a context manager to patch a JSON response.""" + response = mock.Mock() + response.status_code = status_code + response.text = contents + return mock.patch('requests.get', return_value=response) + + +class EastMoneyFundFetcher(unittest.TestCase): + + def test_error_network(self): + with response(None, 404): + with self.assertRaises(ValueError) as exc: + eastmoneyfund.get_price_series( + '377240', datetime.datetime.now(), datetime.datetime.now()) + + def test_unsupport_page(self): + with response(unsupport_content): + with self.assertRaises(ValueError) as exc: + eastmoneyfund.get_price_series( + '377240', datetime.datetime.now(), datetime.datetime.now()) + self.assertEqual( + eastmoneyfund.UnsupportTickerError, exc.exception) + + def test_latest_price(self): + with response(contents): + srcprice = eastmoneyfund.Source().get_latest_price('377240') + self.assertIsInstance(srcprice, source.SourcePrice) + self.assertEqual(Decimal('5.1890'), srcprice.price) + self.assertEqual('CNY', srcprice.quote_currency) + + def test_historical_price(self): + with response(contents): + time = datetime.datetime(2018, 3, 27, 0, 0, 0, tzinfo=tz.tzutc()) + srcprice = eastmoneyfund.Source().get_historical_price('377240', time) + self.assertIsInstance(srcprice, source.SourcePrice) + self.assertEqual(Decimal('5.1890'), srcprice.price) + self.assertEqual('CNY', srcprice.quote_currency) + self.assertEqual(datetime.datetime(2020, 10, 9, 15, 0, 0, + tzinfo=eastmoneyfund.TIMEZONE), + srcprice.time) + + def test_get_prices_series(self): + with response(contents): + time = datetime.datetime(2018, 3, 27, 0, 0, 0, tzinfo=tz.tzutc()) + srcprice = eastmoneyfund.Source().get_prices_series( + '377240', time-datetime.timedelta(days=10), time) + self.assertIsInstance(srcprice, list) + self.assertIsInstance(srcprice[-1], source.SourcePrice) + self.assertEqual(Decimal('5.1890'), srcprice[-1].price) + self.assertEqual('CNY', srcprice[-1].quote_currency) + self.assertEqual(datetime.datetime(2020, 10, 9, 15, 0, 0, + tzinfo=eastmoneyfund.TIMEZONE), + srcprice[-1].time) + self.assertIsInstance(srcprice[0], source.SourcePrice) + self.assertEqual(Decimal('4.8310'), srcprice[0].price) + self.assertEqual('CNY', srcprice[0].quote_currency) + self.assertEqual(datetime.datetime(2020, 9, 10, 15, 0, 0, + tzinfo=eastmoneyfund.TIMEZONE), + srcprice[0].time) + + +if __name__ == '__main__': + unittest.main()