]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blob - market.py
Refactor the store to be more conciliant with multiple markets
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / market.py
1 from ccxt import ExchangeError
2 import ccxt_wrapper as ccxt
3 import time
4 from store import *
5
6 class Market:
7 debug = False
8 ccxt = None
9 report = None
10 trades = None
11 balances = None
12
13 def __init__(self, ccxt_instance, debug=False):
14 self.debug = debug
15 self.ccxt = ccxt_instance
16 self.ccxt._market = self
17 self.report = ReportStore(self)
18 self.trades = TradeStore(self)
19 self.balances = BalanceStore(self)
20
21 @classmethod
22 def from_config(cls, config, debug=False):
23 ccxt_instance = ccxt.poloniexE(config)
24
25 # For requests logging
26 ccxt_instance.session.origin_request = ccxt_instance.session.request
27 ccxt_instance.session._parent = ccxt_instance
28
29 def request_wrap(self, *args, **kwargs):
30 r = self.origin_request(*args, **kwargs)
31 self._parent._market.report.log_http_request(args[0],
32 args[1], kwargs["data"], kwargs["headers"], r)
33 return r
34 ccxt_instance.session.request = request_wrap.__get__(ccxt_instance.session,
35 ccxt_instance.session.__class__)
36
37 return cls(ccxt_instance, debug=debug)
38
39 def move_balances(self):
40 needed_in_margin = {}
41 moving_to_margin = {}
42
43 for currency in self.balances.all:
44 if self.balances.all[currency].margin_free != 0:
45 needed_in_margin[currency] = 0
46 for trade in self.trades.all:
47 if trade.value_to.currency not in needed_in_margin:
48 needed_in_margin[trade.value_to.currency] = 0
49 if trade.trade_type == "short":
50 needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
51 for currency, needed in needed_in_margin.items():
52 current_balance = self.balances.all[currency].margin_free
53 moving_to_margin[currency] = (needed - current_balance)
54 delta = moving_to_margin[currency].value
55 if self.debug:
56 self.report.log_debug_action("Moving {} from exchange to margin".format(moving_to_margin[currency]))
57 continue
58 if delta > 0:
59 self.ccxt.transfer_balance(currency, delta, "exchange", "margin")
60 elif delta < 0:
61 self.ccxt.transfer_balance(currency, -delta, "margin", "exchange")
62 self.report.log_move_balances(needed_in_margin, moving_to_margin)
63
64 self.balances.fetch_balances()
65
66 fees_cache = {}
67 def fetch_fees(self):
68 if self.ccxt.__class__ not in self.fees_cache:
69 self.fees_cache[self.ccxt.__class__] = self.ccxt.fetch_fees()
70 return self.fees_cache[self.ccxt.__class__]
71
72 ticker_cache = {}
73 ticker_cache_timestamp = time.time()
74 def get_ticker(self, c1, c2, refresh=False):
75 def invert(ticker):
76 return {
77 "inverted": True,
78 "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2,
79 "original": ticker,
80 }
81 def augment_ticker(ticker):
82 ticker.update({
83 "inverted": False,
84 "average": (ticker["bid"] + ticker["ask"] ) / 2,
85 })
86
87 if time.time() - self.ticker_cache_timestamp > 5:
88 self.ticker_cache = {}
89 self.ticker_cache_timestamp = time.time()
90 elif not refresh:
91 if (c1, c2, self.ccxt.__class__) in self.ticker_cache:
92 return self.ticker_cache[(c1, c2, self.ccxt.__class__)]
93 if (c2, c1, self.ccxt.__class__) in self.ticker_cache:
94 return invert(self.ticker_cache[(c2, c1, self.ccxt.__class__)])
95
96 try:
97 self.ticker_cache[(c1, c2, self.ccxt.__class__)] = self.ccxt.fetch_ticker("{}/{}".format(c1, c2))
98 augment_ticker(self.ticker_cache[(c1, c2, self.ccxt.__class__)])
99 except ExchangeError:
100 try:
101 self.ticker_cache[(c2, c1, self.ccxt.__class__)] = self.ccxt.fetch_ticker("{}/{}".format(c2, c1))
102 augment_ticker(self.ticker_cache[(c2, c1, self.ccxt.__class__)])
103 except ExchangeError:
104 self.ticker_cache[(c1, c2, self.ccxt.__class__)] = None
105 return self.get_ticker(c1, c2)
106
107 def follow_orders(self, sleep=None):
108 if sleep is None:
109 sleep = 7 if self.debug else 30
110 if self.debug:
111 self.report.log_debug_action("Set follow_orders tick to {}s".format(sleep))
112 tick = 0
113 self.report.log_stage("follow_orders_begin")
114 while len(self.trades.all_orders(state="open")) > 0:
115 time.sleep(sleep)
116 tick += 1
117 open_orders = self.trades.all_orders(state="open")
118 self.report.log_stage("follow_orders_tick_{}".format(tick))
119 self.report.log_orders(open_orders, tick=tick)
120 for order in open_orders:
121 if order.get_status() != "open":
122 self.report.log_order(order, tick, finished=True)
123 else:
124 order.trade.update_order(order, tick)
125 self.report.log_stage("follow_orders_end")
126
127 def prepare_trades(self, base_currency="BTC", liquidity="medium", compute_value="average"):
128 self.report.log_stage("prepare_trades")
129 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
130 total_base_value = sum(values_in_base.values())
131 new_repartition = self.balances.dispatch_assets(total_base_value, liquidity=liquidity)
132 # Recompute it in case we have new currencies
133 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
134 self.trades.compute_trades(values_in_base, new_repartition)
135
136 def update_trades(self, base_currency="BTC", liquidity="medium", compute_value="average", only=None):
137 self.report.log_stage("update_trades")
138 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
139 total_base_value = sum(values_in_base.values())
140 new_repartition = self.balances.dispatch_assets(total_base_value, liquidity=liquidity)
141 self.trades.compute_trades(values_in_base, new_repartition, only=only)
142
143 def prepare_trades_to_sell_all(self, base_currency="BTC", compute_value="average"):
144 self.report.log_stage("prepare_trades_to_sell_all")
145 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
146 total_base_value = sum(values_in_base.values())
147 new_repartition = self.balances.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
148 self.trades.compute_trades(values_in_base, new_repartition)
149
150