]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - store.py
Various fixes/improvements
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / store.py
1 import portfolio
2 import simplejson as json
3 from decimal import Decimal as D, ROUND_DOWN
4 from datetime import date, datetime
5 import inspect
6
7 __all__ = ["BalanceStore", "ReportStore", "TradeStore"]
8
9 class ReportStore:
10 def __init__(self, market, verbose_print=True):
11 self.market = market
12 self.verbose_print = verbose_print
13
14 self.logs = []
15
16 def print_log(self, message):
17 message = str(message)
18 if self.verbose_print:
19 print(message)
20
21 def add_log(self, hash_):
22 hash_["date"] = datetime.now()
23 self.logs.append(hash_)
24
25 def to_json(self):
26 def default_json_serial(obj):
27 if isinstance(obj, (datetime, date)):
28 return obj.isoformat()
29 return str(obj)
30 return json.dumps(self.logs, default=default_json_serial)
31
32 def set_verbose(self, verbose_print):
33 self.verbose_print = verbose_print
34
35 def log_stage(self, stage):
36 self.print_log("-" * (len(stage) + 8))
37 self.print_log("[Stage] {}".format(stage))
38
39 self.add_log({
40 "type": "stage",
41 "stage": stage,
42 })
43
44 def log_balances(self, tag=None):
45 self.print_log("[Balance]")
46 for currency, balance in self.market.balances.all.items():
47 self.print_log("\t{}".format(balance))
48
49 self.add_log({
50 "type": "balance",
51 "tag": tag,
52 "balances": self.market.balances.as_json()
53 })
54
55 def log_tickers(self, amounts, other_currency,
56 compute_value, type):
57 values = {}
58 rates = {}
59 if callable(compute_value):
60 compute_value = inspect.getsource(compute_value).strip()
61
62 for currency, amount in amounts.items():
63 values[currency] = amount.as_json()["value"]
64 rates[currency] = amount.rate
65 self.add_log({
66 "type": "tickers",
67 "compute_value": compute_value,
68 "balance_type": type,
69 "currency": other_currency,
70 "balances": values,
71 "rates": rates,
72 "total": sum(amounts.values()).as_json()["value"]
73 })
74
75 def log_dispatch(self, amount, amounts, liquidity, repartition):
76 self.add_log({
77 "type": "dispatch",
78 "liquidity": liquidity,
79 "repartition_ratio": repartition,
80 "total_amount": amount.as_json(),
81 "repartition": { k: v.as_json()["value"] for k, v in amounts.items() }
82 })
83
84 def log_trades(self, matching_and_trades, only):
85 trades = []
86 for matching, trade in matching_and_trades:
87 trade_json = trade.as_json()
88 trade_json["skipped"] = not matching
89 trades.append(trade_json)
90
91 self.add_log({
92 "type": "trades",
93 "only": only,
94 "debug": self.market.debug,
95 "trades": trades
96 })
97
98 def log_orders(self, orders, tick=None, only=None, compute_value=None):
99 if callable(compute_value):
100 compute_value = inspect.getsource(compute_value).strip()
101 self.print_log("[Orders]")
102 self.market.trades.print_all_with_order(ind="\t")
103 self.add_log({
104 "type": "orders",
105 "only": only,
106 "compute_value": compute_value,
107 "tick": tick,
108 "orders": [order.as_json() for order in orders if order is not None]
109 })
110
111 def log_order(self, order, tick, finished=False, update=None,
112 new_order=None, compute_value=None):
113 if callable(compute_value):
114 compute_value = inspect.getsource(compute_value).strip()
115 if finished:
116 self.print_log("[Order] Finished {}".format(order))
117 elif update == "waiting":
118 self.print_log("[Order] {}, tick {}, waiting".format(order, tick))
119 elif update == "adjusting":
120 self.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
121 elif update == "market_fallback":
122 self.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick))
123 elif update == "market_adjust":
124 self.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))
125
126 self.add_log({
127 "type": "order",
128 "tick": tick,
129 "update": update,
130 "order": order.as_json(),
131 "compute_value": compute_value,
132 "new_order": new_order.as_json() if new_order is not None else None
133 })
134
135 def log_move_balances(self, needed, moving):
136 self.add_log({
137 "type": "move_balances",
138 "debug": self.market.debug,
139 "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() },
140 "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() },
141 })
142
143 def log_http_request(self, method, url, body, headers, response):
144 self.add_log({
145 "type": "http_request",
146 "method": method,
147 "url": url,
148 "body": body,
149 "headers": headers,
150 "status": response.status_code,
151 "response": response.text
152 })
153
154 def log_error(self, action, message=None, exception=None):
155 self.print_log("[Error] {}".format(action))
156 if exception is not None:
157 self.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception)))
158 if message is not None:
159 self.print_log("\t{}".format(message))
160
161 self.add_log({
162 "type": "error",
163 "action": action,
164 "exception_class": exception.__class__.__name__ if exception is not None else None,
165 "exception_message": str(exception) if exception is not None else None,
166 "message": message,
167 })
168
169 def log_debug_action(self, action):
170 self.print_log("[Debug] {}".format(action))
171
172 self.add_log({
173 "type": "debug_action",
174 "action": action,
175 })
176
177 class BalanceStore:
178 def __init__(self, market):
179 self.market = market
180 self.all = {}
181
182 def currencies(self):
183 return self.all.keys()
184
185 def in_currency(self, other_currency, compute_value="average", type="total"):
186 amounts = {}
187 for currency, balance in self.all.items():
188 other_currency_amount = getattr(balance, type)\
189 .in_currency(other_currency, self.market, compute_value=compute_value)
190 amounts[currency] = other_currency_amount
191 self.market.report.log_tickers(amounts, other_currency,
192 compute_value, type)
193 return amounts
194
195 def fetch_balances(self, tag=None):
196 all_balances = self.market.ccxt.fetch_all_balances()
197 for currency, balance in all_balances.items():
198 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
199 currency in self.all:
200 self.all[currency] = portfolio.Balance(currency, balance)
201 self.market.report.log_balances(tag=tag)
202
203 def dispatch_assets(self, amount, liquidity="medium", repartition=None):
204 if repartition is None:
205 repartition = portfolio.Portfolio.repartition(self.market, liquidity=liquidity)
206 sum_ratio = sum([v[0] for k, v in repartition.items()])
207 amounts = {}
208 for currency, (ptt, trade_type) in repartition.items():
209 amounts[currency] = ptt * amount / sum_ratio
210 if trade_type == "short":
211 amounts[currency] = - amounts[currency]
212 self.all.setdefault(currency, portfolio.Balance(currency, {}))
213 self.market.report.log_dispatch(amount, amounts, liquidity, repartition)
214 return amounts
215
216 def as_json(self):
217 return { k: v.as_json() for k, v in self.all.items() }
218
219 class TradeStore:
220 def __init__(self, market):
221 self.market = market
222 self.all = []
223
224 @property
225 def pending(self):
226 return list(filter(lambda t: not t.is_fullfiled, self.all))
227
228 def compute_trades(self, values_in_base, new_repartition, only=None):
229 computed_trades = []
230 base_currency = sum(values_in_base.values()).currency
231 for currency in self.market.balances.currencies():
232 if currency == base_currency:
233 continue
234 value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0))
235 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0))
236
237 if value_from.value * value_to.value < 0:
238 computed_trades.append(self.trade_if_matching(
239 value_from, portfolio.Amount(base_currency, 0),
240 currency, only=only))
241 computed_trades.append(self.trade_if_matching(
242 portfolio.Amount(base_currency, 0), value_to,
243 currency, only=only))
244 else:
245 computed_trades.append(self.trade_if_matching(
246 value_from, value_to,
247 currency, only=only))
248 for matching, trade in computed_trades:
249 if matching:
250 self.all.append(trade)
251 self.market.report.log_trades(computed_trades, only)
252
253 def trade_if_matching(self, value_from, value_to, currency,
254 only=None):
255 trade = portfolio.Trade(value_from, value_to, currency,
256 self.market)
257 matching = only is None or trade.action == only
258 return [matching, trade]
259
260 def prepare_orders(self, only=None, compute_value="default"):
261 orders = []
262 for trade in self.pending:
263 if only is None or trade.action == only:
264 orders.append(trade.prepare_order(compute_value=compute_value))
265 self.market.report.log_orders(orders, only, compute_value)
266
267 def print_all_with_order(self, ind=""):
268 for trade in self.all:
269 trade.print_with_order(ind=ind)
270
271 def run_orders(self):
272 orders = self.all_orders(state="pending")
273 for order in orders:
274 order.run()
275 self.market.report.log_stage("run_orders")
276 self.market.report.log_orders(orders)
277
278 def all_orders(self, state=None):
279 all_orders = sum(map(lambda v: v.orders, self.all), [])
280 if state is None:
281 return all_orders
282 else:
283 return list(filter(lambda o: o.status == state, all_orders))
284
285 def update_all_orders_status(self):
286 for order in self.all_orders(state="open"):
287 order.get_status()
288
289