[docs]classnsectime(int,metaclass=_nsectime):""" OneTick data type representing datetime with nanoseconds precision. Can be used to specify otp.Source column type when converting columns or creating new ones. Note that this constructor creates datetime value in GMT timezone and doesn't take into account the timezone with which the query is executed. Examples -------- >>> t = otp.Tick(A=0) >>> t['A'] = t['A'].apply(otp.nsectime) >>> t['B'] = otp.nsectime(24 * 60 * 60 * 1000 * 1000 * 1000 + 2) >>> t.schema {'A': <class 'onetick.py.types.nsectime'>, 'B': <class 'onetick.py.types.nsectime'>} >>> otp.run(t) Time A B 0 2003-12-01 1969-12-31 19:00:00 1970-01-01 19:00:00.000000002 """pass
[docs]classmsectime(int,metaclass=_msectime):""" OneTick data type representing datetime with milliseconds precision. Can be used to specify otp.Source column type when converting columns or creating new ones. Note that this constructor creates datetime value in GMT timezone and doesn't take into account the timezone with which the query is executed. Examples -------- >>> t = otp.Tick(A=1) >>> t = t.table(A=otp.msectime) >>> t['B'] = otp.msectime(2) >>> t.schema {'A': <class 'onetick.py.types.msectime'>, 'B': <class 'onetick.py.types.msectime'>} >>> otp.run(t) Time A B 0 2003-12-01 1969-12-31 19:00:00.001 1969-12-31 19:00:00.002 """pass
classOTPBaseTimeOffset:datepart="'invalid'"# that is just base class for other datepartsclassExpressionDefinedTimeOffset(OTPBaseTimeOffset):def__init__(self,datepart,n):self.datepart=datepartself.n=nfromonetick.py.core.column_operations.baseimport_Operationdefproxy_wrap(attr):deff(self,*args,**kwargs):returngetattr(self.n,attr)(*args,**kwargs)returnfforattr,valueininspect.getmembers(_Operation,callable):ifnothasattr(ExpressionDefinedTimeOffset,attr):setattr(ExpressionDefinedTimeOffset,attr,proxy_wrap(attr))# ---------------------------- ## Implement datepart unitsdef_construct_dpf(dp_class,str_repr=None,**dp_class_params):""" construct a datepart factory """ifstr_reprisNone:str_repr=dp_class.__name__.lower()class_DatePartCls(dp_class,OTPBaseTimeOffset):datepart=f"'{str_repr}'"def_factory(n):fromonetick.py.core.column_operations._methods.methodsimportis_arithmeticalfromonetick.py.core.columnimport_Columnifisinstance(n,int):ifdp_class_params:return_DatePartCls(**dp_class_params)*nreturn_DatePartCls(n)ifis_arithmetical(n):n=_process_datediff(n)returnExpressionDefinedTimeOffset(_DatePartCls.datepart,n)ifisinstance(n,_Column):returnExpressionDefinedTimeOffset(_DatePartCls.datepart,n)raiseValueError("Unknown type was passed as arg, integer constant or column or expression is expected here")def_process_datediff(n):n_time_operand=_get_n_time_operand(n)ifn_time_operand:# check if otp.Hour(date1 - date2) is called, return a number of hours between two days in such waysfromonetick.py.core.column_operations._methods.methodsimportsub,_wrap_objectfromonetick.py.core.column_operations.baseimport_Operationfromonetick.py.core.columnimport_Column,_LagOperatoravailable_types=(_Column,_LagOperator)if(getattr(n,"_op_func",sub)andlen(n._op_params)==2andisinstance(n._op_params[0],available_types)andisinstance(n._op_params[1],available_types)):def_datediff(*args):args=', '.join(map(_wrap_object,args))returnf'DATEDIFF({_DatePartCls.datepart}, {args}, _TIMEZONE)',intreturn_Operation(_datediff,[n._op_params[1],n._op_params[0]])else:raiseValueError("Date arithmetic operations (except date2-date1, which calculate an amount of ""periods between two dates) are not accepted in TimeOffset constructor")returnndef_get_n_time_operand(n):fromonetick.py.core.column_operations._methods.op_typesimportare_timeresult=0foropinevery_operand(n):ifare_time(get_object_type(op)):result+=1returnresultreturn_factory_add_examples_to_docs=""" Object representing {}'s datetime offset. Can be added to or subtracted from: * :py:class:`otp.datetime <onetick.py.datetime>` objects * :py:class:`Source <onetick.py.Source>` columns of datetime type Parameters ---------- n: int, :class:`~onetick.py.Column`, :class:`~onetick.py.Operation` Offset integer value or column of :class:`~onetick.py.Source`. The only :class:`~onetick.py.Operation` supported is subtracting one datetime column from another. See example below. Examples --------{}""".formatYear=_construct_dpf(offsets.DateOffset,"year",years=1)Year.__doc__=_add_examples_to_docs('year',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Year(1) 2013-12-12 12:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Year(1) 2011-12-12 12:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Year(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2013-12-12 12:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1)) >>> t['DIFF'] = otp.Year(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2023-01-01 1""")Quarter=_construct_dpf(offsets.DateOffset,"quarter",months=3)Quarter.__doc__=_add_examples_to_docs('quarter',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Quarter(1) 2013-03-12 12:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Quarter(1) 2012-09-12 12:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12, tz='GMT') >>> t['T'] += otp.Quarter(t['A']) >>> otp.run(t, start=otp.datetime(2003, 12, 2), end=otp.datetime(2003, 12, 3), timezone='GMT') Time T A 0 2003-12-02 2013-03-12 12:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1)) >>> t['DIFF'] = otp.Quarter(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2023-01-01 4""")Month=_construct_dpf(offsets.DateOffset,"month",months=1)Month.__doc__=_add_examples_to_docs('month',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Month(1) 2013-01-12 12:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Month(1) 2012-11-12 12:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Month(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2013-01-12 12:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1)) >>> t['DIFF'] = otp.Month(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2023-01-01 12""")Week=_construct_dpf(offsets.Week)Week.__doc__=_add_examples_to_docs('week',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Week(1) 2012-12-19 12:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Week(1) 2012-12-05 12:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Week(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-19 12:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1)) >>> t['DIFF'] = otp.Week(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2023-01-01 53""")Day=_construct_dpf(offsets.Day)Day.__doc__=_add_examples_to_docs('day',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Day(1) 2012-12-13 12:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Day(1) 2012-12-11 12:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Day(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-13 12:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1)) >>> t['DIFF'] = otp.Day(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2023-01-01 365""")Hour=_construct_dpf(offsets.Hour)Hour.__doc__=_add_examples_to_docs('hour',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Hour(1) 2012-12-12 13:00:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Hour(1) 2012-12-12 11:00:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Hour(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-12 13:00:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 2)) >>> t['DIFF'] = otp.Hour(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2022-01-02 24""")Minute=_construct_dpf(offsets.Minute)Minute.__doc__=_add_examples_to_docs('minute',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Minute(1) 2012-12-12 12:01:00 >>> otp.datetime(2012, 12, 12, 12) - otp.Minute(1) 2012-12-12 11:59:00 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Minute(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-12 12:01:00 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 1)) >>> t['DIFF'] = otp.Minute(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2022-01-01 01:00:00 60""")Second=_construct_dpf(offsets.Second)Second.__doc__=_add_examples_to_docs('second',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Second(1) 2012-12-12 12:00:01 >>> otp.datetime(2012, 12, 12, 12) - otp.Second(1) 2012-12-12 11:59:59 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Second(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-12 12:00:01 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 1)) >>> t['DIFF'] = otp.Second(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2022-01-01 00:01:00 60""")Milli=_construct_dpf(offsets.Milli,"millisecond")Milli.__doc__=_add_examples_to_docs('millisecond',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Milli(1) 2012-12-12 12:00:00.001000 >>> otp.datetime(2012, 12, 12, 12) - otp.Milli(1) 2012-12-12 11:59:59.999000 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Milli(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-12 12:00:00.001 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 0, 1)) >>> t['DIFF'] = otp.Milli(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2022-01-01 00:00:01 1000""")# microseconds are not supported yetNano=_construct_dpf(offsets.Nano,"nanosecond")Nano.__doc__=_add_examples_to_docs('nanosecond',""" Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object: >>> otp.datetime(2012, 12, 12, 12) + otp.Nano(1) 2012-12-12 12:00:00.000000001 >>> otp.datetime(2012, 12, 12, 12) - otp.Nano(1) 2012-12-12 11:59:59.999999999 Use offset in columns: >>> t = otp.Tick(A=1) >>> t['T'] = otp.datetime(2012, 12, 12, 12) >>> t['T'] += otp.Nano(t['A']) >>> otp.run(t) Time T A 0 2003-12-01 2012-12-12 12:00:00.000000001 1 Use it to calculate difference between two dates: >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 0, 1)) >>> t['DIFF'] = otp.Nano(t['B'] - t['A']) >>> otp.run(t) Time A B DIFF 0 2003-12-01 2022-01-01 2022-01-01 00:00:01 1000000000""")# ---------------------------- #class_inner_string(type):def__str__(cls):ifcls.lengthisEllipsis:return"varstring"ifcls.length:returnf"string[{cls.length}]"else:return"string"def__repr__(cls):returnstr(cls)# We have ot use functools.cache, because 'class' in python is an object,# and _inner_str for the same item is different for every call,# but we want to make str[1024] be equal to another str[1024]@functools.lru_cache(maxsize=None)# noqa: W1518def__getitem__(cls,item):class_inner_str(string,metaclass=_inner_string):# pylint: disable=E0601def__len__(self):returnself.__class__.lengthif(type(item)isnotintoritem<1)anditemisnotEllipsis:raiseTypeError("It is not allowed to have non numeric index")_inner_str.length=itemreturn_inner_str
[docs]classstring(str,metaclass=_inner_string):""" OneTick data type representing string with length and varstring. To set string length use ``__getitem__``. If the length is not set then the :py:attr:`~DEFAULT_LENGTH` length is used by default. In this case using ``otp.string`` is the same as using ``str``. If the length is set to Ellipse it represents varstring. Varstring is used for returning variably sized strings. Note ---- If you try to set value with length x to string[y] and x > y, value will be truncated to y length. Attributes ---------- DEFAULT_LENGTH: int default length of the string when the length is not specified Examples -------- >>> # OTdirective: skip-snippet:; >>> t = otp.Tick(A='a') >>> t = t.table(A=otp.string[10]) >>> t.schema {'A': string[10]} Example of truncation column value to set string length. >>> # OTdirective: skip-snippet:; >>> t['A'] *= 100 >>> t['B'] = t['A'].str.len() >>> otp.run(t) Time A B 0 2003-12-01 aaaaaaaaaa 10 Example of string with default length. >>> t = otp.Tick(A='a') >>> t['A'] *= 100 >>> t['B'] = t['A'].str.len() >>> otp.run(t) Time A B 0 2003-12-01 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 64 Setting Ellipsis as length represents varstring. >>> t = otp.Tick(A='a') >>> t = t.table(A=otp.string[...]) >>> t.schema {'A': varstring} Varstring length is multiplied. >>> t['A'] *= 65 >>> t['B'] = t['A'].str.len() >>> otp.run(t) Time A B 0 2003-12-01 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 65 `otp.varstring` is a shortcut: >>> t = otp.Tick(A='a') >>> t = t.table(A=otp.varstring) >>> t.schema {'A': varstring} """DEFAULT_LENGTH=64length=None
varstring=string[...]class_nan_base(type):def__str__(cls):return"double"class_nan(float,metaclass=_nan_base):""" Object that represents NaN (not a number) float value. Can be used anywhere where float value is expected. Examples -------- >>> t = otp.Ticks({'A': [1.1, 2.2, otp.nan]}) >>> t['B'] = otp.nan >>> t['C'] = t['A'] / 0 >>> t['D'] = t['A'] + otp.nan >>> otp.run(t) Time A B C D 0 2003-12-01 00:00:00.000 1.1 NaN inf NaN 1 2003-12-01 00:00:00.001 2.2 NaN inf NaN 2 2003-12-01 00:00:00.002 NaN NaN NaN NaN """__name__='nan'def__str__(self):return"NAN()"nan=_nan()class_inf(float,metaclass=_nan_base):""" Object that represents infinity value. Can be used anywhere where float value is expected. Examples -------- >>> t = otp.Ticks({'A': [1.1, 2.2, otp.inf]}) >>> t['B'] = otp.inf >>> t['C'] = t['A'] / 0 >>> t['D'] = t['A'] - otp.inf >>> otp.run(t) Time A B C D 0 2003-12-01 00:00:00.000 1.1 inf inf -inf 1 2003-12-01 00:00:00.001 2.2 inf inf -inf 2 2003-12-01 00:00:00.002 inf inf inf NaN """__name__='inf'def__init__(self):self._sign=""# empty string or '-' for negative infinitydef__str__(self):returnf"{self._sign}INFINITY()"def__neg__(self):result=_inf()result._sign=""ifself._signelse"-"returnresultinf=_inf()class_decimal_str(type):def__str__(cls):return'decimal'
[docs]classdecimal(float,metaclass=_decimal_str):""" Object that represents decimal OneTick value. Decimal is 128 bit base 10 floating point number. Examples -------- >>> t = otp.Ticks({'A': [otp.decimal(1), otp.decimal(2)]}) >>> t['B'] = otp.decimal(1.23456789) >>> t['C'] = t['A'] / 0 >>> t['D'] = t['A'] + otp.nan >>> otp.run(t) Time A B C D 0 2003-12-01 00:00:00.000 1.0 1.234568 inf NaN 1 2003-12-01 00:00:00.001 2.0 1.234568 inf NaN """def__add__(self,other):returnself.__class__(super().__add__(other))def__radd__(self,other):returnself.__class__(super().__radd__(other))def__sub__(self,other):returnself.__class__(super().__sub__(other))def__rsub__(self,other):returnself.__class__(super().__rsub__(other))def__mul__(self,other):returnself.__class__(super().__mul__(other))def__rmul__(self,other):returnself.__class__(super().__rmul__(other))def__truediv__(self,other):returnself.__class__(super().__truediv__(other))def__rtruediv__(self,other):returnself.__class__(super().__rtruediv__(other))def__str__(self):returnf'{float(self)}'def__repr__(self):returnf"{self.__class__.__name__}({self})"
# --------------------------------------------------------------- ## AUXILIARY FUNCTIONS# --------------------------------------------------------------- #defis_type_basic(dtype):returndtypein(int,float,str,byte,short,uint,ulong,nsectime,msectime,decimal,)orissubclass(dtype,string)# TODO: PY-632: unify these functions with othersdefget_source_base_type(value):ifinspect.isclass(value):value_type=valueifnotis_type_basic(value_type):warnings.warn('Setting schema with complex types is deprecated,'' use basic type instead',DeprecationWarning)else:warnings.warn('Setting schema with instance of the class is deprecated,'' use type instead',DeprecationWarning)value_type=type(value)# convert string to custom string if necessaryifvalue_typeisstrandlen(value)>string.DEFAULT_LENGTH:value_type=string[len(value)]ifissubclass(value_type,bool):value_type=floatifis_time_type(value_type):value_type=nsectime# check valid value typeifget_base_type(value_type)notin[int,float,str,bool]:raiseTypeError(f'Type "{repr(value_type)}" is not supported.')ifnotis_type_basic(value_type):raiseTypeError(f"Type {repr(value_type)} can't be set in schema.")returnvalue_typedefis_type_supported(dtype):returnget_base_type(dtype)in[int,float,str,bool]orissubclass(dtype,(datetime,date))defget_base_type(obj):ifissubclass(obj,str):returnstrelifissubclass(obj,bool):returnboolelifissubclass(obj,int):returnintelifissubclass(obj,float):returnfloatreturntype(None)defget_object_type(obj):ifisinstance(obj,(_nan,_inf)):returnfloatifisinstance(obj,typing.Type):returnobjelse:ifhasattr(obj,'dtype'):dtype=obj.dtypeifisinstance(dtype,np.dtype):returndtype.typereturndtypeelse:ifis_time_type(obj):returnnsectimereturntype(obj)defget_type_by_objects(objs):""" Helper that calculates the widest type of the list pased objects. Used to determine type by returned values. """# collect typestypes=set()forvinobjs:t=get_object_type(v)ifissubclass(t,str):t=strtypes.add(t)# does not allow to mix string and numeric typesdtype=Noneifstrintypesand(floatintypesorintintypesorboolintypesornsectimeintypesormsectimeintypes):raiseTypeError("It is not allowed to return values of string type and numeric type in one function.")# if there is only one value there, then# use it as isiflen(types)==1:dtype=next(iter(types))ifdtypeisbool:returndtype# process numeric types: the most generic is floatifintintypes:dtype=intifboolintypes:dtype=floatiffloatintypes:dtype=float# process string types, taking into account OneTick long stringsifstrintypes:max_len=string.DEFAULT_LENGTHforvinobjs:t=get_object_type(v)ifissubclass(t,string):max_len=max(t.length,max_len)elifisinstance(v,str):max_len=max(len(v),max_len)ifmax_len==string.DEFAULT_LENGTH:dtype=strelse:dtype=string[max_len]# pylint: disable=E1136# process msectime and nsectimeifdtypeisfloatand(msectimeintypesornsectimeintypes):raiseTypeError("It is not allowed to return value of time type and float type in one function.")ifmsectimeintypes:dtype=msectimeifnsectimeintypes:dtype=nsectime# we assume the None value has float default value, ie NaNiftype(None)isdtype:dtype=floatreturndtype# ------------------- ## extend datetimeclassAbstractTime:def__init__(self):self.ts:_timestamp@propertydefyear(self):returnself.ts.year@propertydefmonth(self):returnself.ts.month@propertydefday(self):returnself.ts.daydefdate(self):return_date(self.year,self.month,self.day)@propertydefstart(self):return_timestamp(self.year,self.month,self.day)@propertydefend(self):return_timestamp(next_day(self.start))defstrftime(self,fmt):returnself.ts.strftime(fmt)@propertydefvalue(self):returnself.ts.valuedeftimestamp(self):returnself.ts.timestamp()def__eq__(self,other):other=getattr(other,"ts",other)returnself.ts==otherdef__hash__(self):returnhash(self.ts)def__gt__(self,other):other=getattr(other,"ts",other)returnself.ts>otherdef__ge__(self,other):other=getattr(other,"ts",other)returnself.ts>=otherdef__lt__(self,other):other=getattr(other,"ts",other)returnself.ts<otherdef__le__(self,other):other=getattr(other,"ts",other)returnself.ts<=other
[docs]classdatetime(AbstractTime):""" Class `datetime` is used for representing date with time in onetick-py. It can be used both when specifying start and end time for queries and in column operations with :py:class:`onetick.py.Source`. :ref:`Datetime offset objects <datetime_offsets>` (e.g. `otp.Nano`, `otp.Day`) can be added to or subtracted from `otp.datetime` object. Parameters ---------- first_arg: int, str, otp.datetime, pandas.Timestamp, datetime.datetime If `month`, `day` and other parts of date are specified, first argument will be considered as year. Otherwise, first argument will be converted to otp.datetime. month: int Number between 1 and 12. day: int Number between 1 and 31. hour: int, default=0 Number between 0 and 23. minute: int, default=0 Number between 0 and 59. second: int, default=0 Number between 0 and 59. microsecond: int, default=0 Number between 0 and 999999. nanosecond: int, default=0 Number between 0 and 999. tzinfo: datetime.tzinfo Timezone object. tz: str Timezone name. Examples -------- Initialization by datetime.datetime class from standard library >>> otp.datetime(datetime(2019, 1, 1, 1)) 2019-01-01 01:00:00 Initialization by pandas Timestamp class >>> otp.datetime(pd.Timestamp(2019, 1, 1, 1)) 2019-01-01 01:00:00 Initialization by int timestamp >>> otp.datetime(1234567890) 1970-01-01 00:00:01.234567890 Initialization by params with nanoseconds >>> otp.datetime(2019, 1, 1, 1, 2, 3, 4, 5) 2019-01-01 01:02:03.000004005 Initialization by string >>> otp.datetime("2019/01/01 1:02") 2019-01-01 01:02:00 `otp.dt` is the alias for `otp.datetime` >>> otp.dt(2019, 1, 1) 2019-01-01 00:00:00 See also -------- :ref:`Datetime offset objects <datetime_guide>`. """def__init__(self,first_arg,month=None,day=None,hour=None,minute=None,second=None,microsecond=None,nanosecond=None,*,tzinfo=None,tz=None,):# TODO: python 3.8 change first_arg to positional only argtz,tzinfo=self._process_timezones_args(tz,tzinfo)ifnotany([month,day,hour,minute,second,microsecond,nanosecond]):result=self._create_from_one_arg(first_arg,tz,tzinfo)else:result=self._create_from_several_arg(first_arg,month,day,hour,minute,second,microsecond,nanosecond,tzinfo)self.ts=resultdef_process_timezones_args(self,tz,tzinfo):iftzisnotNone:iftzinfoisNone:tzinfo=get_tzfile_by_name(tz)# pandas is broken https://github.com/pandas-dev/pandas/issues/31929tz=Noneelse:raiseValueError("tzinfo and tz params are mutually exclusive parameters, ""they can't be specified both at the same time")returntz,tzinfodef_create_from_several_arg(self,first_arg,month,day,hour,minute,second,microsecond,nanosecond,tzinfo):ifnanosecondisnotNoneandnot(0<=nanosecond<=999):raiseValueError("Nanosecond parameter should be between 0 and 999. ""Please use microsecond parameter or otp.Nano object.")result=_timestamp(first_arg,month,day,houror0,minuteor0,secondor0,microsecondor0,nanosecondor0).replace(tzinfo=tzinfo)returnresultdef_create_from_one_arg(self,first_arg,tz,tzinfo):arg_tz=getattr(first_arg,"tz",None)arg_tzinfo=getattr(first_arg,"tzinfo",None)iftzandarg_tzandarg_tz!=tzortzinfoandarg_tzinfoandarg_tzinfo!=tzinfo:raiseValueError("You've specified the timezone for the object, which already has it. ""It is recommended to swap the current timezone to desired by method of this object ""and then create otp.datetime object.")ifisinstance(first_arg,(datetime,date)):first_arg=first_arg.tsresult=_timestamp(first_arg,tzinfo=tzinfo,tz=tz)returnresult@propertydefstart(self):returnsuper().start.replace(tzinfo=self.tzinfo)@propertydefend(self):returnsuper().end.replace(tzinfo=self.tzinfo)
[docs]@staticmethoddefnow(tz=None):""" Will return `otp.datetime` object with timestamp at the moment of calling this function. Not to be confused with function :func:`otp.now` which can only add column with current timestamp to the `otp.Source` when running the query. Parameters ---------- tz : str or timezone object, default None Timezone to localize to. """returndatetime_now(_timestamp.now(tz))
[docs]def__add__(self,other):""" Add :ref:`datetime offset <datetime_offsets>` to otp.datetime. Parameters ---------- other: OTPBaseTimeOffset, datetime object to add Returns ------- result: datetime, :py:class:`pandas.Timedelta` return datetime if otp.Nano or another date offset object was passed as an argument, or pandas.Timedelta object if otp.datetime was passed as an argument. Examples -------- >>> otp.datetime(2022, 2, 24) + otp.Nano(1) 2022-02-24 00:00:00.000000001 """self._error_on_int_param(other,"+")returndatetime(self.ts+other)
[docs]def__sub__(self,other):""" Subtract :ref:`datetime offset <datetime_offsets>` from otp.datetime. Parameters ---------- other: OTPBaseTimeOffset, datetime object to subtract Returns ------- result: datetime, :py:class:`pandas.Timedelta` return datetime if otp.Nano or another date offset object was passed as an argument, or pandas.Timedelta object if otp.datetime was passed as an argument. Examples -------- >>> otp.datetime(2022, 2, 24) - otp.Nano(1) 2022-02-23 23:59:59.999999999 """self._error_on_int_param(other,"-")other=getattr(other,"ts",other)result=self.ts-other# do not convert to datetime in case timedelta is returned (arg is date)result=datetime(result)ifisinstance(result,_timestamp)elseresultreturnresult
def_error_on_int_param(self,other,op):iftype(other)isint:raiseTypeError(f"unsupported operand type(s) for {op}: 'otp.datetime' and 'int'")def__str__(self):returnstr(self.ts)def__repr__(self):returnstr(self.ts)deftz_localize(self,tz):""" Localize tz-naive datetime object to a given timezone Parameters ---------- tz: str or tzinfo timezone to localize datetime object into Returns ------- result: datetime localized datetime object Examples -------- >>> d = otp.datetime(2021, 6, 3) >>> d.tz_localize("EST5EDT") 2021-06-03 00:00:00-04:00 """returndatetime(self.ts.tz_localize(tz))deftz_convert(self,tz):""" Convert tz-aware datetime object to another timezone Parameters ---------- tz: str or tzinfo timezone to convert datetime object into Returns ------- result: datetime converted datetime object Examples -------- >>> d = otp.datetime(2021, 6, 3, tz="EST5EDT") >>> d.tz_convert("Europe/Moscow") 2021-06-03 07:00:00+03:00 """returndatetime(self.ts.tz_convert(tz))
classdatetime_now(datetime):''' Class to distinguish the `now` in operations '''dt=datetime
[docs]classdate(datetime):""" Class `date` is used for representing date in onetick-py. It can be used both when specifying start and end time for queries and in column operations with onetick.py.Source. Parameters ---------- first_arg: int, str, otp.datetime, pandas.Timestamp, datetime.datetime, datetime.date If `month` and `day` arguments are specified, first argument will be considered as year. Otherwise, first argument will be converted to otp.date. month: int Number between 1 and 12. day: int Number between 1 and 31. Examples -------- :ref:`Datetime guide <datetime_guide>`. """def__init__(self,first_arg:typing.Union[int,str,_date,_datetime,_timestamp,AbstractTime],month=None,day=None):ifmonthisNoneanddayisNone:ifisinstance(first_arg,AbstractTime):first_arg=first_arg.tselifisinstance(first_arg,(int,str)):first_arg=_timestamp(first_arg)ifisinstance(first_arg,(_datetime,_timestamp,datetime)):first_arg=first_arg.date()self.ts=_timestamp(first_arg)# remove hour, minutes and so onelifall((month,day)):self.ts=_timestamp(first_arg,month,day)else:raiseValueError("Please specify three integers (year, month, day) ""or object or create date from (string, int timestamp, ""pandas.Timestamp, otp.datetime, otp.date, ""datetime.datetime, datetime.date)")def__str__(self):returnself.ts.strftime("%Y-%m-%d")def__repr__(self):returnself.ts.strftime("%Y-%m-%d")defto_str(self,format="%Y%m%d"):""" Convert date to string, by default it will be in YYYYMMDD format. Parameters ---------- format: str strftime format of string to convert to. Returns ------- result: str """returnself.ts.strftime(format)
class_integer_str(type):def__str__(cls):returncls.__name__class_integer(int,metaclass=_integer_str):def__new__(cls,value,*args,**kwargs):type_size=8*ctypes.sizeof(cls._CTYPE)ifcls._UNSIGNED:min_value,max_value=0,(2**type_size)-1else:min_value,max_value=-(2**(type_size-1)),(2**(type_size-1))-1ifnotmin_value<=value<=max_value:raiseValueError(f"{cls.__name__} values must be between {min_value} and {max_value}")returnsuper().__new__(cls,value,*args,**kwargs)def__get_result(self,value):ifisinstance(value,int):returnself.__class__(self._CTYPE(value).value)returnvaluedef__add__(self,other):returnself.__get_result(super().__add__(other))def__radd__(self,other):returnself.__get_result(super().__radd__(other))def__sub__(self,other):returnself.__get_result(super().__sub__(other))def__rsub__(self,other):returnself.__get_result(super().__rsub__(other))def__mul__(self,other):returnself.__get_result(super().__mul__(other))def__rmul__(self,other):returnself.__get_result(super().__rmul__(other))def__truediv__(self,other):returnself.__get_result(super().__truediv__(other))def__rtruediv__(self,other):returnself.__get_result(super().__rtruediv__(other))def__str__(self):returnstr(int(self))def__repr__(self):returnf"{self.__class__.__name__}({self})"
[docs]classulong(_integer):""" OneTick data type representing unsigned long integer. The size of the type is not specified and may vary across different systems. Most commonly it's a 8-byte type with allowed values from 0 to 2**64 - 1. Note that the value is checked to be valid in constructor, but no overflow checking is done when arithmetic operations are performed. Examples -------- >>> t = otp.Tick(A=otp.ulong(1)) >>> t['B'] = otp.ulong(1) + 1 >>> t.schema {'A': <class 'onetick.py.types.ulong'>, 'B': <class 'onetick.py.types.ulong'>} Note that arithmetic operations may result in overflow. Here we get 2**64 - 1 instead of -1. >>> t = otp.Tick(A=otp.ulong(0) - 1) >>> otp.run(t) # doctest: +SKIP Time A 0 2003-12-01 18446744073709551615 """_CTYPE=ctypes.c_ulong_UNSIGNED=True
[docs]classuint(_integer):""" OneTick data type representing unsigned integer. The size of the type is not specified and may vary across different systems. Most commonly it's a 4-byte type with allowed values from 0 to 2**32 - 1. Note that the value is checked to be valid in constructor, but no overflow checking is done when arithmetic operations are performed. Examples -------- >>> t = otp.Tick(A=otp.uint(1)) >>> t['B'] = otp.uint(1) + 1 >>> t.schema {'A': <class 'onetick.py.types.uint'>, 'B': <class 'onetick.py.types.uint'>} Note that arithmetic operations may result in overflow. Here we get 2**32 - 1 instead of -1. >>> t = otp.Tick(A=otp.uint(0) - 1) >>> otp.run(t) # doctest: +SKIP Time A 0 2003-12-01 4294967295 """_CTYPE=ctypes.c_uint_UNSIGNED=True
[docs]classbyte(_integer):""" OneTick data type representing byte integer. The size of the type is not specified and may vary across different systems. Most commonly it's a 1-byte type with allowed values from -128 to 127. Note that the value is checked to be valid in constructor, but no overflow checking is done when arithmetic operations are performed. Examples -------- >>> t = otp.Tick(A=otp.byte(1)) >>> t['B'] = otp.byte(1) + 1 >>> t.schema {'A': <class 'onetick.py.types.byte'>, 'B': <class 'onetick.py.types.byte'>} Note that arithmetic operations may result in overflow. Here we get 127 instead of -129. >>> t = otp.Tick(A=otp.byte(-128) - 1) >>> otp.run(t) Time A 0 2003-12-01 127 """_CTYPE=ctypes.c_byte_UNSIGNED=False
[docs]classshort(_integer):""" OneTick data type representing short integer. The size of the type is not specified and may vary across different systems. Most commonly it's a 2-byte type with allowed values from -32768 to 32767. Note that the value is checked to be valid in constructor, but no overflow checking is done when arithmetic operations are performed. Examples -------- >>> t = otp.Tick(A=otp.short(1)) >>> t['B'] = otp.short(1) + 1 >>> t.schema {'A': <class 'onetick.py.types.short'>, 'B': <class 'onetick.py.types.short'>} Note that arithmetic operations may result in overflow. Here we get 32767 instead of -32769. >>> t = otp.Tick(A=otp.short(-32768) - 1) >>> otp.run(t) Time A 0 2003-12-01 32767 """_CTYPE=ctypes.c_short_UNSIGNED=False
# ------------------- #deftype2str(t):iftisint:return"long"iftisstr:return"string"iftisfloat:return"double"iftisNone:return''returnstr(t)defstr2type(type_name:str):"""Converts OneTick type by its name into Python/OTP domain type Args: type_name (str): name of type from CSV or OneTick DB, possible values listed here: http://solutions.pages.soltest.onetick.com/iac/onetick-server/ep_guide/EP/FieldTypeDeclarations.htm#supported_field_types Returns: class: Python/OTP type representing OneTick type """iftype_namein["long","int","time32"]:returnintiftype_name=="byte":returnbyteiftype_name=="short":returnshortiftype_name=="uint":returnuintiftype_name=="ulong":returnulongeliftype_namein["double","float"]:returnfloateliftype_name=="decimal":returndecimaleliftype_name=="msectime":returnmsectimeeliftype_name=="nsectime":returnnsectimeeliftype_namein["string","matrix",f"string[{string.DEFAULT_LENGTH}]"]:returnstreliftype_name=="varstring":returnvarstringeliftype_name.find("string")!=-1:length=int(type_name[type_name.find("[")+1:type_name.find("]")])returnstring[length]returnNone# TODO: move this union of types to some common placedefdatetime2expr(dt:typing.Union[_datetime,_date,pd.Timestamp,date,datetime],timezone:str=None,timezone_naive:str=None,)->str:""" Convert python datetime values to OneTick string representation. If `dt` is timezone-aware then timezone will be taken from `dt` value. If `dt` is timezone-naive then timezone specified with otp.config['tz'] or otp.run() will be used. Parameters ---------- dt date or datetime value timezone: str This timezone will be used unconditionally. timezone_naive: str This timezone will be used if `dt` is timezone-naive. """dt_str=dt.strftime('%Y-%m-%d %H:%M:%S.%f')ifisinstance(dt,(pd.Timestamp,datetime)):dt_str+=f'{dt.nanosecond:03}'[-3:]else:dt_str+='000'timezone=timezoneorget_timezone_from_datetime(dt)ortimezone_naivetimezone=f'"{timezone}"'iftimezoneelse'_TIMEZONE'returnf'PARSE_NSECTIME("%Y-%m-%d %H:%M:%S.%J", "{dt_str}", {timezone})'defvalue2str(v):""" Converts a python value from the `v` parameter into OneTick format. """ifissubclass(type(v),str):# there is no escape, so replacing double quotes with concatenation with itreturn'"'+str(v).replace('"','''"+'"'+"''')+'"'ifisinstance(v,decimal):returnf'DECIMAL({v})'ifisinstance(v,float)andnot(isinstance(v,(_inf,_nan))):# PY-286: support science notations=str(v)if"e"ins:s=f"{v:.20f}".rstrip("0")ifs=="nan":returnstr(nan)returnsifis_time_type(v):returndatetime2expr(v)ifisinstance(v,nsectime):# we do not need the same for msectime because it works as isifint(v)>15e12:# it is 2445/5/1returnf'NSECTIME({v})'# This branch is for backward compatibility. Originally here was a bug that# allowed to pass only milliseconds as a value into the otp.nsectime constructor.# Obviously we expect there only nanoseconds, and the built-in NSECTIME works only# with nanoseconds.warnings.warn('It seems that you are using number of milliseconds as nanoseconds. ')returnstr(v)deftime2nsectime(time,timezone=None):""" Converts complex time types to nsectime timestamp. Parameters ---------- time: datetime.datetime, datetime.date, otp.datetime, otp.date, pandas.Timestamp time to convert timezone: convert timezone before nsectime calculation Returns ------- result: int number of nanoseconds since epoch """ifisinstance(time,(_datetime,_date)):time=pd.Timestamp(time)elifisinstance(time,date):time=datetime(time)iftimezone:ifnothas_timezone_parameter():# accommodating legacy behavior prior to 20220327-3 weekly buildtime=time.replace(tzinfo=None)else:iftime.tzinfoisNone:time=time.tz_localize(timezone)else:time=time.tz_convert(timezone)returntime.valuedefis_time_type(time):""" Returns true if argument is subclass of any time type Checks if pass type is time type, currently checks for otp.date, otp.datetime, pd.Timestamp, datetime.date, datetime.datetime Parameters ---------- time: object or type of the object Returns ------- result: bool Return true if argument is time type Examples -------- >>> is_time_type(datetime) # OTdirective: skip-example: ; True >>> is_time_type(type(5)) # OTdirective: skip-example: ; False >>> is_time_type(datetime(2019, 1, 1)) # OTdirective: snippet-name: types.is time; True """time=timeifinspect.isclass(time)elsetype(time)# do not check for datetime.datetime and pd.Timestamp, because they are in the same hierarchy# datetime.date -> datetime.datetime -> pd.Timestamp, where `->` means base classreturnissubclass(time,(_date,datetime,date))defnext_day(dt:typing.Union[_date,_datetime,date,datetime,pd.Timestamp])->_datetime:""" Return next day of `dt` as datetime.datetime. """dt=(dt+Day(1)).date()return_datetime(dt.year,dt.month,dt.day)