def test_frac_pips(self, frac_pips: FracPips, pair: Pair, price: Price): p1 = frac_pips.to_ratio_price(pair.quote.rounder) p2 = frac_pips.to_quote_price(pair.quote) p3 = frac_pips.to_pair_price(pair) fp = FracPips.from_price(price) assert isinstance(p1, BasePrice) assert isinstance(p2, BasePrice) assert isinstance(p3, BasePrice) assert isinstance(fp, FracPips) assert price == p1 == p2 == p3 assert frac_pips == fp
def view_set(self, xandles: Xandles): """Set ourselves around the candles in xandles view.""" # DEPRECATING?....yrids should not need to know about price view. if self.height is None or self.scale is None: return low, high = xandles.find_view_low_high() if low is None or high is None: return fp_delta = high - low fp_pad = ceil(fp_delta / 10) self.top = FracPips(high + fp_pad) self.bot = FracPips(high - fp_pad) fp_height = fp_delta + (2 * fp_pad) self.fpp = fp_height / self.height self.update()
def get_grid_list(self, fp_low: FracPips, fp_high: FracPips) -> List[FracPips]: """Return list of fractional pips to put grid lines at. Args: fp_low: lowest price to place grid lines at or above fp_high: highest price to place grid lines at or below """ grid_list: List[FracPips] = [] remainder = fp_low % self.interval grid_fp = FracPips(fp_low - remainder) while grid_fp <= fp_high: grid_list.append(grid_fp) grid_fp = FracPips(grid_fp + self.interval) return grid_list
def shift(self, x: int, y: int): """Shift GeoCandles data the given amount. Args: x: horizontal pixels where to the right is positive. y: vertical pixels where down is positive. """ candle_slot_shift = ceil(x / self.xandles.offset) new_ndx = self.xandles.ndx - candle_slot_shift pad_adjust = Xandles.PAD * 4 min_slots = round( (self.xandles.width - pad_adjust) / self.xandles.offset) if new_ndx < 0: new_ndx = 0 pull_size = Xandles.calculate_pull_size(self.xandles.width, self.xandles.offset, new_ndx) candles = self.collector.grab(pull_size) max_ndx = len(candles) - min_slots if max_ndx <= 0: max_ndx = 1 if new_ndx >= max_ndx: new_ndx = max_ndx self.xandles.update(candles=candles, ndx=new_ndx) if self.price_view: fp_mid, fpp = Yrids.calculate_price_view(self.xandles, self.yrids.height) self.yrids.update(mid=fp_mid, fpp=fpp) else: fp_shift = round(y * self.yrids.fpp) new_mid = FracPips(self.yrids.mid - fp_shift) self.yrids.update(mid=new_mid) self.refresh()
def __init__( self, height: Optional[int] = None, mid: Optional[FracPips] = None, fpp: float = PriceScale.DEFAULT_FPP, scale: PriceScale = PriceScale.DEFAULT, ): # ---------------------------------------------------------------------- # User set attributes # ---------------------------------------------------------------------- # the height of the view in pixels self.height: Optional[int] = height # the middle price in fractional pips self.mid: FracPips = None if mid is None else FracPips(mid) # fpp is the ratio of fractional pips to vertical pixel. self.fpp: float = fpp # the grid scale for price (how many fpp between grid lines) self.scale: PriceScale = scale # ---------------------------------------------------------------------- # Attributes derived from update when user attributes are all set # ---------------------------------------------------------------------- # The height if the scrollable area of canvas. self.scroll_height: Optional[int] = None # list of frac pips and pixel locations for grid lines. self.grid_list: Optional[List[Tuple[FracPips, int]]] = None # the price in fractional pips at top and bottom of viewable area. self.top: Optional[FracPips] = None self.bot: Optional[FracPips] = None # the price in fractional pips at top and bottom of scrollable area. self.scroll_top: Optional[FracPips] = None self.scroll_bot: Optional[FracPips] = None # ---------------------------------------------------------------------- # Update provided we have enough info to. # ---------------------------------------------------------------------- self.update()
def test_price(self, value, subclass, pips, frac_pips): if subclass is ValueError: with pytest.raises(ValueError): Price(value) else: price = Price(value) assert price.pips == pips assert FracPips.from_price(price) == frac_pips assert isinstance(price, BasePrice) assert isinstance(price, subclass)
class TestFracPips: @pytest.mark.parametrize( "frac_pips,pair,price", ( (FracPips(112345), Pair.AUD_USD, Price("1.12345")), (FracPips(100123), Pair.USD_JPY, Price("100.123")), ), ) def test_frac_pips(self, frac_pips: FracPips, pair: Pair, price: Price): p1 = frac_pips.to_ratio_price(pair.quote.rounder) p2 = frac_pips.to_quote_price(pair.quote) p3 = frac_pips.to_pair_price(pair) fp = FracPips.from_price(price) assert isinstance(p1, BasePrice) assert isinstance(p2, BasePrice) assert isinstance(p3, BasePrice) assert isinstance(fp, FracPips) assert price == p1 == p2 == p3 assert frac_pips == fp
class Const: DEFAULT_FPP = 10.0 # initial frac pips per pixel value before data loaded. FP_MAX: FracPips = FracPips(1_000_000) FP_MIN: FracPips = FracPips(0) MIN_FPP = 0.01 # lowest allowed value for fpp ONE_THIRD = float(1) / float(3) PRICE_PAD: FracPips = FracPips(10_000) PRICE_VIEW_PAD = 25 TAIL_PADDING = 50 # num pixels to pad to right of candles when tailing. TIME_CANVAS_HEIGHT = 42 PRICE_CANVAS_WIDTH = 80 # 110 MIN_TIME_GRID_WIDTH = 120
def update( self, height: Optional[int] = None, mid: Optional[FracPips] = None, fpp: Optional[float] = None, scale: Optional[PriceScale] = None, ) -> bool: """Update Yrids with specified values, resolve if possible. All the args are optional, and if left as None they will stay the same as they already were. Args: height: height of view in in pixels. mid: frac pips price of mid point of view. fpp: ratio of frac pips per pixel scale: price scale object indicating frac pips between price grid lines. """ if height is not None: self.height = height if mid is not None: self.mid = mid if fpp is not None: self.fpp = fpp if scale is not None: self.scale = scale if not self.can_resolve(): return False self.scroll_height = self.height * 3 fp_view_delta = round((self.height / 2) * self.fpp) fp_scroll_delta = round((self.height / 2) * self.fpp * 3) self.top = FracPips(self.mid + fp_view_delta) self.bot = FracPips(self.mid - fp_view_delta) self.scroll_top = FracPips(self.mid + fp_scroll_delta) self.scroll_bot = FracPips(self.mid - fp_scroll_delta) self.grid_list = [] for fp in self.scale.get_grid_list(self.scroll_bot, self.scroll_top): y = self.fp_to_y(fp) self.grid_list.append((fp, y)) return True
def calculate_price_view(cls, xandles: Xandles, height: int) -> Tuple[FracPips, float]: """Calculate the price_view fp_mid and fpp from Xandles and pixel height. Args: xandles: must be loaded with candles we can get prices from. height: height of view area price view will cover. Returns: frac pip price of mid point of view, Frac pips per pixel ratio """ low, high = xandles.find_view_low_high() fp_mid = FracPips(round((high + low) / 2)) fp_delta = high - low fp_pad = ceil(fp_delta / 10) fp_height = fp_delta + (2 * fp_pad) fpp = fp_height / height return fp_mid, fpp
def find_view_low_high( self) -> Tuple[Optional[FracPips], Optional[FracPips]]: """Return the lowest and highest price in view as frac pips. If there are no prices in the view, then return None, None instead. """ if not self.candles: return None, None high = FracPips(0) low = FracPips(1_000_000) for candle in self.iter_view_candles(): candle_high = candle.high_fp candle_low = candle.low_fp if candle_high > high: high = candle_high if candle_low < low: low = candle_low if low > high: return None, None return low, high
def c_fp(self) -> FracPips: """Closing price as Fractional Pips.""" return FracPips.from_price(self.c)
def l_fp(self) -> FracPips: """Low price as Fractional Pips.""" return FracPips.from_price(self.l)
def h_fp(self) -> FracPips: """High price as Fractional Pips.""" return FracPips.from_price(self.h)
def o_fp(self) -> FracPips: """Open price as Fractional Pips.""" return FracPips.from_price(self.o)
def y_to_fp(self, y: int) -> FracPips: """Convert y pixel coordinate to price in frac pips.""" return FracPips(self.scroll_top - round(y * self.fpp))
def price_to_y(self, price: Price) -> int: """Convert Price to y pixel coordinate.""" return self.fp_to_y(FracPips.from_price(price))
class PriceScale: """For finding where to put price grid lines on scale.""" NUM_GRID = 6 MIN_PIXEL = 80 DEFAULT_FPP: float = 1.0 DEFAULT: "PriceScale" = None # Different numbers of pips we might have between each grid line. INTERVALS = ( FracPips(10), FracPips(25), FracPips(50), FracPips(100), FracPips(250), FracPips(500), FracPips(1000), FracPips(2500), FracPips(5000), FracPips(10_000), FracPips(25_000), FracPips(50_000), FracPips(100_000), FracPips(250_000), FracPips(500_000), FracPips(1_000_000), FracPips(2_500_000), FracPips(5_000_000), FracPips(10_000_000), FracPips(25_000_000), FracPips(50_000_000), FracPips(100_000_000), FracPips(250_000_000), FracPips(500_000_000), ) def __init__(self, fpp: float): """Set up PriceScale based on fractional pips per pixel. The self.interval attribute is loaded with the smallest fractional pip value from self.INTERVALS which will result in the grid lines being at least self.MIN_PIXEL pixels away from each other. If there is no such interval we just pick the biggest. Args: fpp: fractional pips per pixel. """ self.interval: FracPips = self.INTERVALS[-1] for interval in self.INTERVALS: pixels: float = float(interval) / fpp if pixels >= self.MIN_PIXEL: self.interval = interval break def __repr__(self): return f"<Scale {self.interval}>" def get_grid_list(self, fp_low: FracPips, fp_high: FracPips) -> List[FracPips]: """Return list of fractional pips to put grid lines at. Args: fp_low: lowest price to place grid lines at or above fp_high: highest price to place grid lines at or below """ grid_list: List[FracPips] = [] remainder = fp_low % self.interval grid_fp = FracPips(fp_low - remainder) while grid_fp <= fp_high: grid_list.append(grid_fp) grid_fp = FracPips(grid_fp + self.interval) return grid_list