Skip to content

Commit

Permalink
Moved some methods in column.py, and added between_pct()
Browse files Browse the repository at this point in the history
  • Loading branch information
shner-elmo committed Jun 19, 2024
1 parent 3ed345d commit 53eab3d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 57 deletions.
4 changes: 1 addition & 3 deletions src/tradingview_screener/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@
from __future__ import annotations

from tradingview_screener.screener import Scanner, get_all_symbols
from tradingview_screener.column import Column
from tradingview_screener.column import Column, col
from tradingview_screener.query import Query, And, Or

col = Column # create a short alias for simplicity
119 changes: 68 additions & 51 deletions src/tradingview_screener/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


if TYPE_CHECKING:
from typing import Literal, Optional, Iterable
from typing import Optional, Iterable
from tradingview_screener.query import FilterOperationDict


Expand Down Expand Up @@ -66,94 +66,111 @@ def crosses(self, other) -> FilterOperationDict:
return {'left': self.name, 'operation': 'crosses', 'right': self._extract_name(other)}

def crosses_above(self, other) -> FilterOperationDict:
return {
'left': self.name,
'operation': 'crosses_above',
'right': self._extract_name(other),
}
return {'left': self.name, 'operation': 'crosses_above', 'right': self._extract_name(other)}

def crosses_below(self, other) -> FilterOperationDict:
return {'left': self.name, 'operation': 'crosses_below', 'right': self._extract_name(other)}

def between(self, left, right) -> FilterOperationDict:
return {
'left': self.name,
'operation': 'crosses_below',
'right': self._extract_name(other),
'operation': 'in_range',
'right': [self._extract_name(left), self._extract_name(right)],
}

def _impl_above_below_pct(
self, operation: Literal['above%', 'below%'], column: Column | str, pct1, pct2=None
) -> FilterOperationDict:
if pct2:
return {
'left': self.name,
'operation': 'in_range%',
'right': [self._extract_name(column), *sorted([pct1, pct2])],
}
def not_between(self, left, right) -> FilterOperationDict:
return {
'left': self.name,
'operation': operation,
'right': [self._extract_name(column), pct1],
'operation': 'not_in_range',
'right': [self._extract_name(left), self._extract_name(right)],
}

def above_pct(
self, column: Column | str, pct1: float, pct2: Optional[float] = None
) -> FilterOperationDict:
def isin(self, values: Iterable) -> FilterOperationDict:
return {'left': self.name, 'operation': 'in_range', 'right': list(values)}

def not_in(self, values: Iterable) -> FilterOperationDict:
return {'left': self.name, 'operation': 'not_in_range', 'right': list(values)}

def has(self, values: Iterable) -> FilterOperationDict:
"""
Field contains any of the values
(it's the same as `isin()`, except that it works on fields of type `set`)
"""
return {'left': self.name, 'operation': 'has', 'right': list(values)}

def has_none_of(self, values: Iterable) -> FilterOperationDict:
"""
Field doesn't contain any of the values
(it's the same as `not_in()`, except that it works on fields of type `set`)
"""
return {'left': self.name, 'operation': 'has_none_of', 'right': list(values)}

def above_pct(self, column: Column | str, pct: float) -> FilterOperationDict:
"""
Examples:
The closing price is higher than the VWAP by more than 3%
>>> Column('close').above_pct('VWAP', 1.03)
The percentage change between the Close and the EMA is between 20% and 50%
>>> Column('close').above_pct('EMA200', 1.2, 1.5)
closing price is above the 52-week-low by more than 150%
>>> Column('close').above_pct('price_52_week_low', 2.5)
"""
return self._impl_above_below_pct('above%', column=column, pct1=pct1, pct2=pct2)
return {
'left': self.name,
'operation': 'above%',
'right': [self._extract_name(column), pct],
}

def below_pct(
self, column: Column | str, pct1, pct2: Optional[float] = None
) -> FilterOperationDict:
def below_pct(self, column: Column | str, pct: float) -> FilterOperationDict:
"""
Examples:
TODO
The closing price is lower than the VWAP by 3% or more
>>> Column('close').below_pct('VWAP', 1.03)
"""
return self._impl_above_below_pct('below%', column=column, pct1=pct1, pct2=pct2)

def has(self, values: Iterable) -> FilterOperationDict:
"""
Field contains any of the values
return {
'left': self.name,
'operation': 'below%',
'right': [self._extract_name(column), pct],
}

(it's the same as isin(), except that it works on fields of type `set`)
def between_pct(
self, column: Column | str, pct1: float, pct2: Optional[float] = None
) -> FilterOperationDict:
"""
return {'left': self.name, 'operation': 'has', 'right': list(values)}
Examples:
def has_none_of(self, values: Iterable) -> FilterOperationDict:
"""
Field doesn't contain any of the values
The percentage change between the Close and the EMA is between 20% and 50%
>>> Column('close').between_pct('EMA200', 1.2, 1.5)
"""
return {'left': self.name, 'operation': 'has_none_of', 'right': list(values)}

def between(self, left, right) -> FilterOperationDict:
return {
'left': self.name,
'operation': 'in_range',
'right': [self._extract_name(left), self._extract_name(right)],
'operation': 'in_range%',
'right': [self._extract_name(column), pct1, pct2],
}

def not_between(self, left, right) -> FilterOperationDict:
def not_between_pct(
self, column: Column | str, pct1: float, pct2: Optional[float] = None
) -> FilterOperationDict:
"""
Examples:
The percentage change between the Close and the EMA is between 20% and 50%
>>> Column('close').not_between_pct('EMA200', 1.2, 1.5)
"""
return {
'left': self.name,
'operation': 'not_in_range',
'right': [self._extract_name(left), self._extract_name(right)],
'operation': 'not_in_range%',
'right': [self._extract_name(column), pct1, pct2],
}

def isin(self, values: Iterable) -> FilterOperationDict:
return {'left': self.name, 'operation': 'in_range', 'right': list(values)}

def like(self, other) -> FilterOperationDict:
return {'left': self.name, 'operation': 'match', 'right': self._extract_name(other)}

def __repr__(self) -> str:
return f'< Column({self.name!r}) >'


col = Column # create a short alias for convenience
9 changes: 6 additions & 3 deletions src/tradingview_screener/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import pandas as pd

from tradingview_screener.column import Column
from tradingview_screener.constants import MARKETS, HEADERS, URL
from tradingview_screener.constants import HEADERS, URL


if TYPE_CHECKING:
from typing import TypedDict, Any, Literal, Optional, NotRequired, Self
from typing import TypedDict, Any, Literal, NotRequired, Self

class FilterOperationDict(TypedDict):
left: str
Expand All @@ -31,6 +31,7 @@ class FilterOperationDict(TypedDict):
'above%',
'below%',
'in_range%',
'not_in_range%',
'has', # set must contain one of the values
'has_none_of', # set must NOT contain ANY of the values
]
Expand All @@ -45,6 +46,7 @@ class SymbolsDict(TypedDict, total=False):
query: dict[Literal['types'], list]
tickers: list[str]
symbolset: list[str]
watchlist: dict[Literal['id'], int]

class ExpressionDict(TypedDict):
expression: FilterOperationDict
Expand Down Expand Up @@ -486,7 +488,8 @@ def set_index(self, *indexes: str) -> Self:
9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11)
You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES),
just note that the syntax is `SYML:{source};{ticker}`.
just note that the syntax is
`SYML:{source};{symbol}`.
:param indexes: One or more strings representing the financial indexes to filter by
:return: An instance of the `Query` class with the filter applied
Expand Down

0 comments on commit 53eab3d

Please sign in to comment.