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