"""Base classes.""" from enum import Enum, auto import numpy as np import pandas as pd from .const import ChartType, TimeFrame __all__ = ('Indicator', 'Symbol', 'Quotes') # I actually can't think of a worse reason to override an array than # this: # - a method .new() that mutates the data from an input data frame # - mutating the time column wholesale based on a setting # - enforcing certain fields / columns # - zero overriding of any of the array interface for the purposes of # a different underlying implementation. # Literally all this can be done in a simple function with way less # confusion for the reader. class BaseQuotes(np.recarray): def __new__(cls, shape=None, dtype=None, order='C'): dt = np.dtype( [ ('id', int), ('time', float), ('open', float), ('high', float), ('low', float), ('close', float), ('volume', int), ] ) shape = shape or (1,) return np.ndarray.__new__(cls, shape, (np.record, dt), order=order) def _nan_to_closest_num(self): """Return interpolated values instead of NaN.""" for col in ['open', 'high', 'low', 'close']: mask = np.isnan(self[col]) if not mask.size: continue self[col][mask] = np.interp( np.flatnonzero(mask), np.flatnonzero(~mask), self[col][~mask] ) def _set_time_frame(self, default_tf): tf = { 1: TimeFrame.M1, 5: TimeFrame.M5, 15: TimeFrame.M15, 30: TimeFrame.M30, 60: TimeFrame.H1, 240: TimeFrame.H4, 1440: TimeFrame.D1, } minutes = int(np.diff(self.time[-10:]).min() / 60) self.timeframe = tf.get(minutes) or tf[default_tf] # bruh this isn't creating anything it's copying data in # from a data frame... def new(self, data, source=None, default_tf=None): shape = (len(data),) self.resize(shape, refcheck=False) if isinstance(data, pd.DataFrame): data.reset_index(inplace=True) data.insert(0, 'id', data.index) data.Date = self.convert_dates(data.Date) data = data.rename( columns={ 'Date': 'time', 'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume', } ) for name in self.dtype.names: self[name] = data[name] elif isinstance(data, (np.recarray, BaseQuotes)): self[:] = data[:] self._nan_to_closest_num() self._set_time_frame(default_tf) return self def convert_dates(self, dates): breakpoint() return np.array([d.timestamp() for d in dates]) class SymbolType(Enum): FOREX = auto() CFD = auto() FUTURES = auto() SHARES = auto() class Symbol: FOREX = SymbolType.FOREX CFD = SymbolType.CFD FUTURES = SymbolType.FUTURES SHARES = SymbolType.SHARES def __init__(self, ticker, mode, tick_size=0, tick_value=None): self.ticker = ticker self.mode = mode if self.mode in [self.FOREX, self.CFD]: # number of units of the commodity, currency # or financial asset in one lot self.contract_size = 100_000 # (100000 == 1 Lot) elif self.mode == self.FUTURES: # cost of a single price change point ($10) / # one minimum price movement self.tick_value = tick_value # minimum price change step (0.0001) self.tick_size = tick_size if isinstance(tick_size, float): self.digits = len(str(tick_size).split('.')[1]) else: self.digits = 0 def __repr__(self): return 'Symbol (%s | %s)' % (self.ticker, self.mode) class Indicator: def __init__( self, label=None, window=None, data=None, tp=None, base=None, **kwargs ): self.label = label self.window = window self.data = data or [0] self.type = tp or ChartType.LINE self.base = base or {'linewidth': 0.5, 'color': 'black'} self.lineStyle = {'linestyle': '-', 'linewidth': 0.5, 'color': 'blue'} self.lineStyle.update(kwargs) # This creates a global array that seems to be shared between all # charting UI components Quotes = BaseQuotes()