implement provider structure & yfin provider & historical update rates flow done

This commit is contained in:
ALIHAN DIKEL
2024-08-27 17:54:25 +03:00
commit 04307152d2
18 changed files with 301 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
venv
.DS_Store
data
.idea/
*.yml
tuncel.yml
__pycache__
localdata/gsheets
localdata

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/daytrade.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,31 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="ignoredPackages">
<value>
<list size="3">
<item index="0" class="java.lang.String" itemvalue="tensorflow" />
<item index="1" class="java.lang.String" itemvalue="tensorflow-estimator" />
<item index="2" class="java.lang.String" itemvalue="numpy" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E501" />
<option value="E261" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="src.processor.run.infra" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (daytrade)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (daytrade)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/daytrade.iml" filepath="$PROJECT_DIR$/.idea/daytrade.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

25
config.py Normal file
View File

@@ -0,0 +1,25 @@
import os
from dynaconf import Dynaconf
def get_dynaconf_related_config_files():
config_folder_path = 'localdata'
config_file_paths = []
for filename in os.listdir(config_folder_path):
if filename.endswith('.yml') or filename.endswith(".yaml"):
file_path = os.path.join(config_folder_path, filename)
config_file_paths.append(file_path)
return config_file_paths
# --- DynaConf ---------------------------------------------------------------------------------------------------------
envvar = Dynaconf(
envvar_prefix="DAYTRADE",
environments=True,
settings_files=get_dynaconf_related_config_files(),
)

View File

View File

@@ -0,0 +1,37 @@
from datetime import datetime, timedelta
from gspread import Cell
from gspread.utils import ValueInputOption
from loguru import logger
from gsheets_client.session import SessionHandler
session = SessionHandler()
session.connect()
def get_sheet(sheet_name):
sheet = session.get_sheet(sheet_name=sheet_name)
return sheet
def get_date_range(sheet):
header = sheet.find("date")
start_cell = Cell(header.row + 3, header.col)
col_vals = sheet.col_values(header.col)
end_cell = Cell(len(col_vals), header.col)
return f"{start_cell.address}:{end_cell.address}", (start_cell.address, end_cell.address)
def update_date_column(sheet, num_days):
today = datetime.today()
dates = [[date] for date in [(today - timedelta(days=i)).strftime('%Y-%m-%d') for i in range(num_days)]]
header = sheet.find("date")
start_cell = Cell(header.row + 3, header.col)
sheet.update(values=dates[1:], range_name=start_cell.address, value_input_option=ValueInputOption.user_entered)
def update_historical(sheet, ticker, data: dict):
logger.debug(f"updating historical values for {ticker}")
header = sheet.find(ticker)
start_cell = Cell(header.row + 3, header.col)
sheet.update(values=data["close"], range_name=start_cell.address, value_input_option=ValueInputOption.user_entered)

48
gsheets_client/session.py Normal file
View File

@@ -0,0 +1,48 @@
import os
from functools import wraps
import gspread
from loguru import logger
from config import envvar
#from config.config import settings
def with_connection(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if not self._is_connected:
logger.error(f"Failed to execute {func.__name__}: session handler disconnected")
return None
return func(self, *args, **kwargs)
return wrapper
class SessionHandler:
def __init__(self):
self._is_connected = False
def connect(self):
try:
logger.debug(f"connecting to spreadsheet")
self.gc = gspread.service_account(filename=envvar.SERVICE_ACCOUNT_JSON)
self.ss = self.gc.open_by_key(key=envvar.ASSET_SHEET_ID)
self._is_connected = True
logger.success("spreadsheet connection OK")
except Exception as e:
logger.error(f"Failed to connect to spreadsheet")
logger.error(f"Error type: {type(e)}")
logger.error(f"Error msg: {e}")
self._is_connected = False
@with_connection
def get_sheet(self, sheet_name):
try:
logger.debug(f"creating object for sheet: {sheet_name}")
return self.ss.worksheet(sheet_name)
except Exception as e:
logger.error(f"Failed to get sheet {sheet_name}: {e}")
return None

0
providers/__init__.py Normal file
View File

20
providers/base.py Normal file
View File

@@ -0,0 +1,20 @@
from abc import ABC, abstractmethod
import pandas as pd
class DataProvider(ABC):
@abstractmethod
def get_ticker(self, ticker: str):
pass
@abstractmethod
def get_historical(self, ticker: str, start: str, end: str) -> pd.DataFrame:
pass
@abstractmethod
def get_current(self, ticker: str) -> float:
pass
@abstractmethod
def convert_ticker_symbol_to_gsheets_header(self, ticker: str) -> str:
pass

38
providers/provider.py Normal file
View File

@@ -0,0 +1,38 @@
from datetime import datetime
import pandas as pd
from providers.base import DataProvider
class FinDataRetriever:
def __init__(self, provider: DataProvider, ticker: str):
self.provider = provider
self.ticker = ticker
def get_ticker(self):
return self.provider.get_ticker(self.ticker)
def get_historical(self, start: str, end: str) -> pd.DataFrame:
return self.provider.get_historical(self.ticker, start, end)
def get_historical_dynamic(self, sheet, date_range):
start_date = sheet.acell(date_range[1]).value
end_date = datetime.today().strftime('%Y-%m-%d')
histdata = self.provider.get_historical(self.ticker, start_date, end_date)
## fill forward for indicators traded only on business days
histdata.index = pd.to_datetime(histdata.index).tz_localize(None)
df_date_range = pd.date_range(start=start_date, end=end_date, freq='D')
histdata = histdata.reindex(df_date_range)
if end_date in histdata.index:
histdata.drop(end_date, inplace=True)
histdata.ffill(inplace=True)
values_close = list(reversed([[close] for close in histdata["Close"].to_list()]))
values_dates = list(reversed([[date] for date in histdata.index.to_list()]))
return {"close": values_close, "date": values_dates}
def get_current(self) -> float:
return self.provider.get_current(self.ticker)

View File

26
providers/sources/yfin.py Normal file
View File

@@ -0,0 +1,26 @@
import yfinance as yf
import pandas as pd
from providers.base import DataProvider
class YFinanceProvider(DataProvider):
def get_ticker(self, ticker: str):
return yf.Ticker(ticker)
def get_historical(self, ticker: str, start: str, end: str) -> pd.DataFrame:
ticker = self.get_ticker(ticker)
return ticker.history(start=start, end=end)
def get_current(self, ticker: str) -> float:
ticker = self.get_ticker(ticker)
return ticker.info['currentPrice']
def get_news(self, ticker: str) -> float:
return "ok"
def convert_ticker_symbol_to_gsheets_header(self, ticker: str) -> str:
return ticker\
.replace("-", "")\
.replace("=X", "")\
.lower()

View File

@@ -0,0 +1,27 @@
import os
from loguru import logger
from config import envvar
from gsheets_client import repository as gsheets
from providers.provider import FinDataRetriever
from providers.sources.yfin import YFinanceProvider
logger.info("starting flow: update historical rates")
ss_system = gsheets.get_sheet(sheet_name="system")
gsheets.update_date_column(sheet=ss_system, num_days=int(envvar.HISTORICAL_MAX_DATE))
_, date_range = gsheets.get_date_range(sheet=ss_system)
tickers = ["BTC-USD", "ETH-USD", "BTC-ETH", "USDTRY=X"]
for ticker in tickers:
logger.info(f"updating historical rates on system sheet for: {ticker}")
retriever = FinDataRetriever(provider=YFinanceProvider(), ticker=ticker)
data = retriever.get_historical_dynamic(sheet=ss_system, date_range=date_range)
ticker_header = retriever.provider.convert_ticker_symbol_to_gsheets_header(ticker)
gsheets.update_historical(sheet=ss_system, ticker=ticker_header, data=data)
logger.success("completed flow: update historical rates")