mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-07-14 01:10:34 +08:00
197 lines
5.2 KiB
Python
197 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
from functools import reduce
|
|
from operator import mul
|
|
from sys import float_info as sflt
|
|
|
|
TRADING_DAYS_IN_YEAR = 250
|
|
TRADING_HOURS_IN_DAY = 6.5
|
|
MINUTES_IN_HOUR = 60
|
|
|
|
def combination(**kwargs):
|
|
"""https://stackoverflow.com/questions/4941753/is-there-a-math-ncr-function-in-python"""
|
|
n = int(math.fabs(kwargs.pop('n', 1)))
|
|
r = int(math.fabs(kwargs.pop('r', 0)))
|
|
|
|
if kwargs.pop('repetition', False) or kwargs.pop('multichoose', False):
|
|
n = n + r - 1
|
|
|
|
# if r < 0: return None
|
|
r = min(n, n - r)
|
|
if r == 0:
|
|
return 1
|
|
|
|
numerator = reduce(mul, range(n, n - r, -1), 1)
|
|
denominator = reduce(mul, range(1, r + 1), 1)
|
|
return numerator // denominator
|
|
|
|
|
|
def cross(series_a:pd.Series, series_b:pd.Series, above:bool =True, asint:bool =True, offset:int =None, **kwargs):
|
|
series_a = verify_series(series_a)
|
|
series_b = verify_series(series_b)
|
|
offset = get_offset(offset)
|
|
|
|
series_a.apply(zero)
|
|
series_b.apply(zero)
|
|
|
|
# Calculate Result
|
|
current = series_a > series_b # current is above
|
|
previous = series_a.shift(1) < series_b.shift(1) # previous is below
|
|
# above if both are true, below if both are false
|
|
cross = current & previous if above else ~current & ~previous
|
|
|
|
if asint:
|
|
cross = cross.astype(int)
|
|
|
|
# Offset
|
|
if offset != 0:
|
|
cross = cross.shift(offset)
|
|
|
|
# Name & Category
|
|
cross.name = f"{series_a.name}_{'XA' if above else 'XB'}_{series_b.name}"
|
|
cross.category = 'utility'
|
|
|
|
return cross
|
|
|
|
|
|
def df_error_analysis(dfA:pd.DataFrame, dfB:pd.DataFrame, **kwargs):
|
|
""" """
|
|
col = kwargs.pop('col', None)
|
|
corr_method = kwargs.pop('corr_method', 'pearson')
|
|
|
|
# Find their differences
|
|
diff = dfA - dfB
|
|
df = pd.DataFrame({'diff': diff.describe()})
|
|
extra = pd.DataFrame([diff.var(), diff.mad(), diff.sem(), dfA.corr(dfB, method=corr_method)], index=['var', 'mad', 'sem', 'corr'])
|
|
|
|
# Append the differences to the DataFrame
|
|
df = df['diff'].append(extra, ignore_index=False)[0]
|
|
|
|
# For plotting
|
|
# diff.hist()
|
|
# if diff[diff > 0].any():
|
|
# diff.plot(kind='kde')
|
|
|
|
if col is not None:
|
|
return df[col]
|
|
else:
|
|
return df
|
|
|
|
def fibonacci(**kwargs):
|
|
"""Fibonacci Sequence as a numpy array"""
|
|
n = int(math.fabs(kwargs.pop('n', 2)))
|
|
zero = kwargs.pop('zero', False)
|
|
weighted = kwargs.pop('weighted', False)
|
|
|
|
if zero:
|
|
a, b = 0, 1
|
|
else:
|
|
n -= 1
|
|
a, b = 1, 1
|
|
|
|
result = np.array([a])
|
|
for i in range(0, n):
|
|
a, b = b, a + b
|
|
result = np.append(result, a)
|
|
|
|
if weighted:
|
|
fib_sum = np.sum(result)
|
|
if fib_sum > 0:
|
|
return result / fib_sum
|
|
else:
|
|
return result
|
|
else:
|
|
return result
|
|
|
|
|
|
def get_drift(x:int):
|
|
"""Returns an int if not zero, otherwise defaults to one."""
|
|
return int(x) if x and x != 0 else 1
|
|
|
|
|
|
def get_offset(x:int):
|
|
"""Returns an int, otherwise defaults to zero."""
|
|
return int(x) if x else 0
|
|
|
|
|
|
def pascals_triangle(n:int =None, **kwargs):
|
|
"""Pascal's Triangle
|
|
|
|
Returns a numpy array of the nth row of Pascal's Triangle.
|
|
n=4 => triangle: [1, 4, 6, 4, 1]
|
|
=> weighted: [0.0625, 0.25, 0.375, 0.25, 0.0625
|
|
=> inverse weighted: [0.9375, 0.75, 0.625, 0.75, 0.9375]
|
|
"""
|
|
n = int(math.fabs(n)) if n is not None else 0
|
|
weighted = kwargs.pop('weighted', False)
|
|
inverse = kwargs.pop('inverse', False)
|
|
|
|
# Calculation
|
|
triangle = np.array([combination(n=n, r=i) for i in range(0, n + 1)])
|
|
triangle_sum = np.sum(triangle)
|
|
triangle_weights = triangle / triangle_sum
|
|
inverse_weights = 1 - triangle_weights
|
|
|
|
if weighted and inverse:
|
|
return inverse_weights
|
|
if weighted:
|
|
return triangle_weights
|
|
if inverse:
|
|
return None
|
|
|
|
return triangle
|
|
|
|
|
|
def signed_series(series:pd.Series, initial:int =None):
|
|
"""Returns a Signed Series with or without an initial value"""
|
|
series = verify_series(series)
|
|
sign = series.diff(1)
|
|
sign[sign > 0] = 1
|
|
sign[sign < 0] = -1
|
|
sign.iloc[0] = initial
|
|
return sign
|
|
|
|
|
|
def symmetric_triangle(n:int =None, **kwargs):
|
|
n = int(math.fabs(n)) if n is not None else 2
|
|
weighted = kwargs.pop('weighted', False)
|
|
|
|
if n == 2:
|
|
triangle = [1, 1]
|
|
|
|
if n > 2:
|
|
if n % 2 == 0:
|
|
front = [i + 1 for i in range(0, math.floor(n/2))]
|
|
triangle = front + front[::-1]
|
|
else:
|
|
front = [i + 1 for i in range(0, math.floor(0.5 * (n + 1)))]
|
|
triangle = front.copy()
|
|
front.pop()
|
|
triangle += front[::-1]
|
|
|
|
if weighted:
|
|
triangle_sum = np.sum(triangle)
|
|
triangle_weights = triangle / triangle_sum
|
|
return triangle_weights
|
|
|
|
return triangle
|
|
|
|
|
|
def verify_series(series:pd.Series):
|
|
"""If a Pandas Series return it."""
|
|
if series is not None and isinstance(series, pd.core.series.Series):
|
|
return series
|
|
|
|
|
|
def weights(w):
|
|
def _dot(x):
|
|
return np.dot(w, x)
|
|
return _dot
|
|
|
|
|
|
def zero(x):
|
|
"""If the value is close to zero, then return zero. Otherwise return the value."""
|
|
return 0 if -sflt.epsilon < x and x < sflt.epsilon else x |