diff options
Diffstat (limited to 'market.py')
-rw-r--r-- | market.py | 157 |
1 files changed, 145 insertions, 12 deletions
@@ -1,17 +1,150 @@ | |||
1 | from ccxt import ExchangeError | ||
1 | import ccxt_wrapper as ccxt | 2 | import ccxt_wrapper as ccxt |
2 | from store import ReportStore | 3 | import time |
4 | from store import * | ||
3 | 5 | ||
4 | def get_market(config): | 6 | class Market: |
5 | market = ccxt.poloniexE(config) | 7 | debug = False |
8 | ccxt = None | ||
9 | report = None | ||
10 | trades = None | ||
11 | balances = None | ||
6 | 12 | ||
7 | # For requests logging | 13 | def __init__(self, ccxt_instance, debug=False): |
8 | market.session.origin_request = market.session.request | 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) | ||
9 | 149 | ||
10 | def request_wrap(self, *args, **kwargs): | ||
11 | r = self.origin_request(*args, **kwargs) | ||
12 | ReportStore.log_http_request(args[0], args[1], kwargs["data"], | ||
13 | kwargs["headers"], r) | ||
14 | return r | ||
15 | market.session.request = request_wrap.__get__(market.session, market.session.__class__) | ||
16 | 150 | ||
17 | return market | ||