diff options
Diffstat (limited to 'store.py')
-rw-r--r-- | store.py | 224 |
1 files changed, 208 insertions, 16 deletions
@@ -1,6 +1,181 @@ | |||
1 | import portfolio | 1 | import portfolio |
2 | import simplejson as json | ||
3 | from decimal import Decimal as D, ROUND_DOWN | ||
4 | from datetime import date, datetime | ||
2 | 5 | ||
3 | __all__ = ["BalanceStore", "TradeStore"] | 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 | }) | ||
4 | 179 | ||
5 | class BalanceStore: | 180 | class BalanceStore: |
6 | all = {} | 181 | all = {} |
@@ -16,6 +191,8 @@ class BalanceStore: | |||
16 | other_currency_amount = getattr(balance, type)\ | 191 | other_currency_amount = getattr(balance, type)\ |
17 | .in_currency(other_currency, market, compute_value=compute_value) | 192 | .in_currency(other_currency, market, compute_value=compute_value) |
18 | amounts[currency] = other_currency_amount | 193 | amounts[currency] = other_currency_amount |
194 | ReportStore.log_tickers(market, amounts, other_currency, | ||
195 | compute_value, type) | ||
19 | return amounts | 196 | return amounts |
20 | 197 | ||
21 | @classmethod | 198 | @classmethod |
@@ -25,6 +202,7 @@ class BalanceStore: | |||
25 | if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ | 202 | if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ |
26 | currency in cls.all: | 203 | currency in cls.all: |
27 | cls.all[currency] = portfolio.Balance(currency, balance) | 204 | cls.all[currency] = portfolio.Balance(currency, balance) |
205 | ReportStore.log_balances(market) | ||
28 | 206 | ||
29 | @classmethod | 207 | @classmethod |
30 | def dispatch_assets(cls, amount, liquidity="medium", repartition=None): | 208 | def dispatch_assets(cls, amount, liquidity="medium", repartition=None): |
@@ -38,14 +216,20 @@ class BalanceStore: | |||
38 | amounts[currency] = - amounts[currency] | 216 | amounts[currency] = - amounts[currency] |
39 | if currency not in BalanceStore.all: | 217 | if currency not in BalanceStore.all: |
40 | cls.all[currency] = portfolio.Balance(currency, {}) | 218 | cls.all[currency] = portfolio.Balance(currency, {}) |
219 | ReportStore.log_dispatch(amount, amounts, liquidity, repartition) | ||
41 | return amounts | 220 | return amounts |
42 | 221 | ||
222 | @classmethod | ||
223 | def as_json(cls): | ||
224 | return { k: v.as_json() for k, v in cls.all.items() } | ||
225 | |||
43 | class TradeStore: | 226 | class TradeStore: |
44 | all = [] | 227 | all = [] |
45 | debug = False | 228 | debug = False |
46 | 229 | ||
47 | @classmethod | 230 | @classmethod |
48 | def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False): | 231 | def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False): |
232 | computed_trades = [] | ||
49 | cls.debug = cls.debug or debug | 233 | cls.debug = cls.debug or debug |
50 | base_currency = sum(values_in_base.values()).currency | 234 | base_currency = sum(values_in_base.values()).currency |
51 | for currency in BalanceStore.currencies(): | 235 | for currency in BalanceStore.currencies(): |
@@ -55,41 +239,49 @@ class TradeStore: | |||
55 | value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) | 239 | value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) |
56 | 240 | ||
57 | if value_from.value * value_to.value < 0: | 241 | if value_from.value * value_to.value < 0: |
58 | cls.add_trade_if_matching( | 242 | computed_trades.append(cls.trade_if_matching( |
59 | value_from, portfolio.Amount(base_currency, 0), | 243 | value_from, portfolio.Amount(base_currency, 0), |
60 | currency, only=only, market=market) | 244 | currency, only=only, market=market)) |
61 | cls.add_trade_if_matching( | 245 | computed_trades.append(cls.trade_if_matching( |
62 | portfolio.Amount(base_currency, 0), value_to, | 246 | portfolio.Amount(base_currency, 0), value_to, |
63 | currency, only=only, market=market) | 247 | currency, only=only, market=market)) |
64 | else: | 248 | else: |
65 | cls.add_trade_if_matching(value_from, value_to, | 249 | computed_trades.append(cls.trade_if_matching( |
66 | currency, only=only, market=market) | 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) | ||
67 | 256 | ||
68 | @classmethod | 257 | @classmethod |
69 | def add_trade_if_matching(cls, value_from, value_to, currency, | 258 | def trade_if_matching(cls, value_from, value_to, currency, |
70 | only=None, market=None): | 259 | only=None, market=None): |
71 | trade = portfolio.Trade(value_from, value_to, currency, | 260 | trade = portfolio.Trade(value_from, value_to, currency, |
72 | market=market) | 261 | market=market) |
73 | if only is None or trade.action == only: | 262 | matching = only is None or trade.action == only |
74 | cls.all.append(trade) | 263 | return [matching, trade] |
75 | return True | ||
76 | return False | ||
77 | 264 | ||
78 | @classmethod | 265 | @classmethod |
79 | def prepare_orders(cls, only=None, compute_value="default"): | 266 | def prepare_orders(cls, only=None, compute_value="default"): |
267 | orders = [] | ||
80 | for trade in cls.all: | 268 | for trade in cls.all: |
81 | if only is None or trade.action == only: | 269 | if only is None or trade.action == only: |
82 | trade.prepare_order(compute_value=compute_value) | 270 | orders.append(trade.prepare_order(compute_value=compute_value)) |
271 | ReportStore.log_orders(orders, only, compute_value) | ||
83 | 272 | ||
84 | @classmethod | 273 | @classmethod |
85 | def print_all_with_order(cls): | 274 | def print_all_with_order(cls, ind=""): |
86 | for trade in cls.all: | 275 | for trade in cls.all: |
87 | trade.print_with_order() | 276 | trade.print_with_order(ind=ind) |
88 | 277 | ||
89 | @classmethod | 278 | @classmethod |
90 | def run_orders(cls): | 279 | def run_orders(cls): |
91 | for order in cls.all_orders(state="pending"): | 280 | orders = cls.all_orders(state="pending") |
281 | for order in orders: | ||
92 | order.run() | 282 | order.run() |
283 | ReportStore.log_stage("run_orders") | ||
284 | ReportStore.log_orders(orders) | ||
93 | 285 | ||
94 | @classmethod | 286 | @classmethod |
95 | def all_orders(cls, state=None): | 287 | def all_orders(cls, state=None): |