This commit is contained in:
David Brazda
2024-10-25 14:12:20 +02:00
parent 3afbf53368
commit eee8de727c
5 changed files with 229 additions and 2 deletions

View File

@ -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',

View File

@ -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
from .vbtindicators import register_custom_inds
from .utils import find_dotenv, AGG_TYPE, zoneNY, zonePRG, zoneUTC

30
ttools/config.py Normal file
View File

@ -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')

56
ttools/loaders.py Normal file
View File

@ -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

140
ttools/utils.py Normal file
View File

@ -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"