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