Das hatte ich ja auch vor "einen Experten beauftragt", aber mir fehlt es halt u.a. an "Delegations"- und sonstigen Erfahrung. Und nun war ich ganz mit dem Script von User bords0 beschäftigt, weil das schon ziemlich nach dem aussah, was mir so vorschwebte.
Selbermachen oder in Auftrag geben? Fifo Buchhaltung
Bords0 und snafu,
was meint ihr, welche Möglichkeiten sind sinnvoll weiter vorzugehen?
Ich selber bin „Laie“ und mache das ganze hier nach der Methode „Learning by Doing“.
Mir wäre es natürlich lieb, wenn der Weg nicht mehr all zu lang ist, aber falsche Hoffnungen will ich mir auch nicht machen.
Snafu, willst du an deinem zweiten Script weitermachen? Bist du einfach an dem Thema interessiert? Macht das Sinn zwei Scripte? Zumindest könnte man die berechneten Zahlen dann vergleichen. Aber wenn ich dir alles ein zweites mal erklären muss kostet das Zeit. Das ist eher das Problem, als ein paar Taler springen lassen.
Bords0 würde es Sinn machen dein Script mit Git (jetzt) online zu stellen? Oder ist das nur Mehraufwand? Ich könnte z.B. in Bitcoinforen einen Beitrag posten um das zum testen anzubieten. Dann könnten Leute an Hand ihrer Daten Feedback geben. Oder es hat doch der ein oder andere was privat in die Richtung gemacht und will es dann beisteuern, was er bisher nicht öffentlich gemacht hat. Aber ich habe keine Ahnung von Git (was hochladen würde ich hinkriegen) und weiß nicht ob solche Aktionen (Mehraufwand) jetzt Sinn machen. Das wohl doch ziemlich „idealistisch“.
Was meinst du wie ich vorgehen soll? Was denkst du von deiner Seite was das Script noch bräuchte, außer dass es getestet werden muss? Kannst du das noch einbauen, „Sperre für ungewollte Verrechnung von früheren Verkäufen mit späteren Käufen“?
Ich erwarte nicht dich ewig kostenlos verpflichten zu können.
Ich habe angefangen von den Auswertungen etwas in eine Datei schreiben zu lassen.
Das klappt mit der Berechnung der Zeiten noch nicht. (Haltedauer heißt jetzt holdingOK/ holdingNO)
Wie bekommt man ({time_diff.days} days) von if /else in die for Schleife bzw. in die output-Datei?
Jetzt sieht es so aus:
Der ersten buy hat jetzt einen höheren Preis/Rate, so ergibt die erste Berechnung ein Minusbetrag. Das muss auch dabei sein.
was meint ihr, welche Möglichkeiten sind sinnvoll weiter vorzugehen?
Ich selber bin „Laie“ und mache das ganze hier nach der Methode „Learning by Doing“.
Mir wäre es natürlich lieb, wenn der Weg nicht mehr all zu lang ist, aber falsche Hoffnungen will ich mir auch nicht machen.
Wie gesagt, den Experten, der mir alle Probleme bei dem Fifo Ding abnimmt, hätte ich auch gerne beauftragt. __deets__ hatte ja leider keine Zeit.
Kenst du noch einen?
Snafu, willst du an deinem zweiten Script weitermachen? Bist du einfach an dem Thema interessiert? Macht das Sinn zwei Scripte? Zumindest könnte man die berechneten Zahlen dann vergleichen. Aber wenn ich dir alles ein zweites mal erklären muss kostet das Zeit. Das ist eher das Problem, als ein paar Taler springen lassen.
Bords0 würde es Sinn machen dein Script mit Git (jetzt) online zu stellen? Oder ist das nur Mehraufwand? Ich könnte z.B. in Bitcoinforen einen Beitrag posten um das zum testen anzubieten. Dann könnten Leute an Hand ihrer Daten Feedback geben. Oder es hat doch der ein oder andere was privat in die Richtung gemacht und will es dann beisteuern, was er bisher nicht öffentlich gemacht hat. Aber ich habe keine Ahnung von Git (was hochladen würde ich hinkriegen) und weiß nicht ob solche Aktionen (Mehraufwand) jetzt Sinn machen. Das wohl doch ziemlich „idealistisch“.
Ja in diesem Sinn können Daten inkonsistent sein.
Was meinst du wie ich vorgehen soll? Was denkst du von deiner Seite was das Script noch bräuchte, außer dass es getestet werden muss? Kannst du das noch einbauen, „Sperre für ungewollte Verrechnung von früheren Verkäufen mit späteren Käufen“?
Ich erwarte nicht dich ewig kostenlos verpflichten zu können.
Ich habe angefangen von den Auswertungen etwas in eine Datei schreiben zu lassen.
Code: Alles auswählen
output_pl.write(f"{sell_t}\t{rate_diff * amount:0.2f}\t{holdingNO}{holdingOK}\n")
Wie bekommt man ({time_diff.days} days) von if /else in die for Schleife bzw. in die output-Datei?
Jetzt sieht es so aus:
Code: Alles auswählen
2017-01-10 16:11:44 -48.69 {6}{398}
2017-01-10 16:11:44 0.00 {2}{398}
2017-01-14 22:22:11 210.90 {2}{398}
2018-02-15 09:22:55 34.52 {6}{398}
2018-02-15 09:22:55 76.47 {6}{402}
2018-02-15 09:22:55 97.82 {6}{399}
Code: Alles auswählen
import pandas as pd
import io
from datetime import timedelta, datetime
week = timedelta(weeks=1)
raw = io.StringIO("""DateTime, Type, Amount, Rate
2017-01-08 11:43:00,buy,0.10000001,777.33
2017-01-08 11:43:00,buy,1.00000002,210.55
2017-01-10 16:11:44,sell,0.10000002,290.44
2017-01-11 18:43:00,buy,0.30000003,300.88
2017-01-12 18:55:00,buy,0.40000004,311.22
2017-01-14 22:22:11,sell,0.90000000,444.88
2018-02-15 09:22:55,sell,0.80000008,555.77
""")
file = r'datafile.csv'
daten = pd.read_csv(raw, parse_dates=[0],
skipinitialspace=True) # damit die header vernünftig sind
output_pl = open("output_pl.txt","w")
output = open("output.txt","w")
buys = daten[daten.Type == "buy"]
sells = daten[daten.Type == "sell"]
def fifo(buys, sells):
iter_buys = buys.Amount.iteritems() # enumerate(buys)
i_buy, amount_buy = None, 0.0
for i_sell, amount_sell in sells.Amount.iteritems():
while amount_sell > 1e-10: # Falls es am Schluss genau aufgeht, kann die Rechengenauigkeit sonst ein Problem machen
if amount_buy == 0.0: # nächster Kauf wird benötigt
i_buy, amount_buy = next(iter_buys)
amount = min(amount_buy, amount_sell)
amount_sell -= amount
amount_buy -= amount
rate_diff = sells.at[i_sell, "Rate"] - buys.at[i_buy, "Rate"]
time_diff = sells.at[i_sell, "DateTime"] - buys.at[i_buy, "DateTime"]
yield i_sell, i_buy, amount, rate_diff, time_diff
for i_sell, i_buy, amount, rate_diff, time_diff in fifo(buys, sells):
sell_t = sells.at[i_sell, "DateTime"]
buy_t = buys.at[i_buy, "DateTime"]
sell_r = sells.at[i_sell, "Rate"]
buy_r = buys.at[i_buy, "Rate"]
output_pl.write(f"{sell_t}\t{rate_diff * amount:0.2f}\t{holdingNO}{holdingOK}\n")
output.write(f"\nSELL id {i_sell} {sell_t} (Amount -{amount:0.8f} BTC) {sell_r} EUR\n"
f" BUY id {i_buy} {buy_t} {amount:0.8f} BTC {buy_r} EUR\n"
f" Rate Diff {rate_diff:0.2f} EUR * {amount:0.8f} BTC = "
f"{rate_diff * amount:0.2f} EUR\n")
print(f"\nSELL id {i_sell} {sell_t} (Amount -{amount:0.8f} BTC) {sell_r} EUR")
print(f" BUY id {i_buy} {buy_t} {amount:0.8f} BTC {buy_r} EUR")
print(f" Rate Diff {rate_diff:0.2f} EUR * {amount:0.8f} BTC = {rate_diff * amount:0.2f} EUR")
if time_diff.days >= 365:
print(f" holding period > 1 year: OK ({time_diff.days} days)")
holdingOK = {time_diff.days}
else:
print(f" holding period > 1 year: NO ({time_diff.days} days)")
holdingNO = {time_diff.days}
Tut mir Leid.
Also ich glaube, dass du es eigentich so gut wie fertig hast... Aber das hängt natürlich ab, was du noch alles brauchst. Es war zum Beispiel ein Überraschung für mich, dass deine Daten nicht "sauber" sind.
Glaub ich nicht, man kann ja auf das Forum verweisen.
Ja, ich habs eingebaut, siehe unten. Ich rate aber dringend dazu, die Funktion fifo nur die Tupel (sell_id, buy_id, amount) ausgeben zu lassen, und nur in der Schleife im Hauptprogramm die Ausgabe zu machen.
Danke, das ist nett, und ich weiß das zu schätzen. Wenn du willst, spende einen Betrag, den du für angemessen hältst, z.B. an Spyder (www.spyder-ide.org), damit "arbeite" ich meistens.
Ich selbst möchte nichts.
Ja, das sieht seltsam aus. Du willst sowohl holdingNO als auch holdingOK ausgeben, es wird pro Schleifendurchlauf aber nur einer der Werte berechnet, und auch erst, nachdem du den Wert ausgegeben hast.Fiffio hat geschrieben: ↑Mittwoch 27. März 2019, 10:23 Ich habe angefangen von den Auswertungen etwas in eine Datei schreiben zu lassen.Das klappt mit der Berechnung der Zeiten noch nicht. (Haltedauer heißt jetzt holdingOK/ holdingNO)Code: Alles auswählen
output_pl.write(f"{sell_t}\t{rate_diff * amount:0.2f}\t{holdingNO}{holdingOK}\n")
Vielleicht möchtest du folgendes: Es wird einfach nur ausgegeben, ob es OK ist. Das könnte man in einer Variaben "is_holding_OK" speichern oder so.
Ich habe das Beispiel noch etwas erweitet, damit man die Verkäufe ohne passende Käufe besser sieht.
Code: Alles auswählen
import pandas as pd
import io
from datetime import timedelta, datetime
week = timedelta(weeks=1)
raw = io.StringIO("""DateTime, Type, Amount, Rate
2017-01-08 11:43:00,buy,0.10000001,777.33
2017-01-08 11:43:00,buy,1.00000001,210.55
2017-01-10 16:11:44,sell,0.10000002,290.44
2017-01-11 18:43:00,buy,0.30000003,300.88
2017-01-12 18:55:00,buy,0.40000004,311.22
2017-01-14 22:22:11,sell,0.90000000,444.88
2018-02-15 09:22:55,sell,0.80000008,555.77
2018-02-16 09:22:55,sell,1.00000008,445.55
2018-02-15 18:55:00,buy,0.40000004,311.22
""")
file = r'bitstamp1.csv'
daten = pd.read_csv(raw, parse_dates=[0],
skipinitialspace=True) # damit die header vernünftig sind
buys = daten[daten.Type == "buy"]
sells = daten[daten.Type == "sell"]
def fifo(buys, sells):
iter_buys = buys.Amount.iteritems() # enumerate(buys)
i_buy, amount_buy = None, 0.0
for i_sell, amount_sell in sells.Amount.iteritems():
while amount_sell > 1e-10: # Falls es am Schluss genau aufgeht, kann die Rechengenauigkeit sonst ein Problem machen
if amount_buy == 0.0: # nächster Kauf wird benötigt
# gibt es noch einen?
try:
i_buy, amount_buy = next(iter_buys)
except StopIteration:
yield i_sell, None, amount_sell
break
# wenn der nicht in der Vergangenheit liegt,
# dann kann er auch nicht verwendet werden.
if buys.at[i_buy, "DateTime"] > sells.at[i_sell, "DateTime"]:
yield i_sell, None, amount_sell
break
# verkaufen kann ich so viel, wie beim Kauf und beim Verkauf vorhanden ist
amount = min(amount_buy, amount_sell)
amount_sell -= amount
amount_buy -= amount
yield i_sell, i_buy, amount
# Ausgabedateien öffnen - und dafür sorgen, dass sie automatisch wieder geschlossen werden
with open("output_pl.txt","w") as output_pl:
with open("output.txt","w") as output:
# Jetzt über alle Verkaufs/Kaufs-Paare iterieren
for i_sell, i_buy, amount in fifo(buys, sells):
# Verkaufsinfos bestimmen
sell_t = sells.at[i_sell, "DateTime"]
sell_r = sells.at[i_sell, "Rate"]
# Ausgeben und diesen Schleifendurchlauf beenden, wenn kein Verkauf zustande kam
if i_buy is None:
print(f"\nSELL id {i_sell} {sell_t} (amount {amount:0.8f} BTC) {sell_r} EUR")
print("no matching buy found")
continue
# Kaufinfos bestimmen
buy_t = buys.at[i_buy, "DateTime"]
buy_r = buys.at[i_buy, "Rate"]
# Relevante berechnete Werte bestimmen
rate_diff = sell_r - buy_r
time_diff = sell_t - buy_t
is_holding_OK = "OK" if time_diff.days >= 365 else "not OK"
# Ausgabe auf Bildschirm
print(f"\nSELL id {i_sell} {sell_t} (amount {amount:0.8f} BTC) {sell_r} EUR")
print(f" BUY id {i_buy} {buy_t} {amount:0.8f} BTC {buy_r} EUR")
print(f" Rate Diff {rate_diff:0.2f} EUR * {amount:0.8f} BTC = {rate_diff * amount:0.2f} EUR")
# Ausgabe in Datei
output_pl.write(f"{sell_t}\t{rate_diff * amount:0.2f}\tholding time is {is_holding_OK} ({time_diff.days} days)\n")
output.write(f"\nSELL id {i_sell} {sell_t} (Amount -{amount:0.8f} BTC) {sell_r} EUR\n"
f" BUY id {i_buy} {buy_t} {amount:0.8f} BTC {buy_r} EUR\n"
f" Rate Diff {rate_diff:0.2f} EUR * {amount:0.8f} BTC = "
f"{rate_diff * amount:0.2f} EUR\n")
Anmerkung 2: Der Code ist nicht schön. Ich wollte aber nicht zu viel ändern, du sollst ihn ja noch wiedererkennen...
Habe das grundsätzliche Vorgehen nochmal neugeschrieben und dabei weitere Features von Pandas verwendet:
Die Umrechnung in Euro ist noch nicht eingebaut, lässt sich aber bei Interesse nachholen. Es wäre auch gut zu wissen, welche Form der Ausgabe gewünscht ist. Ich habe da einfach mal ein mir sinnvoll erscheinendes Format genommen.
Code: Alles auswählen
import io
import pandas as pd
TESTDATA = io.StringIO(
"""DateTime, Type, Amount, Rate
2017-01-08 11:43:00,buy,0.10000001,777.33
2017-01-08 11:43:00,buy,1.00000001,210.55
2017-01-10 16:11:44,sell,0.10000002,290.44
2017-01-11 18:43:00,buy,0.30000003,300.88
2017-01-12 18:55:00,buy,0.40000004,311.22
2017-01-14 22:22:11,sell,0.90000000,444.88
2018-02-15 09:22:55,sell,0.80000008,555.77
2018-02-16 09:22:55,sell,1.00000008,445.55
2018-02-15 18:55:00,buy,0.40000004,311.22
""")
def parse_transactions(csv_stream):
df = pd.read_csv(csv_stream, parse_dates=[0], skipinitialspace=True)
return (df[df.Type == 'sell'], df[df.Type == 'buy'])
def pick_buys(sell, buys, balance=0):
if balance >= sell.Amount:
# Already have enough BTC
return pd.DataFrame()
if buys.empty:
raise ValueError(f'Need more buys (sell ID #{sell.Index})')
mask = buys.Amount.cumsum() + balance < sell.Amount
picked = buys.loc[:mask.idxmin() + 1]
if picked.Amount.sum() < sell.Amount:
raise ValueError(f'Not enough BTC (sell ID #{sell.Index})')
return picked
def run_fifo(sells, buys):
balance = 0
for sell in sells.itertuples():
picked = pick_buys(sell, buys, balance)
picked_amount = 0 if picked.empty else picked.Amount.sum()
new_balance = balance + picked_amount - sell.Amount
print_info(sell, picked, balance, new_balance)
balance = new_balance
buys = buys.drop(picked.index)
def print_info(sell, buys, balance, new_balance):
print(f'Selling {sell.Amount} BTC (ID #{sell.Index})')
if not buys.empty:
print(f'Using balance of {balance} BTC and buys:')
for index, amount in buys.Amount.items():
print(f'{amount} BTC (ID #{index})')
print(f'Balance after sale: {new_balance} BTC')
print()
def main():
sells, buys = parse_transactions(TESTDATA)
try:
run_fifo(sells, buys)
except ValueError as e:
print('STOPPED:', e)
if __name__ == '__main__':
main()
Die pick_buys() nochmal in anderer Form (und IMHO verständlicher) geschrieben:
Ob das Filtern nun mit Python-Boardmitteln oder mit höherem Pandas-Anteil performanter wird, müsste man noch testen.
Code: Alles auswählen
def pick_buys(sell, buys, balance=0):
if balance >= sell.Amount:
# Already have enough BTC
return pd.DataFrame()
if buys.empty:
raise ValueError(f'Need more buys (sell ID #{sell.Index})')
enough = buys.Amount.cumsum() + balance >= sell.Amount
if not any(enough):
raise ValueError(f'Not enough BTC (sell ID #{sell.Index})')
# Stops slicing after the first index meeting the "enough" condition
return buys.loc[:enough.idxmax() + 1]
- __blackjack__
- User
- Beiträge: 13117
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Also mit der vorherigen Version mit „weniger Pandas“ würde ich sagen das es mit noch weniger Pandas verständlicher wäre, nämlich wenn man da aus den einzelnen Datensätzen Objekte machen würde, statt immer mit der Indirektion über Indizes in den `DataFrame` zu arbeiten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Mit etwas weniger Pandas und vorhergehender Berechnung des neuen Saldos:
Code: Alles auswählen
import io
import pandas as pd
TESTDATA = io.StringIO(
"""DateTime, Type, Amount, Rate
2017-01-08 11:43:00,buy,0.10000001,777.33
2017-01-08 11:43:00,buy,1.00000001,210.55
2017-01-10 16:11:44,sell,0.10000002,290.44
2017-01-11 18:43:00,buy,0.30000003,300.88
2017-01-12 18:55:00,buy,0.40000004,311.22
2017-01-14 22:22:11,sell,0.90000000,444.88
2018-02-15 09:22:55,sell,0.80000008,555.77
2018-02-16 09:22:55,sell,1.00000008,445.55
2018-02-15 18:55:00,buy,0.40000004,311.22
""")
def parse_transactions(csv_stream):
df = pd.read_csv(csv_stream, parse_dates=[0], skipinitialspace=True)
return (df[df.Type == 'sell'], df[df.Type == 'buy'])
def sell_buys(sell, buys, balance=0):
balance -= sell.Amount
if balance >= 0:
return balance, pd.DataFrame()
for buy in buys.itertuples():
balance += buy.Amount
if balance >= 0:
return balance, buys.loc[:buy.Index]
raise ValueError(f'Not enough BTC (Sell ID #{sell.Index})')
def run_fifo(sells, buys):
balance = 0
for sell in sells.itertuples():
new_balance, sold_buys = sell_buys(sell, buys, balance)
print_info(sell, sold_buys, balance, new_balance)
balance = new_balance
buys = buys.drop(sold_buys.index)
def print_info(sell, buys, balance, new_balance):
print(f'Selling {sell.Amount} BTC (ID #{sell.Index})')
if not buys.empty:
print(f'Using balance of {balance} BTC and buys:')
for index, amount in buys.Amount.items():
print(f'{amount} BTC (ID #{index})')
print(f'Balance after sale: {new_balance} BTC')
print()
def main():
sells, buys = parse_transactions(TESTDATA)
try:
run_fifo(sells, buys)
except ValueError as e:
print('STOPPED:', e)
if __name__ == '__main__':
main()
Gut dann spende ich etwas für freie Software. Bei Spyder ist das für mich aber etwas kompliziert https://opencollective.com/spyder/donate ich bin kein freund von Google Tracking und ReCaptcha etc. da müsste ich erst meine Surfgrundsätze über den Haufen werfen. Wenn du noch einen anderen Vorschlag hast wo man z.B. (unkompliziert) via Bitcoin spenden kann, dann teile das gerne mit.bords0 hat geschrieben: ↑Freitag 29. März 2019, 00:54 Danke, das ist nett, und ich weiß das zu schätzen. Wenn du willst, spende einen Betrag, den du für angemessen hältst, z.B. an Spyder (www.spyder-ide.org), damit "arbeite" ich meistens.
Ich selbst möchte nichts.
Das Script hat mir nun geholfen bei einzelnen Jahren die Summen zu vergleichen.
Für 2017 bin ich zum Glück gerade noch fertig geworden.
Zu tun gibt es immer noch, aber ich brauche jetzt unbedingt eine Pause.
Snafu, habe dein Script kurz ausprobiert, müsste mich damit aber länger beschäftigen.
Vielen Dank an alle für die viele Hilfe! Wie gesagt, jetzt brauche ich zuerst eine richtige Pause. Melde mich wieder.