def trim_list(cls, quotes: List['Quote'], domain: Any = None) -> List['Quote']: """ Removes empty quotes from the start and end of the list. If a domain is specified, only empty quotes outside of the domain are removed. """ if len(quotes) == 0: return [] i0: Optional[int] = None i1: Optional[int] = None r = range(len(quotes)) if domain is not None: domain = Interval.parse(domain) for i in r: q = quotes[i] if not q.is_empty or (domain is not None and domain.intersects(q.domain)): i0 = i break if i0 is None: return [] for i in reversed(r): q = quotes[i] if not q.is_empty or (domain is not None and domain.intersects(q.domain)): i1 = i break assert i1 is not None return quotes[i0:i1 + 1]
def span_interval(self, interval, start_open=False) -> Interval: """ Returns the time interval which fully contains the specified interval. """ interval = Interval.parse(interval, default_inf=True) if interval.is_empty: return Interval.empty() elif interval.start == interval.end: return self.span_date(interval.start, start_open=start_open) end_open = not start_open if interval.is_negative_infinite: start = -math.inf else: # Move outward if interval is closed but should be open o = not interval.start_open and start_open span = self.span_date(interval.start, start_open=o) start = span.start if interval.is_positive_infinite: end = math.inf else: # Move outward if interval is closed but should be open o = not interval.end_open and end_open span = self.span_date(interval.end, start_open=not o) end = span.end return Interval(start, end, start_open=start_open, end_open=not start_open)
def __init__(self, y, domain=None): super().__init__(min_step=math.inf) self._value = y if domain is not None: domain = Interval.parse(domain) else: domain = Interval.infinite() self._const_interval = domain
def test_parse(): d = Interval.parse(Interval(0, 1, start_open=True, end_open=True)) assert d.start == 0 assert d.end == 1 assert d.start_open assert d.end_open d = Interval.parse((0, 1)) assert d.start == 0 assert d.end == 1 assert not d.start_open assert not d.end_open d = Interval.parse(1) assert d.start == 1 assert d.end == 1 assert not d.start_open assert not d.end_open with pytest.raises(Exception): _ = Interval.parse(None) with pytest.raises(Exception): _ = Interval.parse(None, default_inf=False) assert Interval.parse(None, default_inf=True) == Interval.infinite() d = Interval.parse(math.inf) assert math.isinf(d.start) assert math.isinf(d.end) assert d.start > 0 assert d.end > 0 assert not d.is_negative_infinite assert not d.is_positive_infinite d = Interval.parse(-math.inf) assert math.isinf(d.start) assert math.isinf(d.end) assert d.start < 0 assert d.end < 0 assert not d.is_negative_infinite assert not d.is_positive_infinite d = Interval.parse([]) assert d.is_empty
def reset(self, domain=None): points_len = len(self._points) if points_len == 0: return if domain is None: return self.set([]) domain = Interval.parse(domain) if not self.domain.intersects(domain): return if domain.is_superset_of(self.domain): self.set([]) return remove_start_i, remove_end_i = self._domain_indexes(domain) if remove_start_i == remove_end_i: # Nothing to remove return head = self._points[:remove_start_i] tail = self._points[remove_end_i:] if len(head) != 0 and domain.contains(head[-1][0]): del head[-1] if len(tail) != 0 and domain.contains(tail[0][0]): del tail[0] head_len = len(head) tail_len = len(tail) if head_len + tail_len == 0: self.set([]) return points = head + tail update_start = self._points[0][0] update_start_open = False if head_len != 0: update_start = head[-1][0] update_start_open = True update_end = self._points[-1][0] update_end_open = False if tail_len != 0: update_end = tail[0][0] update_end_open = True update_domain = Interval(update_start, update_end, start_open=update_start_open, end_open=update_end_open) self.set(points, update_domain=update_domain)
def empty_list(cls, price: float, resolution: Any, domain: Any) -> List['Quote']: domain = Interval.parse(domain) quotes: List[Quote] = [] if not domain.is_empty: if not domain.is_finite: raise ValueError('Must specify a finite domain') resolution = Duration.parse(resolution) ohlc = [price] * 4 for span in resolution.iterate(domain, start_open=DOMAIN_START_OPEN): q = Quote(ohlc, date=span.start, volume=0.0, resolution=resolution) quotes.append(q) return quotes
def add_observer(self, *obj, domain=None, begin=None, end=None, autoremove=False, prioritize=False): if begin is None and end is None: return 0 Curve._token_counter += 1 token = Curve._token_counter domain = Interval.parse(domain, default_inf=True) obj_ref = None if len(obj) != 0: if autoremove: # Remove observer automatically obj_ref = weakref.ref(obj[0], lambda _: self.remove_observer(token)) else: # Calling remove_observer() is required obj_ref = weakref.ref(obj[0]) elif autoremove: raise Exception('Autoremoving an observer requires an object') # Do the callback functions require the domain? begin_with_interval = False end_with_interval = False if begin: begin_with_interval = util.count_positional_args(begin) == 1 if end: end_with_interval = util.count_positional_args(end) == 1 # TODO: does saving strong references to callbacks create a retain cycle? self._observer_data[token] = (obj_ref, domain, begin, end, begin_with_interval, end_with_interval) if prioritize: self._ordered_observer_tokens.insert(0, token) else: self._ordered_observer_tokens.append(token) return token
def missing_domains(cls, quotes, domain=None): # TODO: optimise by recursively dividing quotes # in half and checking if the quotes are # contiguous. Then collect domains between # contiguous quotes. quotes_len = len(quotes) if quotes_len == 0: if domain is None: return [] else: return [domain] if domain is None: domain = cls.list_domain(quotes) else: domain = Interval.parse(domain) if domain.is_empty: return [] missing_list = [] head = Interval.intersection([domain, quotes[0].domain.get_lt()]) if not head.is_empty: missing_list.append(head) for i in range(1, quotes_len): q0 = quotes[i - 1] q1 = quotes[i] if q0.end_date != q1.start_date: if q0.end_date > q1.start_date: continue missing = Interval(q0.domain.end, q1.domain.start, start_open=not q0.domain.end_open, end_open=not q1.domain.start_open) missing_list.append(missing) tail = Interval.intersection([domain, quotes[-1].domain.get_gt()]) if not tail.is_empty: missing_list.append(tail) return missing_list
def count(self, interval, start_open=False) -> int: """ Returns the number of intervals in the specified interval. """ interval = Interval.parse(interval, default_inf=True) if interval.is_empty: return 0 if not interval.is_finite: raise Exception('Cannot count intervals on infinite interval') interval = self.span_interval(interval, start_open=start_open) if not self.is_uniform: # Walk spans # TODO: this can be optimised to avoid walking count = 0 for _ in self.iterate(interval): count += 1 return count else: # Use seconds return int(math.ceil(interval.length / self.ave_seconds))
def inside_domain(quotes, domain): """ Returns quotes inside the domain. """ domain = Interval.parse(domain) if domain.is_empty: return [] if domain.is_infinite: return list(quotes) quotes_len = len(quotes) if quotes_len == 0: return [] i0 = max(0, Quote.bisect(quotes, domain.start) - 1) i1 = min(quotes_len, Quote.bisect(quotes, domain.end) + 1) matching_quotes = quotes[i0:i1] while len(matching_quotes) != 0 and not domain.intersects( matching_quotes[0].domain): del matching_quotes[0] while len(matching_quotes) != 0 and not domain.intersects( matching_quotes[-1].domain): del matching_quotes[-1] return matching_quotes
def sample_points(self, domain=None, min_step=None, step=None): domain = Interval.parse(domain, default_inf=True) if self.domain.is_empty: return [] if self.interval is not None and ( (min_step is not None and min_step > self.interval) or (step is not None and step != self.interval)): # Irregular sampling return super().sample_points(domain=domain, min_step=min_step, step=step) if domain is None or domain.is_superset_of(self.domain): # Sample all return list(self._points) # Sample some domain = Interval.intersection([self.domain, domain]) if domain.is_empty: return [] i0, i1 = self._domain_indexes(domain) return self._points[i0:i1]
def iterate(self, interval, size=1, backward=False, start_open=False) -> Iterator[Optional[Interval]]: """ Iterates time-spans inside a interval. Does not trim timespans inside the interval. """ interval = Interval.parse(interval, default_inf=True) if interval.is_empty: return iter([]) start = interval.start if not backward else interval.end if math.isinf(start): raise ValueError('Cannot iterate intervals from infinity') if size < 1: raise ValueError('Size must ba a positive integer') if self.is_uniform: interval_span = self.span_interval(interval, start_open=start_open) start = interval_span.start if not backward else interval_span.end count = math.ceil( self.count(interval_span, start_open=start_open) / size) step = size * self.ave_seconds if backward: step = -step i = 0 def next_uniform_span(): nonlocal start, step, i, count if i == count: return None a = start + i * step b = a + step i += 1 return Interval(min(a, b), max(a, b), start_open=start_open, end_open=not start_open) return iter(next_uniform_span, None) walker = self.walk(start, size=size, backward=backward, start_open=start_open) def next_span_in_interval(): nonlocal start while True: span = next(walker) if span is None or span.is_empty: return None elif span.intersects(interval): return span elif span.contains(start): # Keep looking for interval continue else: # Went outside interval return None return None return iter(next_span_in_interval, None)
def is_in_extension(self, x): domain = Interval.parse(x) return (self.start and domain <= self.extension_interval) or \ (self.end and domain >= self.extension_interval)
def __init__(self, y_func, domain=None, min_step=MIN_STEP): super().__init__(min_step=min_step) self.y_func = y_func self._domain = Interval.parse(domain) if domain is not None else Interval.infinite()