]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blame - store.py
Add processors
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / store.py
CommitLineData
6ca5a1ec 1import portfolio
3d0247f9
IB
2import simplejson as json
3from decimal import Decimal as D, ROUND_DOWN
4from datetime import date, datetime
aca4d437 5import inspect
6ca5a1ec 6
3d0247f9
IB
7__all__ = ["BalanceStore", "ReportStore", "TradeStore"]
8
9class ReportStore:
f86ee140
IB
10 def __init__(self, market, verbose_print=True):
11 self.market = market
12 self.verbose_print = verbose_print
3d0247f9 13
f86ee140
IB
14 self.logs = []
15
16 def print_log(self, message):
3d0247f9 17 message = str(message)
f86ee140 18 if self.verbose_print:
3d0247f9
IB
19 print(message)
20
f86ee140 21 def add_log(self, hash_):
3d0247f9 22 hash_["date"] = datetime.now()
f86ee140 23 self.logs.append(hash_)
3d0247f9 24
f86ee140 25 def to_json(self):
3d0247f9
IB
26 def default_json_serial(obj):
27 if isinstance(obj, (datetime, date)):
28 return obj.isoformat()
be54a201 29 return str(obj)
f86ee140 30 return json.dumps(self.logs, default=default_json_serial)
3d0247f9 31
f86ee140
IB
32 def set_verbose(self, verbose_print):
33 self.verbose_print = verbose_print
3d0247f9 34
f86ee140
IB
35 def log_stage(self, stage):
36 self.print_log("-" * (len(stage) + 8))
37 self.print_log("[Stage] {}".format(stage))
3d0247f9 38
f86ee140 39 self.add_log({
3d0247f9
IB
40 "type": "stage",
41 "stage": stage,
42 })
43
f86ee140
IB
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))
3d0247f9 48
f86ee140 49 self.add_log({
3d0247f9 50 "type": "balance",
18167a3c 51 "tag": tag,
f86ee140 52 "balances": self.market.balances.as_json()
3d0247f9
IB
53 })
54
f86ee140 55 def log_tickers(self, amounts, other_currency,
3d0247f9
IB
56 compute_value, type):
57 values = {}
58 rates = {}
aca4d437
IB
59 if callable(compute_value):
60 compute_value = inspect.getsource(compute_value).strip()
61
3d0247f9
IB
62 for currency, amount in amounts.items():
63 values[currency] = amount.as_json()["value"]
64 rates[currency] = amount.rate
f86ee140 65 self.add_log({
3d0247f9
IB
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
f86ee140
IB
75 def log_dispatch(self, amount, amounts, liquidity, repartition):
76 self.add_log({
3d0247f9
IB
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
f86ee140 84 def log_trades(self, matching_and_trades, only):
3d0247f9
IB
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
f86ee140 91 self.add_log({
3d0247f9
IB
92 "type": "trades",
93 "only": only,
f86ee140 94 "debug": self.market.debug,
3d0247f9
IB
95 "trades": trades
96 })
97
f86ee140 98 def log_orders(self, orders, tick=None, only=None, compute_value=None):
aca4d437
IB
99 if callable(compute_value):
100 compute_value = inspect.getsource(compute_value).strip()
f86ee140
IB
101 self.print_log("[Orders]")
102 self.market.trades.print_all_with_order(ind="\t")
103 self.add_log({
3d0247f9
IB
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
f86ee140 111 def log_order(self, order, tick, finished=False, update=None,
3d0247f9 112 new_order=None, compute_value=None):
aca4d437
IB
113 if callable(compute_value):
114 compute_value = inspect.getsource(compute_value).strip()
3d0247f9 115 if finished:
f86ee140 116 self.print_log("[Order] Finished {}".format(order))
3d0247f9 117 elif update == "waiting":
f86ee140 118 self.print_log("[Order] {}, tick {}, waiting".format(order, tick))
3d0247f9 119 elif update == "adjusting":
f86ee140 120 self.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
3d0247f9 121 elif update == "market_fallback":
f86ee140 122 self.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick))
3d0247f9 123 elif update == "market_adjust":
f86ee140 124 self.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))
3d0247f9 125
f86ee140 126 self.add_log({
3d0247f9
IB
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
f86ee140
IB
135 def log_move_balances(self, needed, moving):
136 self.add_log({
3d0247f9 137 "type": "move_balances",
f86ee140 138 "debug": self.market.debug,
3d0247f9
IB
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
f86ee140
IB
143 def log_http_request(self, method, url, body, headers, response):
144 self.add_log({
3d0247f9
IB
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
f86ee140
IB
154 def log_error(self, action, message=None, exception=None):
155 self.print_log("[Error] {}".format(action))
3d0247f9 156 if exception is not None:
f86ee140 157 self.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception)))
3d0247f9 158 if message is not None:
f86ee140 159 self.print_log("\t{}".format(message))
3d0247f9 160
f86ee140 161 self.add_log({
3d0247f9
IB
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
f86ee140
IB
169 def log_debug_action(self, action):
170 self.print_log("[Debug] {}".format(action))
3d0247f9 171
f86ee140 172 self.add_log({
3d0247f9
IB
173 "type": "debug_action",
174 "action": action,
175 })
6ca5a1ec
IB
176
177class BalanceStore:
f86ee140
IB
178 def __init__(self, market):
179 self.market = market
180 self.all = {}
6ca5a1ec 181
f86ee140
IB
182 def currencies(self):
183 return self.all.keys()
6ca5a1ec 184
f86ee140 185 def in_currency(self, other_currency, compute_value="average", type="total"):
6ca5a1ec 186 amounts = {}
f86ee140 187 for currency, balance in self.all.items():
6ca5a1ec 188 other_currency_amount = getattr(balance, type)\
f86ee140 189 .in_currency(other_currency, self.market, compute_value=compute_value)
6ca5a1ec 190 amounts[currency] = other_currency_amount
f86ee140 191 self.market.report.log_tickers(amounts, other_currency,
3d0247f9 192 compute_value, type)
6ca5a1ec
IB
193 return amounts
194
f86ee140
IB
195 def fetch_balances(self, tag=None):
196 all_balances = self.market.ccxt.fetch_all_balances()
6ca5a1ec
IB
197 for currency, balance in all_balances.items():
198 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
f86ee140
IB
199 currency in self.all:
200 self.all[currency] = portfolio.Balance(currency, balance)
201 self.market.report.log_balances(tag=tag)
6ca5a1ec 202
f86ee140 203 def dispatch_assets(self, amount, liquidity="medium", repartition=None):
6ca5a1ec 204 if repartition is None:
f86ee140 205 repartition = portfolio.Portfolio.repartition(self.market, liquidity=liquidity)
6ca5a1ec
IB
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]
aca4d437 212 self.all.setdefault(currency, portfolio.Balance(currency, {}))
f86ee140 213 self.market.report.log_dispatch(amount, amounts, liquidity, repartition)
6ca5a1ec
IB
214 return amounts
215
f86ee140
IB
216 def as_json(self):
217 return { k: v.as_json() for k, v in self.all.items() }
3d0247f9 218
6ca5a1ec 219class TradeStore:
f86ee140
IB
220 def __init__(self, market):
221 self.market = market
222 self.all = []
6ca5a1ec 223
aca4d437
IB
224 @property
225 def pending(self):
226 return list(filter(lambda t: not t.is_fullfiled, self.all))
227
f86ee140 228 def compute_trades(self, values_in_base, new_repartition, only=None):
3d0247f9 229 computed_trades = []
6ca5a1ec 230 base_currency = sum(values_in_base.values()).currency
f86ee140 231 for currency in self.market.balances.currencies():
6ca5a1ec
IB
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))
1aa7d4fa 236
6ca5a1ec 237 if value_from.value * value_to.value < 0:
f86ee140 238 computed_trades.append(self.trade_if_matching(
1aa7d4fa 239 value_from, portfolio.Amount(base_currency, 0),
f86ee140
IB
240 currency, only=only))
241 computed_trades.append(self.trade_if_matching(
1aa7d4fa 242 portfolio.Amount(base_currency, 0), value_to,
f86ee140 243 currency, only=only))
6ca5a1ec 244 else:
f86ee140 245 computed_trades.append(self.trade_if_matching(
3d0247f9 246 value_from, value_to,
f86ee140 247 currency, only=only))
3d0247f9
IB
248 for matching, trade in computed_trades:
249 if matching:
f86ee140
IB
250 self.all.append(trade)
251 self.market.report.log_trades(computed_trades, only)
1aa7d4fa 252
f86ee140
IB
253 def trade_if_matching(self, value_from, value_to, currency,
254 only=None):
1aa7d4fa 255 trade = portfolio.Trade(value_from, value_to, currency,
f86ee140 256 self.market)
3d0247f9
IB
257 matching = only is None or trade.action == only
258 return [matching, trade]
6ca5a1ec 259
f86ee140 260 def prepare_orders(self, only=None, compute_value="default"):
3d0247f9 261 orders = []
aca4d437 262 for trade in self.pending:
6ca5a1ec 263 if only is None or trade.action == only:
3d0247f9 264 orders.append(trade.prepare_order(compute_value=compute_value))
f86ee140 265 self.market.report.log_orders(orders, only, compute_value)
6ca5a1ec 266
f86ee140
IB
267 def print_all_with_order(self, ind=""):
268 for trade in self.all:
3d0247f9 269 trade.print_with_order(ind=ind)
6ca5a1ec 270
f86ee140
IB
271 def run_orders(self):
272 orders = self.all_orders(state="pending")
3d0247f9 273 for order in orders:
6ca5a1ec 274 order.run()
f86ee140
IB
275 self.market.report.log_stage("run_orders")
276 self.market.report.log_orders(orders)
6ca5a1ec 277
f86ee140
IB
278 def all_orders(self, state=None):
279 all_orders = sum(map(lambda v: v.orders, self.all), [])
6ca5a1ec
IB
280 if state is None:
281 return all_orders
282 else:
283 return list(filter(lambda o: o.status == state, all_orders))
284
f86ee140
IB
285 def update_all_orders_status(self):
286 for order in self.all_orders(state="open"):
6ca5a1ec
IB
287 order.get_status()
288
289