]>
Commit | Line | Data |
---|---|---|
1 | from ccxt import ExchangeError, NotSupported | |
2 | import ccxt_wrapper as ccxt | |
3 | import time | |
4 | from store import * | |
5 | from cachetools.func import ttl_cache | |
6 | ||
7 | class Market: | |
8 | debug = False | |
9 | ccxt = None | |
10 | report = None | |
11 | trades = None | |
12 | balances = None | |
13 | ||
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): | |
24 | config["apiKey"] = config.pop("key") | |
25 | ||
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 | ||
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) | |
50 | if trade.trade_type == "short": | |
51 | needed_in_margin[trade.base_currency] -= trade.delta | |
52 | for currency, needed in needed_in_margin.items(): | |
53 | current_balance = self.balances.all[currency].margin_available | |
54 | moving_to_margin[currency] = (needed - current_balance) | |
55 | delta = moving_to_margin[currency].value | |
56 | if self.debug and delta != 0: | |
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 | ||
67 | @ttl_cache(ttl=3600) | |
68 | def fetch_fees(self): | |
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 | |
77 | ||
78 | @ttl_cache(maxsize=20, ttl=5) | |
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 | }) | |
91 | return ticker | |
92 | ||
93 | tickers = self.get_tickers() | |
94 | if tickers is None: | |
95 | try: | |
96 | ticker = augment_ticker(self.ccxt.fetch_ticker("{}/{}".format(c1, c2))) | |
97 | except ExchangeError: | |
98 | try: | |
99 | ticker = invert(augment_ticker(self.ccxt.fetch_ticker("{}/{}".format(c2, c1)))) | |
100 | except ExchangeError: | |
101 | ticker = None | |
102 | else: | |
103 | if "{}/{}".format(c1, c2) in tickers: | |
104 | ticker = augment_ticker(tickers["{}/{}".format(c1, c2)]) | |
105 | elif "{}/{}".format(c2, c1) in tickers: | |
106 | ticker = invert(augment_ticker(tickers["{}/{}".format(c2, c1)])) | |
107 | else: | |
108 | ticker = None | |
109 | return ticker | |
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", | |
132 | compute_value="average", repartition=None, only=None): | |
133 | ||
134 | self.report.log_stage("prepare_trades", | |
135 | base_currency=base_currency, liquidity=liquidity, | |
136 | compute_value=compute_value, only=only, | |
137 | repartition=repartition) | |
138 | ||
139 | values_in_base = self.balances.in_currency(base_currency, | |
140 | compute_value=compute_value) | |
141 | total_base_value = sum(values_in_base.values()) | |
142 | new_repartition = self.balances.dispatch_assets(total_base_value, | |
143 | liquidity=liquidity, repartition=repartition) | |
144 | self.trades.compute_trades(values_in_base, new_repartition, only=only) | |
145 |