Was soll eigentlich passieren, wenn z.B. 0,8 BTC durch die Käufe vorhanden sind und der nächste Sell 0,3 BTC vorgibt? Also was passiert mit der Differenz von 0,5 BTC, die dabei entsteht? Ist die einfach weg oder bleibt die trotz Verkauf irgendwie doch vorhanden und kann für den nächsten Verkauf mitgenutzt werden? Ich denke mal eher letzteres, also dass der letzte Einkauf nur zum Teil verkauft wird, oder?
Ich habe hier etwas gebastelt, wie ich an die Sache herangehen würde:
https://gist.github.com/seblin/bbf61cb8 ... 226ea972e8
Allerdings ist die Verrechnung der Käufe noch nicht korrekt implementiert, da Einkäufe zum Teil schon als Gewinn verbucht werden, obwohl sie noch nicht bzw noch nicht komplett durch einen Verkauf gedeckt wurden.
Selbermachen oder in Auftrag geben? Fifo Buchhaltung
Ja genau. bords0 der das Script gemacht hat hat das hier beschrieben.snafu hat geschrieben: ↑Dienstag 26. März 2019, 21:50 Was soll eigentlich passieren, wenn z.B. 0,8 BTC durch die Käufe vorhanden sind und der nächste Sell 0,3 BTC vorgibt? Also was passiert mit der Differenz von 0,5 BTC, die dabei entsteht? Ist die einfach weg oder bleibt die trotz Verkauf irgendwie doch vorhanden und kann für den nächsten Verkauf mitgenutzt werden? Ich denke mal eher letzteres, also dass der letzte Einkauf nur zum Teil verkauft wird, oder?
Das muss wohl in diesem Teil des Script sein? Wo der übrig geblieben Teil von z.B. 0,5 BTC gespeichert wird. Ichhabs noch nicht kapiert.
Code: Alles auswählen
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
Im Depo sind z.B. die Anzahl von 0,8 BTC durch Kauftrades vorhanden. Sagen wir gekauft bei Kurs zu 2000.- EUR. Dann geht der Kurs weiter hoch zu 2500.- EUR. Man möchte verkaufen und stellt eine Limitorder mit Verkaufspreis z.B. bei 2520.- ein. Doch der Kurs geht nicht höher und es wird nur ein Teil (Sell 0,3 BTC) bei dem Kurs von 2520.- verkauft. Der Rest 0,5 BTC bleibt als Limitorder stehen und der Kurs fällt wieder nach unten. Die 0,5 BTC müssen dann also zu einem anderen Zeitpunkt (und Kurs) verkauft werden. Usw.
Hab mal getestet ob dein Script meckert, wenn man von den Käufen etwas wegnimmt.snafu hat geschrieben: ↑Dienstag 26. März 2019, 21:50 Ich habe hier etwas gebastelt, wie ich an die Sache herangehen würde:
https://gist.github.com/seblin/bbf61cb8 ... 226ea972e8
Summ der Käufe und Verkäufe ist ja:
buy 1.80000010
sell -1.80000010
also ist das Depo am Ende bei null BTC.
Das Script müsste schon hier meckern. Bsp:
2017-01-08 11:43:00,buy,1.00000001,210.55 (manipuliert)
2017-01-08 11:43:00,buy,1.00000002,210.55 (original)
Code: Alles auswählen
----- Verkauf #1 -------------------------
Verbleibende Bitcoins: 1.0000000100000002
Gesamtsaldo (€): 88.92
Verbleibende Käufe:
DateTime Type Amount Rate
3 2017-01-11 18:43:00 buy 0.3 300.88
4 2017-01-12 18:55:00 buy 0.4 311.22
----- Verkauf #2 -------------------------
Verbleibende Bitcoins: 0.40000004
Gesamtsaldo (€): 132.12
Verbleibende Käufe:
DateTime Type Amount Rate
4 2017-01-12 18:55:00 buy 0.4 311.22
----- Verkauf #3 -------------------------
Verbleibende Bitcoins: 0.700000079
Gesamtsaldo (€): 229.94
Verbleibende Käufe:
Keine
Es müsste "Need" 0.00000001 heißen.
Erst wenn man den buy Datensatz von 1.00000002 bis auf 0.50000000 minimiert
2017-01-08 11:43:00,buy,0.50000000,210.55
heißt es:
Code: Alles auswählen
ValueError: Need 0.10000000099999999 BTC, have 0.30000006 BTC
Ich schau da morgen nochmal drüber. Habe ja angemerkt, dass es noch fehlerhaft ist.
Mein Code geht u.a. davon aus, dass für jeden Verkauf mindestens ein gekauftes Paket genutzt wird. Das ist natürlich falsch, da manchmal genug Reserven aus den Resten eines zuvor gekauften und nicht komplett ausgeschöpften Pakets vorhanden sind, um den Verkauf vollständig abwickeln zu können. Umgekehrt können Verkäufe aus Mangel an Bitcoins ab und zu auch gar nicht stattfinden.
Abläufe aus dem Finanzwesen nachzuprogrammieren und halt komplex und fehleranfällig. Ich an deiner Stelle hätte da schon längst einen Experten beauftragt - auch wenn der ein paar Taler kostet, aber das soll jeder selbst entscheiden...
Mein Code geht u.a. davon aus, dass für jeden Verkauf mindestens ein gekauftes Paket genutzt wird. Das ist natürlich falsch, da manchmal genug Reserven aus den Resten eines zuvor gekauften und nicht komplett ausgeschöpften Pakets vorhanden sind, um den Verkauf vollständig abwickeln zu können. Umgekehrt können Verkäufe aus Mangel an Bitcoins ab und zu auch gar nicht stattfinden.
Abläufe aus dem Finanzwesen nachzuprogrammieren und halt komplex und fehleranfällig. Ich an deiner Stelle hätte da schon längst einen Experten beauftragt - auch wenn der ein paar Taler kostet, aber das soll jeder selbst entscheiden...
StopIteration ist eine Exception. Die tritt hier auf, weil mitWie könnte diese Fehlermeldung abgefangen werden?
RuntimeError: generator raised StopIteration
Das passiert, wenn mehr verkauft werden soll als vorhanden ist. Beispiel:
Code: Alles auswählen
raw = io.StringIO("""DateTime, Type, Amount, Rate 2017-01-08 11:43:00,buy,0.10000001,200.11 2017-01-08 11:43:00,buy,1.00000001,210.55 # statt 1.00000002 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 # Achtung es sind nur 0.80000007 zum Verkauf da! """)
Code: Alles auswählen
i_buy, amount_buy = next(iter_buys)
Abfangen kann man das mit try: except:, mit dem man Ausnahmen (Exceptions) abfangen kann.
Das habe ich gemacht im folgenden Code. Der hat jetzt alle Berechnungen außerhalb der Funktion fifo (die eigentlich eine generator function ist), in der Schleife, in der du auch deinen eigenen Berechnungen und Ausgaben eingebaut hast. (Das rettet wenigstens ein wenig die Struktur, die inzwischen nicht so schön geworden ist.)
Im Fall, dass nicht genug Käufe vorhanden sind, gibt fifo jetzt None für die Nummer des Kaufs zurück, und die Ausgabe in der Schleife habe ich entsprechend angepasst.
Außerdem habe ich einen zusätzlichen Verkauf eingebaut, damit man sieht, was passiert, wenn mehrere Verkäufe nicht bedient werden können.
Wenn deine Daten nicht konsistent sind, musst du evtl. auch aufpassen, dass keine Verkäufe mit Käufen verrechnet werden, die erst später kommen - aber dazu habe ich nichts eingebaut.
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,200.11
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
""")
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
try:
i_buy, amount_buy = next(iter_buys)
except StopIteration:
yield i_sell, None, amount_sell
break
amount = min(amount_buy, amount_sell)
amount_sell -= amount
amount_buy -= amount
yield i_sell, i_buy, amount
for i_sell, i_buy, amount in fifo(buys, sells):
sell_t = sells.at[i_sell, "DateTime"]
sell_r = sells.at[i_sell, "Rate"]
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
buy_t = buys.at[i_buy, "DateTime"]
buy_r = buys.at[i_buy, "Rate"]
rate_diff = sell_r - buy_r
time_diff = sell_t - buy_t
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" Haltedauer > 1 Jahr: OK ({time_diff.days} days)")
else:
print(f" Haltedauer > 1 Jahr: Nein ({time_diff.days} days)")
Code: Alles auswählen
SELL id 2 2017-01-10 16:11:44 (amount 0.10000001 BTC) 290.44 EUR
BUY id 0 2017-01-08 11:43:00 0.10000001 BTC 200.11 EUR
Rate Diff 90.33 EUR * 0.10000001 BTC = 9.03 EUR
Haltedauer > 1 Jahr: Nein (2 days)
SELL id 2 2017-01-10 16:11:44 (amount 0.00000001 BTC) 290.44 EUR
BUY id 1 2017-01-08 11:43:00 0.00000001 BTC 210.55 EUR
Rate Diff 79.89 EUR * 0.00000001 BTC = 0.00 EUR
Haltedauer > 1 Jahr: Nein (2 days)
SELL id 5 2017-01-14 22:22:11 (amount 0.90000000 BTC) 444.88 EUR
BUY id 1 2017-01-08 11:43:00 0.90000000 BTC 210.55 EUR
Rate Diff 234.33 EUR * 0.90000000 BTC = 210.90 EUR
Haltedauer > 1 Jahr: Nein (6 days)
SELL id 6 2018-02-15 09:22:55 (amount 0.10000000 BTC) 555.77 EUR
BUY id 1 2017-01-08 11:43:00 0.10000000 BTC 210.55 EUR
Rate Diff 345.22 EUR * 0.10000000 BTC = 34.52 EUR
Haltedauer > 1 Jahr: OK (402 days)
SELL id 6 2018-02-15 09:22:55 (amount 0.30000003 BTC) 555.77 EUR
BUY id 3 2017-01-11 18:43:00 0.30000003 BTC 300.88 EUR
Rate Diff 254.89 EUR * 0.30000003 BTC = 76.47 EUR
Haltedauer > 1 Jahr: OK (399 days)
SELL id 6 2018-02-15 09:22:55 (amount 0.40000004 BTC) 555.77 EUR
BUY id 4 2017-01-12 18:55:00 0.40000004 BTC 311.22 EUR
Rate Diff 244.55 EUR * 0.40000004 BTC = 97.82 EUR
Haltedauer > 1 Jahr: OK (398 days)
SELL id 6 2018-02-15 09:22:55 (amount 0.00000001 BTC) 555.77 EUR
no matching buy found
SELL id 7 2018-02-16 09:22:55 (amount 1.00000008 BTC) 445.55 EUR
no matching buy found
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.
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: 13004
- 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.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
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.