Wpis z mikrobloga

#gielda #portfelgrubasa #xtb #portfolioperformance #ossprimeme

Wrzucam poniżej skrypt Python importu operacji gotówkowych na rachunku z XTB do Portfolio Performance w wersji 2.1

Zmiany wobec wersji 2.0:
- Brak konieczności zmiany nazwy pliku .xlsx z XTB. Wrzucasz skrypcik do folderku z wyeksportowanym (nowym) raportem z XTB i hula od strzała
- W związku z powyższym eksport zdarzeń nietypowych został zmieniony z plików .xlsx do .csv
- Skrypt teraz uwzględnia liczbę posiadanych udziałów aktywów wypłacających dywidendę i wylicza dywidendę za udział

Poniższy skrypcik zapisujemy do pliku z rozszerzeniem .py albo .ipynb i odpalamy sobie w Pythonie (zobacz jak)

import glob
import pandas as pd

# Import excela z XTB
import warnings
warnings.filterwarnings('ignore', message='Workbook contains no default style')

file_name = glob.glob('*.xlsx')[0]

df = pd.read_excel(file_name, sheet_name='Cash Operations', skiprows=4)

df['Time'] = pd.to_datetime(df['Time'])
df['Day'] = df['Time'].dt.normalize()

# Detekcja nietypowych zdarzeń (corr) potencjalnie wymagających ręcznych korekt
corr = df[df['Comment'].str.contains('corr', case=False, na=False)]

if not(corr.empty):
corr.to_csv('XTB_corr.csv')

# Cleanup
df = df[df['Type'] != 'Total']

df = df.sort_values(by='ID').reset_index(drop=True)

df['Shares'] = pd.to_numeric( df['Comment'].str.extract(r'([0-9]+(?:/[0-9]+)?)\s*@')[0].str.split('/').str[0], errors='coerce')
df['Price'] = pd.to_numeric( df['Comment'].str.extract(r'@\s*([0-9]*\.?[0-9]+)')[0], errors='coerce' )

df['Shares_div'] = pd.to_numeric( df['Comment'].str.extract(r"PLN\s+([\d.]+)")[0], errors='coerce')
df['Shares_div'] = df['Amount'] / df['Shares_div']

# Detekcja transakcji w walucie innej niż waluta rachunku
tr = df[ df['Type'].isin(['Stock purchase', 'Stock sell'])].copy()
tr['Amount_check'] = (tr['Shares'] * tr['Price']).round(2)
fx_tr = tr[ (tr['Amount'].abs() - tr['Amount_check']).abs() > 0.02 ]

if not(fx_tr.empty):
fx_tr.to_csv('XTB_fx_tr.csv')

df['Gross Amount'] = float('nan')
df['Exchange Rate'] = float('nan')

df.loc[fx_tr.index, 'Gross Amount'] = ( df.loc[fx_tr.index, 'Shares'] * df.loc[fx_tr.index, 'Price'] ).round(2)
df.loc[fx_tr.index, 'Exchange Rate'] = ( df.loc[fx_tr.index, 'Amount'].abs() / df.loc[fx_tr.index, 'Gross Amount'] )

df = df.drop(columns=['ID', 'Product', 'Comment'])

# Przypisanie podatków od odsetek do wiersza z odsetkami
df['Taxes'] = float('nan')

ffi = ( df[df['Type'] == 'Free funds interest'].sort_values('Time').copy() )

ffi_tax = ( df[df['Type'] == 'Free funds interest tax'][['Time', 'Amount']].sort_values('Time').rename(columns={'Amount': 'Taxes'}) )

ffi = pd.merge_asof( ffi, ffi_tax, on='Time', direction='nearest', tolerance=pd.Timedelta('1D'), suffixes=('', '_new') )

ffi['Taxes'] = ffi['Taxes_new']
ffi = ffi.drop(columns='Taxes_new')
ffi['Amount'] = ffi['Amount'] + ffi['Taxes'].fillna(0)

# Agregacja dywidend do 1 płatności na instrument dziennie, przypisanie podatku od dywidend do wiersza z dywidendami
div = ( df[df['Type'] == 'Dividend'].groupby(['Day', 'Ticker'], as_index=False).agg({'Time': 'first', 'Type': 'first', 'Amount': 'sum', 'Shares_div': 'sum'}) )

wht = ( df[df['Type'] == 'Withholding tax'].groupby(['Day', 'Ticker'], as_index=False)['Amount'].sum().rename(columns={'Amount': 'Taxes'}) )

div = div.merge(wht, on=['Day', 'Ticker'], how='left')

div['Amount'] = div['Amount'] + div['Taxes'].fillna(0)
div['Shares'] = div['Shares_div']

# Usuń zbędne oryginalne wiersze
df = df[ ~df['Type'].isin([ 'Dividend', 'Withholding tax', 'Free funds interest', 'Free funds interest tax']) ]

# Połącz z wcześniej przetworzonymi wierszami o odsetkach i dywidendach
df = (pd.concat([df, div, ffi], ignore_index=True).sort_values('Time').reset_index(drop=True))

df['Taxes'] = (-1)*df['Taxes']

# Yahoo Finance compatible endings
df['Ticker'] = df['Ticker'].str.replace('.PL', '.WA', regex=False)
df['Ticker'] = df['Ticker'].str.replace('.UK', '.L', regex=False)
df['Ticker'] = df['Ticker'].str.replace('.US', '', regex=False)
df['Ticker'] = df['Ticker'].str.replace('.FR', '.PA', regex=False)

df['Time'] = df['Time'].dt.strftime('%Y-%m-%dT%H:%M')

# Format to PP-importable csv (Polish)
df = df.rename(columns={
'Time': 'Data',
'Type': 'Typ',
'Taxes': 'Podatki',
'Shares': 'Akcje',
'Amount': 'Wartość',
'Gross Amount': 'Kwota brutto',
'Exchange Rate': 'Kurs wymiany',
'Ticker': 'Symbol giełdowy waloru',
'Instrument': 'Nazwa waloru'
})

df['Typ'] = df['Typ'].replace({
'Stock purchase': 'Kupno',
'Stock sell': 'Sprzedaj',
'Deposit': 'Depozyt',
'Withdrawal': 'Wypłata',
'Dividend': 'Dywidenda',
'Free funds interest': 'Odsetki',
'Custody fee': 'Opłaty'
})

# Treat eWallet transfers as deposits or withdrawals
df.loc[ (df['Typ'] == 'Transfer') & (df['Wartość'] > 0), 'Typ' ] = 'Depozyt'
df.loc[ (df['Typ'] == 'Transfer') & (df['Wartość'] < 0), 'Typ' ] = 'Wypłata'

# Reorder columns
df = df[ ['Data', 'Typ', 'Wartość', 'Kwota brutto', 'Kurs wymiany', 'Podatki', 'Akcje', 'Symbol giełdowy waloru'] ]

# Export to csv
df.to_csv('XTB_to_PP.csv', index=False, sep=';', decimal=',')

# Efekt końcowy
df
ossprime - #gielda #portfelgrubasa #xtb #portfolioperformance #ossprimeme

Wrzucam po...

źródło: Zrzut ekranu 2026-07-4 o 13.55.11

Pobierz
  • 3
  • Odpowiedz
  • Otrzymuj powiadomienia
    o nowych komentarzach