From eee8de727cec10e79f690be2a2ded46bd9c4c4e9 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Fri, 25 Oct 2024 14:12:20 +0200 Subject: [PATCH] update --- setup.py | 2 +- ttools/__init__.py | 3 +- ttools/config.py | 30 ++++++++++ ttools/loaders.py | 56 ++++++++++++++++++ ttools/utils.py | 140 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 ttools/config.py create mode 100644 ttools/loaders.py create mode 100644 ttools/utils.py diff --git a/setup.py b/setup.py index 37c9472..30560be 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='ttools', - version='0.4.1', + version='0.4.2', packages=find_packages(), install_requires=[ 'vectorbtpro', diff --git a/ttools/__init__.py b/ttools/__init__.py index f729e87..79a2a70 100644 --- a/ttools/__init__.py +++ b/ttools/__init__.py @@ -1,2 +1,3 @@ from .vbtutils import AnchoredIndicator, create_mask_from_window, isrising, isfalling, isrisingc, isfallingc, trades2entries_exits, figs2cell -from .vbtindicators import register_custom_inds \ No newline at end of file +from .vbtindicators import register_custom_inds +from .utils import find_dotenv, AGG_TYPE, zoneNY, zonePRG, zoneUTC \ No newline at end of file diff --git a/ttools/config.py b/ttools/config.py new file mode 100644 index 0000000..f67accb --- /dev/null +++ b/ttools/config.py @@ -0,0 +1,30 @@ + +from dotenv import load_dotenv +from appdirs import user_data_dir +from ttools.utils import find_dotenv, AGG_TYPE, RecordType, StartBarAlign, zoneNY, zonePRG, zoneUTC +import os +import pytz + +#Trade can be shared with v2realbot, agg cache not (we use df, but v2realbot uses Queue - will be changed in the future, when vectorized agg is added to v2realbot) +DATA_DIR = user_data_dir("v2realbot", False) # or any subfolder, if not sharing cache with v2realbot +LOCAL_TRADE_CACHE = DATA_DIR + "/tradecache:new/" # +daily_file +LOCAL_AGG_CACHE = DATA_DIR + "/aggcache_new/" #+ cache_file +RECTYPE = "BAR" +#AGG conditions -defaults +EXCLUDE_CONDITIONS = ['C','O','4','B','7','V','P','W','U','Z','F'] +MINSIZE = 100 +RECORD_TYPE = RecordType.BAR #loader supports only BAR type (no cbars) + +#Load env variables +ENV_FILE = find_dotenv(__file__) +print(ENV_FILE) +if load_dotenv(ENV_FILE, verbose=True) is False: + print(f"Error loading.env file {ENV_FILE}. Now depending on ENV VARIABLES set externally.") +else: + print(f"Loaded env variables from file {ENV_FILE}") + +#Alpaca accounts +ACCOUNT1_LIVE_API_KEY = os.getenv('ACCOUNT1_LIVE_API_KEY') +ACCOUNT1_LIVE_SECRET_KEY = os.getenv('ACCOUNT1_LIVE_SECRET_KEY') +ACCOUNT1_PAPER_API_KEY = os.getenv('ACCOUNT1_PAPER_API_KEY') +ACCOUNT1_PAPER_SECRET_KEY = os.getenv('ACCOUNT1_PAPER_SECRET_KEY') \ No newline at end of file diff --git a/ttools/loaders.py b/ttools/loaders.py new file mode 100644 index 0000000..d8c4f8c --- /dev/null +++ b/ttools/loaders.py @@ -0,0 +1,56 @@ + +from ctypes import Union +from dotenv import load_dotenv +from appdirs import user_data_dir +from ttools.utils import find_dotenv +from ttools.config import * +import os +from datetime import datetime + +print(DATA_DIR) + +def load_data(symbol: Union[str, list], day_start: datetime, day_end: datetime, agg_type: AGG_TYPE, resolution: Union[str, int], excludes: list = EXCLUDE_CONDITIONS, minsize: int = MINSIZE, ext_hours: bool = False, align: StartBarAlign =StartBarAlign.ROUND, as_df: bool = False, force_reload: bool = False) -> None: + """ + Returns requested aggregated data for give symbol(s) + - if requested agg data already exists in cache, returns it + - otherwise get the trades (from trade cache or Alpaca) and aggregate them and store to cache + + For location of both caches, see config.py + + Note both trades and agg cache are daily_files + + LOCAL_TRADE_CACHE + LOCAL_AGG_CACHE + + Parameters + ---------- + symbol : Union[str, list] + Symbol or list of symbols + day_start : datetime + Start date, zone aware + day_end : datetime + End date, zone aware + agg_type : AGG_TYPE + Type of aggregation + resolution : Union[str, int] + Resolution of aggregation nased on agg_type + excludes : list + List of trade conditions to exclude + minsize : int + Minimum size of trade to be included + ext_hours : bool + If True, requests extended hours data + align : StartBarAlign + How to align first bar RANDOM vs ROUND + as_df : bool + If True, returns dataframe, otherwise returns vbt Data object + force_reload : bool + If True, forces cache reload (doesnt use cache both for trade and agg data) + + Returns + ------- + DF or vbt.Data object + """ + pass + + diff --git a/ttools/utils.py b/ttools/utils.py new file mode 100644 index 0000000..85ff8cd --- /dev/null +++ b/ttools/utils.py @@ -0,0 +1,140 @@ +from pathlib import Path +from enum import Enum +from datetime import datetime, timedelta +from typing import List, Tuple +import pytz +import calendar + +#Zones +zoneNY = pytz.timezone('US/Eastern') +zoneUTC = pytz.utc +zonePRG = pytz.timezone('Europe/Amsterdam') + +def split_range(start: datetime, stop: datetime, period: str = "Y") -> List[Tuple[datetime, datetime]]: + """ + Splits a range of dates into a list of (start, end) tuples, where end is exclusive (start of next range) + + Args: + start (datetime): start date + stop (datetime): end date + period (str): 'Y', 'M', 'W', or 'D' for year, month, week, or day + + + Returns: + List[Tuple[datetime, datetime]]: list of (start, end) tuples + + Example: + + ```python + year_ranges = split_range(day_start, day_stop, period="M") + for start_date,end_date in year_ranges: + print(start_date,end_date) + + >>> 2023-01-15 09:30:00-05:00 2023-02-01 00:00:00-05:00 + >>> 2023-02-01 00:00:00-05:00 2023-03-01 00:00:00-05:00 + >>> 2023-03-01 00:00:00-05:00 2023-04-01 00:00:00-05:00 + >>> 2023-04-01 00:00:00-05:00 2023-05-01 00:00:00-05:00 + >>> 2023-05-01 00:00:00-05:00 2023-06-01 00:00:00-05:00 + ``` + + """ + if start > stop: + raise ValueError("Start date must be before stop date") + + ranges = [] + current_start = start + + while current_start < stop: + if period == "Y": + next_period_start = datetime(current_start.year + 1, 1, 1, tzinfo=current_start.tzinfo) + elif period == "M": + next_year = current_start.year + (current_start.month // 12) + next_month = (current_start.month % 12) + 1 + next_period_start = datetime(next_year, next_month, 1, tzinfo=current_start.tzinfo) + elif period == "W": + next_period_start = current_start + timedelta(weeks=1) + elif period == "D": + next_period_start = current_start + timedelta(days=1) + else: + raise ValueError("Invalid period specified. Choose from 'Y', 'M', 'W', or 'D'.") + + # Set the end of the current period or stop if within the same period + current_end = min(next_period_start, stop) + + # Append the (start, end) tuple to ranges + ranges.append((zoneNY.localize(current_start), zoneNY.localize(current_end))) + + # Move to the start of the next period + current_start = next_period_start + + return ranges + + + +def find_dotenv(start_path): + """ + Searches for a .env file in the given directory or its parents and returns the path. + + Args: + start_path: The directory to start searching from. + + Returns: + Path to the .env file if found, otherwise None. + """ + current_path = Path(start_path) + for _ in range(6): # Limit search depth to 5 levels + dotenv_path = current_path / '.env' + if dotenv_path.exists(): + return dotenv_path + current_path = current_path.parent + return None + +# def get_daily_tradecache_file(): +# return Path(DATA_DIR) / "tradecache" + +# def get_daily_aggcache_file(): +# #nazev obsahuje i child class +# #a take excludes result = ''.join(self.excludes.sort()) +# self.excludes.sort() # Sorts the list in place +# excludes_str = ''.join(map(str, self.excludes)) # Joins the sorted elements after converting them to strings +# cache_file = self.__class__.__name__ + '-' + self.symbol + '-' + str(int(date_from.timestamp())) + '-' + str(int(date_to.timestamp())) + '-' + str(self.rectype) + "-" + str(self.resolution) + "-" + str(self.minsize) + "-" + str(self.align) + '-' + str(self.mintick) + str(self.exthours) + excludes_str + '.cache.gz' +# file_path = DATA_DIR + "/aggcache/" + cache_file +# #print(file_path) +# return file_path + + +#create enum AGG_TYPE +class AGG_TYPE(str, Enum): + """ + Enum class for aggregation types. + ohlcv - time based ohlcv (time as resolution) + ohlcv_vol - volume based ohlcv (volume as resolution) + ohlcv_dol - dollar volume based ohlcv (dollar amount as resolution) + ohlcv_renko - renko based ohlcv (brick size as resolution) + """ + OHLCV = 'ohlcv' + OHLCV_VOL = 'ohlcv_vol' + OHLCV_DOL = 'ohlcv_dol' + OHLCV_RENKO = 'ohlcv_renko' + +class RecordType(str, Enum): + """ + Represents output of aggregator + """ + + BAR = "bar" + CBAR = "cbar" + CBARVOLUME = "cbarvolume" + CBARDOLLAR = "cbardollar" + CBARRENKO = "cbarrenko" + TRADE = "trade" + + +class StartBarAlign(str, Enum): + """ + Represents first bar start time alignement according to timeframe + ROUND = bar starts at 0,5,10 (for 5s timeframe) + RANDOM = first bar starts when first trade occurs + """ + ROUND = "round" + RANDOM = "random" \ No newline at end of file