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