implement apps scripts control & technical analyze & charting

This commit is contained in:
ALIHAN DIKEL
2024-08-28 04:14:26 +03:00
parent 8faa7c611b
commit 21ed23e507
8 changed files with 227 additions and 1 deletions

0
apps_scripts/__init__.py Normal file
View File

View File

@@ -0,0 +1,60 @@
from apps_scripts.session import service as apps_scripts_service
def run_function(deployment_id, name, params=None):
request = {
'function': name,
'devMode': False # Set to False for production
}
if params:
request['parameters'] = params
response = apps_scripts_service.scripts().run(body=request, scriptId=deployment_id).execute()
return response
def create_project(title):
request = {"title": title}
response = apps_scripts_service.projects()\
.create(body=request)\
.execute()
return response
def create_script(script_id):
SAMPLE_CODE = """
function helloWorld() {
var spreadsheet = SpreadsheetApp.openById('1-GpXFraefQOEFk2j6zkThk5okd_7vR5JL6ZZ0c0bI_U');
var sheet = spreadsheet.getSheetByName('system');
var now = new Date();
sheet.getRange("I1").setValue(now.toLocaleString());
Logger.log("Timestamp: " + now.toLocaleString());
}
function doPost(e) {
helloWorld();
return ContentService.createTextOutput("Hello World script executed and written to cell I1.");
}
""".strip()
SAMPLE_MANIFEST = """
{
"timeZone": "Europe/Istanbul",
"exceptionLogging": "CLOUD"
}
""".strip()
request = {
"files": [
{"name": "hello", "type": "SERVER_JS", "source": SAMPLE_CODE},
{"name": "appsscript", "type": "JSON", "source": SAMPLE_MANIFEST, },
]
}
response = apps_scripts_service.projects()\
.updateContent(body=request, scriptId=script_id)\
.execute()
web_console_url = f"https://script.google.com/d/{response['scriptId']}"
return response

32
apps_scripts/session.py Normal file
View File

@@ -0,0 +1,32 @@
import os
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from config import envvar
def generate_creds():
if os.path.exists(envvar.APP_SCRIPT_TOKEN):
creds = Credentials.from_authorized_user_file(envvar.APP_SCRIPT_TOKEN, envvar.SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
envvar.CLIENT_SECRET, envvar.SCOPES
)
creds = flow.run_local_server(port=0)
with open(envvar.APP_SCRIPT_TOKEN, "w") as token:
token.write(creds.to_json())
return creds
session_creds = generate_creds()
service = build("script", "v1", credentials=session_creds)

0
charting/__init__.py Normal file
View File

66
charting/technicals.py Normal file
View File

@@ -0,0 +1,66 @@
from bokeh.plotting import figure, show
from bokeh.layouts import column
from bokeh.models import Range1d
def generate_macd_chart(data):
"""
:param data:
requires a dataframe like this:
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 239 non-null datetime64[ns, UTC]
1 Open 239 non-null float64
2 High 239 non-null float64
3 Low 239 non-null float64
4 Close 239 non-null float64
5 Volume 239 non-null int64
6 MACD 206 non-null float64
7 MACD_Signal 206 non-null float64
8 MACD_Hist 206 non-null float64
"""
# Create the figure for MACD
p1 = figure(x_axis_type="datetime", title="MACD", width=800, height=400)
p1.line(data['Date'], data['MACD'], color="blue", legend_label="MACD")
p1.line(data['Date'], data['MACD_Signal'], color="red", legend_label="Signal")
# Add histogram bars for MACD_Hist
p1.vbar(x=data['Date'], top=data['MACD_Hist'], width=0.5, color="green", legend_label="Histogram")
p1.legend.location = "top_left"
p1.legend.click_policy = "hide"
# Set the x_range to avoid empty space on the right
p1.x_range = Range1d(start=data['Date'].min(), end=data['Date'].max())
# Use column layout if you plan to add more plots
layout = column(p1, sizing_mode='stretch_width')
# Show plot
show(layout)
def generate_macd_with_obv_chart(data):
"""
:param data:
requires a dataframe like this:
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 239 non-null datetime64[ns, UTC]
1 Open 239 non-null float64
2 High 239 non-null float64
3 Low 239 non-null float64
4 Close 239 non-null float64
5 Volume 239 non-null int64
6 MACD 206 non-null float64
7 MACD_Signal 206 non-null float64
8 MACD_Hist 206 non-null float64
9 OBV 239 non-null float64
:return: None
"""
# Create the MACD chart
p1 = figure(x_axis_type="datetime", title="MACD", width=800, height=400)
p1.line(data['Date'], data['MACD'], color="blue", legend_label="MACD")
p1.line(data['Date'], data['MACD_Signal'], color="red", legend_label="Signal")
p1.vbar(x=data['Date'], top=data['MACD_Hist'], width=0.5, color="green", legend_label="Histogram")
p1.legend.location = "top_left"
# Create the OBV chart
p2 = figure(x_axis_type="datetime", title="On-Balance Volume", width=800, height=200)
p2.line(data['Date'], data['OBV'], color="orange", legend_label="OBV")
p2.legend.location = "top_left"
# Show the charts together
show(column(p1, p2))

47
dev.py Normal file
View File

@@ -0,0 +1,47 @@
from datetime import datetime, timedelta
import talib
from loguru import logger
from gsheets_client import repository as gsheets
from providers.provider import FinDataRetriever
from providers.sources.yfin import YFinanceProvider
from charting.technicals import generate_macd_with_obv_chart
ticker = "BTC-USD"
retriever = FinDataRetriever(provider=YFinanceProvider(), ticker=ticker)
ss_system = gsheets.get_sheet(sheet_name="system")
_, date_range = gsheets.get_date_range(sheet=ss_system)
cell_start = ss_system.acell(date_range[1])
cell_end = ss_system.acell(date_range[0])
data_diff = cell_start.row - cell_end.row
end_date = datetime.strptime(cell_end.value, '%Y-%m-%d')
## DIRTYFIX:
# historical ile current arası boşluk, 1 gün daha geriden indic date başlatıyor
end_date += timedelta(days=1)
##
start_date = end_date - timedelta(days=data_diff)
df = retriever.get_historical(start=start_date, end=end_date)
df.drop(columns=['Dividends', 'Stock Splits'], inplace=True)
## to technical anlaysis calculations here
df['MACD'], df['MACD_Signal'], df['MACD_Hist'] = talib.MACD(df['Close'])
df['OBV'] = talib.OBV(df['Close'], df['Volume'])
# parse: Get only technical indicator related column names, convert them to lowercase, and concatenate the prefix 'btcusd_'
ticker_header = retriever.provider.convert_ticker_symbol_to_gsheets_header(ticker)
columns_indics = ["MACD", "MACD_Signal", "MACD_Hist", "OBV"]
indicators = [f'{ticker_header}_{col.lower()}' for col in df.columns if col in columns_indics]
# update: write to sheets
for indicator, columns_indic in zip(indicators, columns_indics):
gsheets.update_historical_indicator(sheet=ss_system, indicator=indicator, data=df[columns_indic])
## web-based charting
df.reset_index(inplace=True)
generate_macd_with_obv_chart(data=df)
print()

View File

@@ -30,8 +30,29 @@ def update_date_column(sheet, num_days):
sheet.update(values=dates[1:], range_name=start_cell.address, value_input_option=ValueInputOption.user_entered)
def update_historical(sheet, ticker, data: dict):
"""
:param sheet:
sheets gspread object
:param ticker:
ticker header "btcusd" as in system sheet
:param data:
{"close": values_close, "date": values_dates}
:return:
"""
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)
def update_historical_indicator(sheet, indicator, data):
logger.debug(f"updating historical singleindic values for {indicator}")
data.fillna(0, inplace=True) # https://stackoverflow.com/questions/76042113/gspread-error-when-inserting-to-google-sheet-out-of-range-float-values-are-not
header = sheet.find(indicator)
start_cell = Cell(header.row + 3, header.col)
values_dates = list(reversed([[date.strftime('%Y-%m-%d')] for date in data.index.to_list()]))
values_indicator = list(reversed([[round(value,2)] for value in data.values.tolist()]))
sheet.update(values=values_indicator, range_name=start_cell.address, value_input_option=ValueInputOption.user_entered)
sheet.update(values=values_dates, range_name="I5", value_input_option=ValueInputOption.user_entered)

View File

@@ -12,7 +12,6 @@ idna==3.8
loguru==0.7.2
lxml==5.3.0
multitasking==0.0.11
numpy==2.1.0
oauthlib==3.2.2
pandas==2.2.2
peewee==3.17.6
@@ -24,6 +23,7 @@ pytz==2024.1
requests==2.32.3
requests-oauthlib==2.0.0
rsa==4.9
setuptools==74.0.0
six==1.16.0
soupsieve==2.6
tzdata==2024.1