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