]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - script.py
Include fees in computation
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / script.py
1 import ccxt
2 # Put your poloniex api key in market.py
3 from market import market
4
5 def static_var(varname, value):
6 def decorate(func):
7 setattr(func, varname, value)
8 return func
9 return decorate
10
11 max_digits = 18
12
13 repartition_pertenthousand = {
14 "BTC": 2857,
15 "ZEC": 3701,
16 "DOGE": 1805,
17 "DGB": 1015,
18 "SC": 623,
19 }
20
21
22 def formatted_price(value):
23 return round(value / 10**max_digits, 8)
24
25 @static_var("cache", {})
26 def get_ticker(c1, c2, market):
27 def invert(ticker):
28 return {
29 "inverted": True,
30 "bid": float(1/ticker["ask"]),
31 "ask": float(1/ticker["bid"]),
32 "bidA": float(1/ticker["askA"]),
33 "askA": float(1/ticker["bidA"]),
34 "bidE": float(1/ticker["askE"]),
35 "askE": float(1/ticker["bidE"]),
36 }
37 def augment_ticker(ticker):
38 bid_factor = 1.01
39 ask_factor = 0.99
40 fees = fetch_fees(market)
41 # FIXME: need to do better than just a multiplier
42 ticker.update({
43 "inverted": False,
44 # Adjusted
45 "bidA": ticker["bid"] * bid_factor,
46 "askA": ticker["ask"] * ask_factor,
47 # Expected in the end
48 "bidE": ticker["bid"] * bid_factor * (1 - fees["maker"]),
49 "askE": ticker["ask"] * ask_factor * (1 - fees["maker"]),
50 # fees
51 "bidF": ticker["bid"] * bid_factor * fees["maker"],
52 "askF": ticker["ask"] * ask_factor * fees["maker"],
53 })
54
55 if (c1, c2, market.__class__) in get_ticker.cache:
56 return get_ticker.cache[(c1, c2, market.__class__)]
57 if (c2, c1, market.__class__) in get_ticker.cache:
58 return invert(get_ticker.cache[(c2, c1, market.__class__)])
59
60 try:
61 get_ticker.cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2))
62 augment_ticker(get_ticker.cache[(c1, c2, market.__class__)])
63 except ccxt.ExchangeError:
64 try:
65 get_ticker.cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1))
66 augment_ticker(get_ticker.cache[(c2, c1, market.__class__)])
67 except ccxt.ExchangeError:
68 get_ticker.cache[(c1, c2, market.__class__)] = None
69 return get_ticker(c1, c2, market)
70
71 def fetch_balances(market):
72 balances = {}
73 fetched_balance = market.fetch_balance()
74 for key, value in fetched_balance["total"].items():
75 if value > 0:
76 balances[key] = int(value * 10**max_digits)
77 return balances
78
79 @static_var("cache", {})
80 def fetch_fees(market):
81 if market.__class__ not in fetch_fees.cache:
82 fetch_fees.cache[market.__class__] = market.fetch_fees()
83 return fetch_fees.cache[market.__class__]
84
85 def assets_value(assets, market, base_currency="BTC"):
86 repartition_in_base_currency = {}
87 for currency, asset_value in assets.items():
88 if currency == base_currency:
89 repartition_in_base_currency[currency] = [asset_value, 0]
90 else:
91 asset_ticker = get_ticker(currency, base_currency, market)
92 if asset_ticker is None:
93 raise Exception("This asset is not available in the chosen market")
94 repartition_in_base_currency[currency] = [
95 int(asset_ticker["bidE"] * asset_value),
96 int(asset_ticker["bidF"] * asset_value)
97 ]
98
99 return repartition_in_base_currency
100
101 def dispatch_assets(base_currency_value, repartition_pertenthousand, market, base_currency="BTC"):
102 sum_pertenthousand = sum([v for k, v in repartition_pertenthousand.items()])
103 repartition_in_base_currency = {}
104 for currency, ptt in repartition_pertenthousand.items():
105 repartition_in_base_currency[currency] = int(ptt * base_currency_value / sum_pertenthousand)
106 return repartition_in_base_currency
107
108 def compute_moves(current_assets, repartition_pertenthousand, market, no_fees=True, base_currency="BTC"):
109 value_in_base = assets_value(current_assets, market, base_currency=base_currency)
110 total_base_value = sum([ v[0] for k, v in value_in_base.items()])
111
112 new_repartition = dispatch_assets(total_base_value, repartition_pertenthousand, market, base_currency=base_currency)
113 mouvements = {}
114
115 if no_fees:
116 for key in set(value_in_base.keys()).union(set(new_repartition.keys())):
117 mouvements[key] = value_in_base.get(key, [0, 0])[0] - new_repartition.get(key, 0)
118 else:
119 for key in set(value_in_base.keys()).union(set(new_repartition.keys())):
120 value, fee = value_in_base.get(key, [0, 0])
121 mouvements[key] = [value - new_repartition.get(key, 0), fee]
122
123 return mouvements
124
125 def compute_order(currency, value, market, base_currency="BTC"):
126 if currency == base_currency or value == 0:
127 return [None, 0, False]
128
129 asset_ticker = get_ticker(currency, base_currency, market)
130 if asset_ticker["inverted"]:
131 asset_ticker = get_ticker(base_currency, currency, market)
132 if value > 0:
133 rate = asset_ticker["askA"]
134 return ["buy", rate, True]
135 else:
136 rate = asset_ticker["bidA"]
137 return ["sell", rate, True]
138 else:
139 if value > 0:
140 rate = asset_ticker["bidA"]
141 return ["sell", rate, False]
142 else:
143 rate = asset_ticker["askA"]
144 return ["buy", rate, False]
145
146 def make_order(currency, value, market, base_currency="BTC"):
147 action, rate, inverted = compute_order(currency, value, market, base_currency=base_currency)
148 amount = formatted_price(abs(value))
149 if not inverted:
150 symbol = "{}/{}".format(currency, base_currency)
151 else:
152 symbol = "{}/{}".format(base_currency, currency)
153 return market.create_order(symbol, 'limit', action, amount, price=rate)
154
155 def make_orders(current_assets, repartition_pertenthousand, market, base_currency="BTC"):
156 mouvements = compute_moves(
157 current_assets,
158 repartition_pertenthousand,
159 market,
160 base_currency=base_currency)
161
162 results = []
163 for currency, value in sorted(mouvements.items(), key=lambda x: x[1]):
164 # FIXME: wait for sales to finish
165 results.append(make_order(currency, value, market, base_currency=base_currency))
166 return results
167
168 def print_assets(assets, indent="", market=None, base_currency="BTC"):
169 if market is not None:
170 format_string = "{}{} {} ({} {})"
171 else:
172 format_string = "{}{} {}"
173 base_currency_price = 0
174
175 for currency, value in assets.items():
176 if market is not None:
177 asset_ticker = get_ticker(currency, base_currency, market)
178 base_currency_price = asset_ticker["bidE"] * value
179 print(format_string.format(
180 indent,
181 formatted_price(value),
182 currency,
183 formatted_price(base_currency_price),
184 base_currency))
185
186 def print_orders(current_assets, repartition_pertenthousand, market, base_currency="BTC"):
187 mouvements = compute_moves(
188 current_assets,
189 repartition_pertenthousand,
190 market,
191 no_fees=False,
192 base_currency=base_currency)
193
194 for currency, [value, fee] in mouvements.items():
195 action, rate, inverted = compute_order(
196 currency,
197 value,
198 market,
199 base_currency=base_currency)
200 if action is not None:
201 currency_price = int(value / rate)
202
203 if not inverted:
204 c1, c2 = [base_currency, currency]
205 v1, v2 = [value, currency_price]
206 else:
207 c1, c2 = [currency, base_currency]
208 v1, v2 = [currency_price, value]
209
210 print("need to {} {} {}'s worth of {}, i.e. {} {} ( + {} {} fee)".format(
211 action,
212 formatted_price(abs(v1)), c1,
213 c2,
214 formatted_price(abs(v2)), c2,
215 formatted_price(fee), c2))
216
217 current_assets = fetch_balances(market)
218 print_orders(current_assets, repartition_pertenthousand, market)