2 import simplejson
as json
3 from decimal
import Decimal
as D
, ROUND_DOWN
4 from datetime
import date
, datetime
6 __all__
= ["BalanceStore", "ReportStore", "TradeStore"]
13 def print_log(cls
, message
):
14 message
= str(message
)
19 def add_log(cls
, hash_
):
20 hash_
["date"] = datetime
.now()
21 cls
.logs
.append(hash_
)
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
)
32 def set_verbose(cls
, verbose_print
):
33 cls
.verbose_print
= verbose_print
36 def log_stage(cls
, stage
):
37 cls
.print_log("-" * (len(stage
) + 8))
38 cls
.print_log("[Stage] {}".format(stage
))
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
))
53 "balances": BalanceStore
.as_json()
57 def log_tickers(cls
, market
, amounts
, other_currency
,
61 for currency
, amount
in amounts
.items():
62 values
[currency
] = amount
.as_json()["value"]
63 rates
[currency
] = amount
.rate
66 "compute_value": compute_value
,
68 "currency": other_currency
,
71 "total": sum(amounts
.values()).as_json()["value"]
75 def log_dispatch(cls
, amount
, amounts
, liquidity
, repartition
):
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() }
85 def log_trades(cls
, matching_and_trades
, only
, debug
):
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
)
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")
106 "compute_value": compute_value
,
108 "orders": [order
.as_json() for order
in orders
if order
is not None]
112 def log_order(cls
, order
, tick
, finished
=False, update
=None,
113 new_order
=None, compute_value
=None):
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
))
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
135 def log_move_balances(cls
, needed
, moving
, debug
):
137 "type": "move_balances",
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() }
,
144 def log_http_request(cls
, method
, url
, body
, headers
, response
):
146 "type": "http_request",
151 "status": response
.status_code
,
152 "response": response
.text
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
))
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,
172 def log_debug_action(cls
, action
):
173 cls
.print_log("[Debug] {}".format(action
))
176 "type": "debug_action",
185 return cls
.all
.keys()
188 def in_currency(cls
, other_currency
, market
, compute_value
="average", type="total"):
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
194 ReportStore
.log_tickers(market
, amounts
, other_currency
,
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 \
204 cls
.all
[currency
] = portfolio
.Balance(currency
, balance
)
205 ReportStore
.log_balances(market
)
208 def dispatch_assets(cls
, amount
, liquidity
="medium", repartition
=None):
209 if repartition
is None:
210 repartition
= portfolio
.Portfolio
.repartition(liquidity
=liquidity
)
211 sum_ratio
= sum([v
[0] for k
, v
in repartition
.items()])
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
, {})
219 ReportStore
.log_dispatch(amount
, amounts
, liquidity
, repartition
)
224 return { k: v.as_json() for k, v in cls.all.items() }
231 def compute_trades(cls
, values_in_base
, new_repartition
, only
=None, market
=None, debug
=False):
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
:
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))
241 if value_from
.value
* value_to
.value
< 0:
242 computed_trades
.append(cls
.trade_if_matching(
243 value_from
, portfolio
.Amount(base_currency
, 0),
244 currency
, only
=only
, market
=market
))
245 computed_trades
.append(cls
.trade_if_matching(
246 portfolio
.Amount(base_currency
, 0), value_to
,
247 currency
, only
=only
, market
=market
))
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
:
254 cls
.all
.append(trade
)
255 ReportStore
.log_trades(computed_trades
, only
, cls
.debug
)
258 def trade_if_matching(cls
, value_from
, value_to
, currency
,
259 only
=None, market
=None):
260 trade
= portfolio
.Trade(value_from
, value_to
, currency
,
262 matching
= only
is None or trade
.action
== only
263 return [matching
, trade
]
266 def prepare_orders(cls
, only
=None, compute_value
="default"):
268 for trade
in cls
.all
:
269 if only
is None or trade
.action
== only
:
270 orders
.append(trade
.prepare_order(compute_value
=compute_value
))
271 ReportStore
.log_orders(orders
, only
, compute_value
)
274 def print_all_with_order(cls
, ind
=""):
275 for trade
in cls
.all
:
276 trade
.print_with_order(ind
=ind
)
280 orders
= cls
.all_orders(state
="pending")
283 ReportStore
.log_stage("run_orders")
284 ReportStore
.log_orders(orders
)
287 def all_orders(cls
, state
=None):
288 all_orders
= sum(map(lambda v
: v
.orders
, cls
.all
), [])
292 return list(filter(lambda o
: o
.status
== state
, all_orders
))
295 def update_all_orders_status(cls
):
296 for order
in cls
.all_orders(state
="open"):