Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Screener Feature #2066

Merged
merged 2 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,17 @@ software_ticker = software.ticker
software_ticker.history()
```

### Market Screener
The `Screener` module allows you to screen the market based on specified queries.

#### Query Construction
To create a query, you can use the `EquityQuery` class to construct your filters step by step. The queries support operators: `GT` (greater than), `LT` (less than), `BTWN` (between), `EQ` (equals), and logical operators `AND` and `OR` for combining multiple conditions.

#### Screener
The `Screener` class is used to execute the queries and return the filtered results. You can set a custom body for the screener or use predefined configurations.

<!-- TODO: link to Github Pages for more including list of predefined bodies, supported fields, operands, and sample code -->

### Logging

`yfinance` now uses the `logging` module to handle messages, default behaviour is only print errors. If debugging, use `yf.enable_debug_mode()` to switch logging to debug with custom formatting.
Expand Down
133 changes: 133 additions & 0 deletions tests/test_screener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import unittest
from unittest.mock import patch, MagicMock
from yfinance.const import PREDEFINED_SCREENER_BODY_MAP
from yfinance.screener.screener import Screener
from yfinance.screener.screener_query import EquityQuery


class TestScreener(unittest.TestCase):

@classmethod
def setUpClass(self):
self.screener = Screener()
self.query = EquityQuery('gt',['eodprice',3])

def test_set_default_body(self):
self.screener.set_default_body(self.query)

self.assertEqual(self.screener.body['offset'], 0)
self.assertEqual(self.screener.body['size'], 100)
self.assertEqual(self.screener.body['sortField'], 'ticker')
self.assertEqual(self.screener.body['sortType'], 'desc')
self.assertEqual(self.screener.body['quoteType'], 'equity')
self.assertEqual(self.screener.body['query'], self.query.to_dict())
self.assertEqual(self.screener.body['userId'], '')
self.assertEqual(self.screener.body['userIdType'], 'guid')

def test_set_predefined_body(self):
k = 'most_actives'
self.screener.set_predefined_body(k)
self.assertEqual(self.screener.body, PREDEFINED_SCREENER_BODY_MAP[k])

def test_set_predefined_body_invalid_key(self):
with self.assertRaises(ValueError):
self.screener.set_predefined_body('invalid_key')

def test_set_body(self):
body = {
"offset": 0,
"size": 100,
"sortField": "ticker",
"sortType": "desc",
"quoteType": "equity",
"query": self.query.to_dict(),
"userId": "",
"userIdType": "guid"
}
self.screener.set_body(body)

self.assertEqual(self.screener.body, body)

def test_set_body_missing_keys(self):
body = {
"offset": 0,
"size": 100,
"sortField": "ticker",
"sortType": "desc",
"quoteType": "equity"
}
with self.assertRaises(ValueError):
self.screener.set_body(body)

def test_set_body_extra_keys(self):
body = {
"offset": 0,
"size": 100,
"sortField": "ticker",
"sortType": "desc",
"quoteType": "equity",
"query": self.query.to_dict(),
"userId": "",
"userIdType": "guid",
"extraKey": "extraValue"
}
with self.assertRaises(ValueError):
self.screener.set_body(body)

def test_patch_body(self):
initial_body = {
"offset": 0,
"size": 100,
"sortField": "ticker",
"sortType": "desc",
"quoteType": "equity",
"query": self.query.to_dict(),
"userId": "",
"userIdType": "guid"
}
self.screener.set_body(initial_body)
patch_values = {"size": 50}
self.screener.patch_body(patch_values)

self.assertEqual(self.screener.body['size'], 50)
self.assertEqual(self.screener.body['query'], self.query.to_dict())

def test_patch_body_extra_keys(self):
initial_body = {
"offset": 0,
"size": 100,
"sortField": "ticker",
"sortType": "desc",
"quoteType": "equity",
"query": self.query.to_dict(),
"userId": "",
"userIdType": "guid"
}
self.screener.set_body(initial_body)
patch_values = {"extraKey": "extraValue"}
with self.assertRaises(ValueError):
self.screener.patch_body(patch_values)

@patch('yfinance.screener.screener.YfData.post')
def test_fetch(self, mock_post):
mock_response = MagicMock()
mock_response.json.return_value = {'finance': {'result': [{}]}}
mock_post.return_value = mock_response

self.screener.set_default_body(self.query)
response = self.screener._fetch()

self.assertEqual(response, {'finance': {'result': [{}]}})

@patch('yfinance.screener.screener.YfData.post')
def test_fetch_and_parse(self, mock_post):
mock_response = MagicMock()
mock_response.json.return_value = {'finance': {'result': [{'key': 'value'}]}}
mock_post.return_value = mock_response

self.screener.set_default_body(self.query)
self.screener._fetch_and_parse()
self.assertEqual(self.screener.response, {'key': 'value'})

if __name__ == '__main__':
unittest.main()
5 changes: 4 additions & 1 deletion yfinance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
from .cache import set_tz_cache_location
from .domain.sector import Sector
from .domain.industry import Industry
from .screener.screener import Screener
from .screener.screener_query import EquityQuery

__version__ = version.version
__author__ = "Ran Aroussi"

import warnings
warnings.filterwarnings('default', category=DeprecationWarning, module='^yfinance')

__all__ = ['download', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry']
__all__ = ['download', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry',
'EquityQuery','Screener']
Loading
Loading