This commit is contained in:
2026-01-15 09:06:47 +01:00
parent aa4b41d7a7
commit c468746108
11 changed files with 1961911 additions and 60 deletions

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM --platform=linux/amd64 python:3
ADD main.py .
ADD config.py .
ADD functions.py .
RUN pip install --no-cache-dir -r requirements.txt
CMD [ "python", "./main.py" ]

View File

@@ -12,7 +12,10 @@ Current Features:
- Dividends ARE calculated correctly...YEEEA!
- Information/KPIs on the whole portfolio
- configuration file
- updating TRML screens (big numbers only)
- Including TRML as a display target
Current Bugs
- Discovered a bug, where the yf-data is a lot lower than it should for historic values. The difference drifts further the further you look into the past
Current Limitation:
- Additional trackers need to be added to the investments-table manually
@@ -20,5 +23,4 @@ Current Limitation:
Feature Pipeline:
- Secure way of storing secret keys
- Substracting the effect of inflation
- Including TRML as a display target
- Realoading configuration with every iteration

View File

@@ -3,12 +3,12 @@
# Program Functionality Switch
update_notion = True
update_TRMNL = True
calculate_benchmark = False
calculate_benchmark = True
# Program Functionality Configuration
programm_cooldown_time = 15 # Programm cooldown timer in minutes
api_cooldowm_time = 0.1 # API cooldown timer in minutes
trmnl_granularity = 70 # Days in between two data points in the TRMNL chart
trmnl_granularity = 80 # Days in between two data points in the TRMNL chart
ticker_benchmark = "VGWL.DE" # Ticker to benchmark the trades against
# Programm Execution Configuration
@@ -39,3 +39,4 @@ notion_db_id_performance = "1c010a5f51bd806f90d8e76a1286cfd4"
trmnl_headers = {"Content-Type": "application/json"}
trmnl_url_chart_1 = "https://usetrmnl.com/api/custom_plugins/334ea2ed-1f20-459a-bea5-dca2c8cf7714"
trmnl_url_chart_2 = "https://usetrmnl.com/api/custom_plugins/72950759-38df-49eb-99fb-b3e2e67c385e"
trmnl_url_chart_3 = "https://usetrmnl.com/api/custom_plugins/a975543a-51dc-4793-b7fa-d6a101dc4025"

View File

@@ -150,13 +150,56 @@ def fetch_last_key_from_dict(dict):
def add_benchmark_ticker(tickers, ticker_benchmarkt):
tickers.append(ticker_benchmarkt)
logging(logging_level="success")
return tickers
# CREATE BENCHMARK TRADES
def create_benchmark_trades(trades):
def create_benchmark_trades(trades, yf_data):
# Prepertion
benchmark_trades = {}
i = 0
# Creating benchmark trades
try:
benchmark_trades = trades
for trade_id in benchmark_trades:
benchmark_trades[trade_id]['ticker'] = config.ticker_benchmark
for trade_id in trades:
# Benchmark-id
i = i+1
benchmark_id = "benchmark" + str(i)
# Copy raw trades
benchmark_trades[benchmark_id] = trades[trade_id]
benchmark_trades[benchmark_id]["ticker"] = config.ticker_benchmark
# Calculate amount invested
amount_invested = benchmark_trades[benchmark_id]["units"] * benchmark_trades[benchmark_id]["course_open"]
# Change course-open for benchmark-ticker performance calculation
success = False
index_date = benchmark_trades[benchmark_id]["date_open"]
while success == False:
try:
course_open_new = yf_data[config.ticker_benchmark].at[index_date, 'Close']
success = True
except:
index_date = index_date + datetime.timedelta(days=1)
benchmark_trades[benchmark_id]["course_open"] = course_open_new # type: ignore
# Change amount for benchmark-ticker performance calculation
benchmark_trades[benchmark_id]["units"] = amount_invested / course_open_new # type: ignore
# Change course-open for benchmark-ticker performance calculation, if relevant
if trades[trade_id]["date_close"] != 0:
success = False
index_date = benchmark_trades[benchmark_id]["date_close"]
while success == False:
try:
course_close_new = yf_data[config.ticker_benchmark].at[index_date, 'Close']
success = True
except:
index_date = index_date + datetime.timedelta(days=1)
benchmark_trades[benchmark_id]["course_close"] = course_close_new # type: ignore
# Logging
logging(logging_level="success")
return benchmark_trades
except Exception as error_message:
@@ -164,6 +207,40 @@ def create_benchmark_trades(trades):
logging(logging_level="error", message=f"Failed with error: {error_message}")
return False
# MERGE BENCHMARK HISTORY & TICKER-HISTROY
def merge_histories(history_per_ticker, benchmark_history):
# Preperation
benchmark_ticker = config.ticker_benchmark
error_count = 0
# Merging Data
for index_date in history_per_ticker:
try:
history_per_ticker[index_date]["benchmark"] = {}
history_per_ticker[index_date]["benchmark"]["current_invested"] = benchmark_history[index_date][benchmark_ticker]["current_invested"]
history_per_ticker[index_date]["benchmark"]["total_dividends"] = benchmark_history[index_date][benchmark_ticker]["total_dividends"]
history_per_ticker[index_date]["benchmark"]["current_value"] = benchmark_history[index_date][benchmark_ticker]["current_value"]
history_per_ticker[index_date]["benchmark"]["current_irr"] = benchmark_history[index_date][benchmark_ticker]["current_irr"]
history_per_ticker[index_date]["benchmark"]["total_performanance"] = benchmark_history[index_date][benchmark_ticker]["total_performanance"]
except:
error_count = error_count +1
# Debugging
if config.selected_logging_level == "debug":
data = json.dumps(history_per_ticker, indent=2) # Converts a python-dictionary into a json
with open("history_per_ticker_with_benchmark.json", "w") as f:
f.write(data)
# Main Logging
if error_count == 0:
logging(logging_level="success")
return history_per_ticker
else:
logging(logging_level="warning")
logging(logging_level="warning", message=f"Error merging benchmark-history into ticker-history in {error_count} cases")
return False
# -------------------------- #
@@ -268,19 +345,25 @@ def fetch_format_notion_trades(db_id_trades):
format_errors = format_errors + 1
error_message = e
# Main Logging
if fetch_error == False & format_errors == 0:
logging(logging_level="success")
logging(logging_level="info", message=f"{number_of_trades} trades recieved and formated")
return trades
elif fetch_error == False & format_errors > 0:
logging(logging_level="warning")
logging(logging_level="warning", message=f"{format_errors} trades out of {number_of_trades} skiped...maybe due to missing values?")
return trades
# Logging
if fetch_error == True:
logging(logging_level="error")
logging(logging_level="error", message=f"Failed with error: {error_message}")
return False
else:
logging(logging_level="error")
logging(logging_level="error", message=f"Failed with error: {error_message}")
return False
# Writing Example File
if config.selected_logging_level == "debug":
with open("trades.json", "w") as f:
f.write(str(trades))
# Logging
if format_errors == 0:
logging(logging_level="success")
logging(logging_level="info", message=f"{number_of_trades} trades recieved and formated")
return trades
else:
logging(logging_level="warning")
logging(logging_level="warning", message=f"{format_errors} trades out of {number_of_trades} skiped...maybe due to missing values?")
return trades
# NOTION FETCH & FORMAT INVESTMENT OVERVIEW
def fetch_format_notion_investments(db_id_investments):
@@ -289,7 +372,6 @@ def fetch_format_notion_investments(db_id_investments):
format_errors = 0
number_of_investments = 0
# Download data & check for success
data = notion_get_pages(db_id_investments)
if data is True:
@@ -322,11 +404,11 @@ def fetch_format_notion_investments(db_id_investments):
# Main Logging
if fetch_error == False & format_errors == 0:
logging(logging_level="success")
logging(logging_level="info", message=f"{number_of_investments} trades recieved and formated")
logging(logging_level="info", message=f"{number_of_investments} investments recieved and formated")
return investments
elif fetch_error == False & format_errors > 0:
logging(logging_level="warning")
logging(logging_level="warning", message=f"{format_errors} trades out of {number_of_investments} skiped...maybe due to missing values?")
logging(logging_level="warning", message=f"{format_errors} investments out of {number_of_investments} skiped...maybe due to missing values?")
return investments
else:
logging(logging_level="error")
@@ -351,18 +433,18 @@ def fetch_format_yf_data(tickers):
try:
# Download data
api = yf.Ticker(ticker)
data = api.history(period="max")
data = api.history(period="max", auto_adjust=False)
except:
# Store error for later logging
fetch_errors = fetch_errors + 1
skip_formating = True
data = True
# If the download was successfull:
if skip_formating == False:
# Try formating the data
try:
# Convert to Pandas DataFrame
data = pd.DataFrame(data)
data = pd.DataFrame(data) # type: ignore
# Delete the columns "Stock Splits", "High", "Low" and "Open"
del data['Open']
@@ -713,7 +795,7 @@ def calc_history_per_trade(trades, yf_data):
return False
# CALC THE HISTORY PER TRADE & OVERALL
def calc_history_per_ticker(history_per_trade, tickers):
def calc_history_per_ticker(history_per_trade, tickers, trades):
# ------------------ CREATE JSON OBJECT
# Create the json-dict
@@ -748,7 +830,6 @@ def calc_history_per_ticker(history_per_trade, tickers):
dict_daily["total"]["total_dividends"] = 0
dict_daily["total"]["current_value"] = 0
dict_daily["total"]["current_irr"] = 0
dict_daily["total"]["current_irr"] = 0
dict_daily["total"]["total_performanance"] = 0
# Loop over each trade-entry for that day
@@ -830,6 +911,8 @@ def calc_history_per_ticker(history_per_trade, tickers):
logging(logging_level="error", message=f"Failed with error message: {error_message}")
return False
# --------------------------- #
# HISTORY SELECTION FUNCTIONS #
# --------------------------- #
@@ -1012,6 +1095,10 @@ def prep_trmnl_chart_udpate(history_to_show, series_to_show_1 = "total", data_to
series_to_show_1 = "Portfolio"
if series_to_show_2 == "total":
series_to_show_2 = "Portfolio"
if series_to_show_1 == "benchmark":
series_to_show_1 = "Benchmark: " + config.ticker_benchmark
if series_to_show_2 == "benchmark":
series_to_show_2 = "Benchmark: " + config.ticker_benchmark
# Generating nicer data titels
data_to_show_1 = data_to_show_1.replace("_", " ").capitalize()

417728
history_per_ticker.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1111920
history_per_trade.json Normal file

File diff suppressed because it is too large Load Diff

84
main.py
View File

@@ -1,6 +1,7 @@
import functions
import config
import imp
while True:
@@ -43,27 +44,6 @@ while True:
# ----------------------------------------- #
# PART 2: Calculating Benchmark performance #
# ----------------------------------------- #
# Configuration dependent execution:
if config.calculate_benchmark == True:
# Creating benchmark trades
print("Creating 'benchmark trades'...", end="", flush=True)
benchmark_trades = functions.create_benchmark_trades(trades)
# Calculating benchmark trades
print("Calculating the history per benchmark-trade...", end=" ", flush=True)
history_per_benchmark_trade = functions.calc_history_per_trade(benchmark_trades, yf_data)
###
# ICH BIN MIR UNSICHER, WIE ICH HIERMIT WEITER MACHEN SOLL
# ICH GLAUBE, ICH MUSS DIE HISTORY PER BENCHMARK-TICKER UND PER TICKER MERGEN
# DANN HABE ICH ABER DAS PROBLEM, DASS ICH DIE ECHTEN TRADES NICHT VON DEN BENCHMARKS UNTERSCHEIDEN KANN
###
# ------------------------------------------------ #
# PART 2: Updating the notion investments database #
# ------------------------------------------------ #
@@ -73,7 +53,7 @@ while True:
# Calculates & stores a history per ticker AND a total across all tickers indexed by the ticker name
print("Calculating history per ticker...", end=" ", flush=True)
history_per_ticker = functions.calc_history_per_ticker(history_per_trade, tickers)
history_per_ticker = functions.calc_history_per_ticker(history_per_trade, tickers, trades)
# Configuration dependent execution:
if config.update_notion == True:
@@ -87,8 +67,32 @@ while True:
functions.push_notion_investment_update(investments)
# ----------------------------------------- #
# PART 3: Calculating Benchmark performance #
# ----------------------------------------- #
# Configuration dependent execution:
if config.calculate_benchmark == True:
# Creating benchmark trades
print("Creating 'benchmark trades'...", end="", flush=True)
benchmark_trades = functions.create_benchmark_trades(trades, yf_data)
# Calculating benchmark trades
print("Calculating the history per benchmark-trade...", end=" ", flush=True)
history_per_benchmark_trade = functions.calc_history_per_trade(benchmark_trades, yf_data)
# Calculates & stores a history for the benchmark
print("Calculating benchmark-history overall...", end=" ", flush=True)
history_benchmark = functions.calc_history_per_ticker(history_per_benchmark_trade, tickers, benchmark_trades)
# Merging the benchmark_history into the ticker_history
print("Merging the benchmark-history into the ticker-history...", end=" ", flush=True)
history_per_ticker = functions.merge_histories(history_per_ticker, history_benchmark)
# --------------------------------- #
# PART 3: Updating the TRMNL Screen #
# PART 4: Updating the TRMNL Screen #
# --------------------------------- #
# Configuration dependent execution:
if config.update_TRMNL == True:
@@ -107,7 +111,7 @@ while True:
history_per_ticker_filtered,
series_to_show_1="total",
data_to_show_1="current_value",
series_to_show_2="DBPG.DE",
series_to_show_2="benchmark",
data_to_show_2="current_value"
)
@@ -121,7 +125,7 @@ while True:
history_per_ticker_filtered,
series_to_show_1="total",
data_to_show_1="current_irr",
series_to_show_2="DBPG.DE",
series_to_show_2="benchmark",
data_to_show_2="current_irr"
)
@@ -129,20 +133,38 @@ while True:
print("Updating a TERMNL screen...", end=" ", flush=True)
functions.push_trmnl_update_chart(trmnl_update_object, config.trmnl_url_chart_2, config.trmnl_headers)
# Prepare a new TRMNL update
print("Constructing a TERMNL update object...", end=" ", flush=True)
trmnl_update_object = functions.prep_trmnl_chart_udpate(
history_per_ticker_filtered,
series_to_show_1="total",
data_to_show_1="total_performanance",
series_to_show_2="benchmark",
data_to_show_2="total_performanance"
)
# Push the update to TRMNL
print("Updating a TERMNL screen...", end=" ", flush=True)
functions.push_trmnl_update_chart(trmnl_update_object, config.trmnl_url_chart_3, config.trmnl_headers)
# --------------------------- #
# PART 4: Cool off and repeat #
# PART 5: Cool off and repeat #
# --------------------------- #
# Logging
print("Completed cycle at: {}".format(functions.datetime.datetime.now()))
print("Waiting a few minutes before the next execution")
print("---------------------------------------------------------------------------")
# Clear variables
trades = {}
yf_data = {}
history_per_trade = {}
tickers = []
# Reload config
imp.reload(config)
# Logging
print("Completed cycle at: {}".format(functions.datetime.datetime.now()))
print("Waiting a few minutes before the next execution")
print("---------------------------------------------------------------------------")
# Wait for api-cooldown
functions.time.sleep(config.programm_cooldown_time * 60)

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
yfinance

1
trades.json Normal file

File diff suppressed because one or more lines are too long

229
trmnl_update_object.json Normal file
View File

@@ -0,0 +1,229 @@
{
"merge_variables": {
"big_numbers": {
"current_value": "197917.0",
"total_performanance": "44796.0",
"current_irr": "38.09"
},
"charts": [
{
"data": [
[
"2020-07-17T00:00:00",
77.16
],
[
"2020-10-05T00:00:00",
-18.14
],
[
"2020-12-24T00:00:00",
43.8
],
[
"2021-03-14T00:00:00",
-30.05
],
[
"2021-06-02T00:00:00",
6.35
],
[
"2021-08-21T00:00:00",
26.02
],
[
"2021-11-09T00:00:00",
101.49
],
[
"2022-01-28T00:00:00",
-6.21
],
[
"2022-04-18T00:00:00",
0.86
],
[
"2022-07-07T00:00:00",
-8.21
],
[
"2022-09-25T00:00:00",
-13.15
],
[
"2022-12-14T00:00:00",
-14.89
],
[
"2023-03-04T00:00:00",
4.24
],
[
"2023-05-23T00:00:00",
-22.33
],
[
"2023-08-11T00:00:00",
-0.26
],
[
"2023-10-30T00:00:00",
-4.23
],
[
"2024-01-18T00:00:00",
29.09
],
[
"2024-04-07T00:00:00",
43.56
],
[
"2024-06-26T00:00:00",
43.76
],
[
"2024-09-14T00:00:00",
20.46
],
[
"2024-12-03T00:00:00",
27.61
],
[
"2025-02-21T00:00:00",
29.27
],
[
"2025-05-12T00:00:00",
17.01
],
[
"2025-07-31T00:00:00",
51.26
],
[
"2025-10-19T00:00:00",
19.07
],
[
"2026-01-07T00:00:00",
38.09
]
],
"name": "Current IRR Portfolio"
},
{
"data": [
[
"2020-07-17T00:00:00",
60.84
],
[
"2020-10-05T00:00:00",
8.7
],
[
"2020-12-24T00:00:00",
28.66
],
[
"2021-03-14T00:00:00",
39.39
],
[
"2021-06-02T00:00:00",
32.15
],
[
"2021-08-21T00:00:00",
25.97
],
[
"2021-11-09T00:00:00",
38.41
],
[
"2022-01-28T00:00:00",
1.21
],
[
"2022-04-18T00:00:00",
10.65
],
[
"2022-07-07T00:00:00",
0.34
],
[
"2022-09-25T00:00:00",
-2.87
],
[
"2022-12-14T00:00:00",
-2.27
],
[
"2023-03-04T00:00:00",
7.53
],
[
"2023-05-23T00:00:00",
-12.24
],
[
"2023-08-11T00:00:00",
6.24
],
[
"2023-10-30T00:00:00",
-0.78
],
[
"2024-01-18T00:00:00",
13.15
],
[
"2024-04-07T00:00:00",
20.95
],
[
"2024-06-26T00:00:00",
20.4
],
[
"2024-09-14T00:00:00",
15.89
],
[
"2024-12-03T00:00:00",
25.82
],
[
"2025-02-21T00:00:00",
20.19
],
[
"2025-05-12T00:00:00",
7.54
],
[
"2025-07-31T00:00:00",
24.09
],
[
"2025-10-19T00:00:00",
13.4
],
[
"2026-01-07T00:00:00",
29.82
]
],
"name": "Current IRR Benchmark: VGWL.DE"
}
]
}
}