aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-02 13:59:25 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-02 14:08:00 +0100
commit9db7d156833cd384baa64b6148b5c646bfcc41f8 (patch)
treed451957ff1d7f0473db6c15d1baa5a56e973832c
parent7bd830a83b662874c145ea9548edfde79eadc68f (diff)
downloadTrader-9db7d156833cd384baa64b6148b5c646bfcc41f8.tar.gz
Trader-9db7d156833cd384baa64b6148b5c646bfcc41f8.tar.zst
Trader-9db7d156833cd384baa64b6148b5c646bfcc41f8.zip
Add processors
Fixes https://git.immae.eu/mantisbt/view.php?id=45
-rw-r--r--helper.py189
-rw-r--r--market.py22
-rw-r--r--test.py111
3 files changed, 184 insertions, 138 deletions
diff --git a/helper.py b/helper.py
index be480e2..f14fd58 100644
--- a/helper.py
+++ b/helper.py
@@ -127,10 +127,9 @@ def main_fetch_markets(pg_config, user):
127def main_process_market(user_market, actions, before=False, after=False): 127def main_process_market(user_market, actions, before=False, after=False):
128 if len(actions or []) == 0: 128 if len(actions or []) == 0:
129 if before: 129 if before:
130 process_sell_all__1_all_sell(user_market) 130 Processor(user_market).process("sell_all", steps="before")
131 if after: 131 if after:
132 process_sell_all__2_wait(user_market) 132 Processor(user_market).process("sell_all", steps="after")
133 process_sell_all__3_all_buy(user_market)
134 else: 133 else:
135 for action in actions: 134 for action in actions:
136 if action in globals(): 135 if action in globals():
@@ -160,51 +159,159 @@ def print_balances(market, base_currency="BTC"):
160 market.report.print_log("total:") 159 market.report.print_log("total:")
161 market.report.print_log(sum(market.balances.in_currency(base_currency).values())) 160 market.report.print_log(sum(market.balances.in_currency(base_currency).values()))
162 161
162class Processor:
163 scenarios = {
164 "sell_needed": [
165 {
166 "name": "wait",
167 "number": 0,
168 "before": False,
169 "after": True,
170 "wait_for_recent": {},
171 },
172 {
173 "name": "sell",
174 "number": 1,
175 "before": False,
176 "after": True,
177 "fetch_balances": ["begin", "end"],
178 "prepare_trades": {},
179 "prepare_orders": { "only": "dispose", "compute_value": "average" },
180 "run_orders": {},
181 "follow_orders": {},
182 },
183 {
184 "name": "buy",
185 "number": 2,
186 "before": False,
187 "after": True,
188 "fetch_balances": ["begin", "end"],
189 "prepare_trades": { "only": "acquire" },
190 "prepare_orders": { "only": "acquire", "compute_value": "average" },
191 "move_balances": {},
192 "run_orders": {},
193 "follow_orders": {},
194 },
195 ],
196 "sell_all": [
197 {
198 "name": "all_sell",
199 "number": 1,
200 "before": True,
201 "after": False,
202 "fetch_balances": ["begin", "end"],
203 "prepare_trades": { "repartition": { "BTC": (1, "long") } },
204 "prepare_orders": { "compute_value": "average" },
205 "run_orders": {},
206 "follow_orders": {},
207 },
208 {
209 "name": "wait",
210 "number": 2,
211 "before": False,
212 "after": True,
213 "wait_for_recent": {},
214 },
215 {
216 "name": "all_buy",
217 "number": 3,
218 "before": False,
219 "after": True,
220 "fetch_balances": ["begin", "end"],
221 "prepare_trades": {},
222 "prepare_orders": { "compute_value": "average" },
223 "move_balances": {},
224 "run_orders": {},
225 "follow_orders": {},
226 },
227 ]
228 }
229
230 allowed_arguments = {
231 "wait_for_recent": ["delta"],
232 "prepare_trades": ["base_currency", "liquidity", "compute_value", "repartition", "only"],
233 "prepare_orders": ["only", "compute_value"],
234 "move_balances": [],
235 "run_orders": [],
236 "follow_orders": ["sleep"],
237 }
238
239 def __init__(self, market):
240 self.market = market
241
242 def select_steps(self, scenario, step):
243 if step == "all":
244 return scenario
245 elif step == "before" or step == "after":
246 return list(filter(lambda x: step in x and x[step], scenario))
247 elif type(step) == int:
248 return [scenario[step-1]]
249 elif type(step) == str:
250 return list(filter(lambda x: x["name"] == step, scenario))
251 else:
252 raise TypeError("Unknown step {}".format(step))
253
254 def process(self, scenario_name, steps="all", **kwargs):
255 scenario = self.scenarios[scenario_name]
256 selected_steps = []
257
258 if type(steps) == str or type(steps) == int:
259 selected_steps += self.select_steps(scenario, steps)
260 else:
261 for step in steps:
262 selected_steps += self.select_steps(scenario, step)
263 for step in selected_steps:
264 self.process_step(scenario_name, step, **kwargs)
265
266 def process_step(self, scenario_name, step, **kwargs):
267 process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"])
268 self.market.report.log_stage("{}_begin".format(process_name))
269 if "begin" in step.get("fetch_balances", []):
270 self.market.balances.fetch_balances(tag="{}_begin".format(process_name))
271
272 for action in ["wait_for_recent", "prepare_trades",
273 "prepare_orders", "move_balances", "run_orders",
274 "follow_orders"]:
275 if action in step:
276 self.run_action(action, step[action], **kwargs)
277
278 if "end" in step.get("fetch_balances", []):
279 self.market.balances.fetch_balances(tag="{}_end".format(process_name))
280 self.market.report.log_stage("{}_end".format(process_name))
281
282 def run_action(self, action, default_args, **kwargs):
283 args = {k: v for k, v in {**default_args, **kwargs}.items() if k in self.allowed_arguments[action] }
284
285 if action == "wait_for_recent":
286 portfolio.Portfolio.wait_for_recent(self.market, **args)
287 if action == "prepare_trades":
288 self.market.prepare_trades(**args)
289 if action == "prepare_orders":
290 self.market.trades.prepare_orders(**args)
291 if action == "move_balances":
292 self.market.move_balances(**args)
293 if action == "run_orders":
294 self.market.trades.run_orders(**args)
295 if action == "follow_orders":
296 self.market.follow_orders(**args)
297
163def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC"): 298def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC"):
164 market.report.log_stage("process_sell_needed__1_sell_begin") 299 Processor(market).process("sell_needed", steps="sell",
165 market.balances.fetch_balances(tag="process_sell_needed__1_sell_begin") 300 liquidity=liquidity, base_currency=base_currency)
166 market.prepare_trades(liquidity=liquidity, base_currency=base_currency)
167 market.trades.prepare_orders(compute_value="average", only="dispose")
168 market.trades.run_orders()
169 market.follow_orders()
170 market.balances.fetch_balances(tag="process_sell_needed__1_sell_end")
171 market.report.log_stage("process_sell_needed__1_sell_end")
172 301
173def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC"): 302def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC"):
174 market.report.log_stage("process_sell_needed__2_buy_begin") 303 Processor(market).process("sell_needed", steps="buy",
175 market.balances.fetch_balances(tag="process_sell_needed__2_buy_begin") 304 liquidity=liquidity, base_currency=base_currency)
176 market.prepare_trades(base_currency=base_currency, liquidity=liquidity, only="acquire")
177 market.trades.prepare_orders(compute_value="average", only="acquire")
178 market.move_balances()
179 market.trades.run_orders()
180 market.follow_orders()
181 market.balances.fetch_balances(tag="process_sell_needed__2_buy_end")
182 market.report.log_stage("process_sell_needed__2_buy_end")
183 305
184def process_sell_all__1_all_sell(market, base_currency="BTC", liquidity="medium"): 306def process_sell_all__1_all_sell(market, base_currency="BTC", liquidity="medium"):
185 market.report.log_stage("process_sell_all__1_all_sell_begin") 307 Processor(market).process("sell_all", steps="all_sell",
186 market.balances.fetch_balances(tag="process_sell_all__1_all_sell_begin") 308 liquidity=liquidity, base_currency=base_currency)
187 market.prepare_trades_to_sell_all(base_currency=base_currency)
188 market.trades.prepare_orders(compute_value="average")
189 market.trades.run_orders()
190 market.follow_orders()
191 market.balances.fetch_balances(tag="process_sell_all__1_all_sell_end")
192 market.report.log_stage("process_sell_all__1_all_sell_end")
193 309
194def process_sell_all__2_wait(market, liquidity="medium", base_currency="BTC"): 310def process_sell_all__2_wait(market, liquidity="medium", base_currency="BTC"):
195 market.report.log_stage("process_sell_all__2_wait_begin") 311 Processor(market).process("sell_all", steps="wait",
196 portfolio.Portfolio.wait_for_recent(market) 312 liquidity=liquidity, base_currency=base_currency)
197 market.report.log_stage("process_sell_all__2_wait_end")
198 313
199def process_sell_all__3_all_buy(market, base_currency="BTC", liquidity="medium"): 314def process_sell_all__3_all_buy(market, base_currency="BTC", liquidity="medium"):
200 market.report.log_stage("process_sell_all__3_all_buy_begin") 315 Processor(market).process("sell_all", steps="all_buy",
201 market.balances.fetch_balances(tag="process_sell_all__3_all_buy_begin") 316 liquidity=liquidity, base_currency=base_currency)
202 market.prepare_trades(liquidity=liquidity, base_currency=base_currency)
203 market.trades.prepare_orders(compute_value="average")
204 market.move_balances()
205 market.trades.run_orders()
206 market.follow_orders()
207 market.balances.fetch_balances(tag="process_sell_all__3_all_buy_end")
208 market.report.log_stage("process_sell_all__3_all_buy_end")
209
210 317
diff --git a/market.py b/market.py
index ca365bd..3381d1e 100644
--- a/market.py
+++ b/market.py
@@ -128,20 +128,18 @@ class Market:
128 order.trade.update_order(order, tick) 128 order.trade.update_order(order, tick)
129 self.report.log_stage("follow_orders_end") 129 self.report.log_stage("follow_orders_end")
130 130
131 def prepare_trades(self, base_currency="BTC", liquidity="medium", compute_value="average", only=None): 131 def prepare_trades(self, base_currency="BTC", liquidity="medium",
132 compute_value="average", repartition=None, only=None):
133
132 self.report.log_stage("prepare_trades", 134 self.report.log_stage("prepare_trades",
133 base_currency=base_currency, liquidity=liquidity, 135 base_currency=base_currency, liquidity=liquidity,
134 compute_value=compute_value, only=only) 136 compute_value=compute_value, only=only,
135 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value) 137 repartition=repartition)
136 total_base_value = sum(values_in_base.values())
137 new_repartition = self.balances.dispatch_assets(total_base_value, liquidity=liquidity)
138 self.trades.compute_trades(values_in_base, new_repartition, only=only)
139 138
140 def prepare_trades_to_sell_all(self, base_currency="BTC", compute_value="average"): 139 values_in_base = self.balances.in_currency(base_currency,
141 self.report.log_stage("prepare_trades_to_sell_all") 140 compute_value=compute_value)
142 values_in_base = self.balances.in_currency(base_currency, compute_value=compute_value)
143 total_base_value = sum(values_in_base.values()) 141 total_base_value = sum(values_in_base.values())
144 new_repartition = self.balances.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") }) 142 new_repartition = self.balances.dispatch_assets(total_base_value,
145 self.trades.compute_trades(values_in_base, new_repartition) 143 liquidity=liquidity, repartition=repartition)
146 144 self.trades.compute_trades(values_in_base, new_repartition, only=only)
147 145
diff --git a/test.py b/test.py
index 7ec8ba7..7409212 100644
--- a/test.py
+++ b/test.py
@@ -783,51 +783,9 @@ class MarketTest(WebMockTestCase):
783 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 783 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
784 m.report.log_stage.assert_called_once_with("prepare_trades", 784 m.report.log_stage.assert_called_once_with("prepare_trades",
785 base_currency='BTC', compute_value='average', 785 base_currency='BTC', compute_value='average',
786 liquidity='medium', only=None) 786 liquidity='medium', only=None, repartition=None)
787 m.report.log_balances.assert_called_once_with(tag="tag") 787 m.report.log_balances.assert_called_once_with(tag="tag")
788 788
789 @mock.patch.object(portfolio.Portfolio, "repartition")
790 @mock.patch.object(market.Market, "get_ticker")
791 @mock.patch.object(market.TradeStore, "compute_trades")
792 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
793 def _get_ticker(c1, c2):
794 if c1 == "USDT" and c2 == "BTC":
795 return { "average": D("0.0001") }
796 if c1 == "XVG" and c2 == "BTC":
797 return { "average": D("0.000001") }
798 self.fail("Should be called with {}, {}".format(c1, c2))
799 get_ticker.side_effect = _get_ticker
800
801 with mock.patch("market.ReportStore"):
802 m = market.Market(self.ccxt)
803 self.ccxt.fetch_all_balances.return_value = {
804 "USDT": {
805 "exchange_free": D("10000.0"),
806 "exchange_used": D("0.0"),
807 "exchange_total": D("10000.0"),
808 "total": D("10000.0")
809 },
810 "XVG": {
811 "exchange_free": D("10000.0"),
812 "exchange_used": D("0.0"),
813 "exchange_total": D("10000.0"),
814 "total": D("10000.0")
815 },
816 }
817
818 m.balances.fetch_balances(tag="tag")
819
820 m.prepare_trades_to_sell_all()
821
822 repartition.assert_not_called()
823 compute_trades.assert_called()
824
825 call = compute_trades.call_args
826 self.assertEqual(1, call[0][0]["USDT"].value)
827 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
828 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
829 m.report.log_stage.assert_called_once_with("prepare_trades_to_sell_all")
830 m.report.log_balances.assert_called_once_with(tag="tag")
831 789
832 @mock.patch.object(portfolio.time, "sleep") 790 @mock.patch.object(portfolio.time, "sleep")
833 @mock.patch.object(market.TradeStore, "all_orders") 791 @mock.patch.object(market.TradeStore, "all_orders")
@@ -2840,57 +2798,41 @@ class HelperTest(WebMockTestCase):
2840 2798
2841 self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;") 2799 self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
2842 2800
2843 @mock.patch("helper.process_sell_all__1_all_sell") 2801 @mock.patch("helper.Processor.process")
2844 @mock.patch("helper.process_sell_all__2_wait") 2802 def test_main_process_market(self, process):
2845 @mock.patch("helper.process_sell_all__3_all_buy")
2846 def test_main_process_market(self, buy, wait, sell):
2847 with self.subTest(before=False, after=False): 2803 with self.subTest(before=False, after=False):
2848 helper.main_process_market("user", None) 2804 m = mock.Mock()
2849 2805 helper.main_process_market(m, None)
2850 wait.assert_not_called() 2806
2851 buy.assert_not_called() 2807 process.assert_not_called()
2852 sell.assert_not_called()
2853 2808
2854 buy.reset_mock() 2809 process.reset_mock()
2855 wait.reset_mock()
2856 sell.reset_mock()
2857 with self.subTest(before=True, after=False): 2810 with self.subTest(before=True, after=False):
2858 helper.main_process_market("user", None, before=True) 2811 helper.main_process_market(m, None, before=True)
2859
2860 wait.assert_not_called()
2861 buy.assert_not_called()
2862 sell.assert_called_once_with("user")
2863 2812
2864 buy.reset_mock() 2813 process.assert_called_once_with("sell_all", steps="before")
2865 wait.reset_mock() 2814
2866 sell.reset_mock() 2815 process.reset_mock()
2867 with self.subTest(before=False, after=True): 2816 with self.subTest(before=False, after=True):
2868 helper.main_process_market("user", None, after=True) 2817 helper.main_process_market(m, None, after=True)
2869 2818
2870 wait.assert_called_once_with("user") 2819 process.assert_called_once_with("sell_all", steps="after")
2871 buy.assert_called_once_with("user")
2872 sell.assert_not_called()
2873 2820
2874 buy.reset_mock() 2821 process.reset_mock()
2875 wait.reset_mock()
2876 sell.reset_mock()
2877 with self.subTest(before=True, after=True): 2822 with self.subTest(before=True, after=True):
2878 helper.main_process_market("user", None, before=True, after=True) 2823 helper.main_process_market(m, None, before=True, after=True)
2879
2880 wait.assert_called_once_with("user")
2881 buy.assert_called_once_with("user")
2882 sell.assert_called_once_with("user")
2883 2824
2884 buy.reset_mock() 2825 process.assert_has_calls([
2885 wait.reset_mock() 2826 mock.call("sell_all", steps="before"),
2886 sell.reset_mock() 2827 mock.call("sell_all", steps="after"),
2828 ])
2829
2830 process.reset_mock()
2887 with self.subTest(action="print_balances"),\ 2831 with self.subTest(action="print_balances"),\
2888 mock.patch("helper.print_balances") as print_balances: 2832 mock.patch("helper.print_balances") as print_balances:
2889 helper.main_process_market("user", ["print_balances"]) 2833 helper.main_process_market("user", ["print_balances"])
2890 2834
2891 buy.assert_not_called() 2835 process.assert_not_called()
2892 wait.assert_not_called()
2893 sell.assert_not_called()
2894 print_balances.assert_called_once_with("user") 2836 print_balances.assert_called_once_with("user")
2895 2837
2896 with self.subTest(action="print_orders"),\ 2838 with self.subTest(action="print_orders"),\
@@ -2898,9 +2840,7 @@ class HelperTest(WebMockTestCase):
2898 mock.patch("helper.print_balances") as print_balances: 2840 mock.patch("helper.print_balances") as print_balances:
2899 helper.main_process_market("user", ["print_orders", "print_balances"]) 2841 helper.main_process_market("user", ["print_orders", "print_balances"])
2900 2842
2901 buy.assert_not_called() 2843 process.assert_not_called()
2902 wait.assert_not_called()
2903 sell.assert_not_called()
2904 print_orders.assert_called_once_with("user") 2844 print_orders.assert_called_once_with("user")
2905 print_balances.assert_called_once_with("user") 2845 print_balances.assert_called_once_with("user")
2906 2846
@@ -3074,7 +3014,8 @@ class HelperTest(WebMockTestCase):
3074 mock.call(tag="process_sell_all__1_all_sell_begin"), 3014 mock.call(tag="process_sell_all__1_all_sell_begin"),
3075 mock.call(tag="process_sell_all__1_all_sell_end"), 3015 mock.call(tag="process_sell_all__1_all_sell_end"),
3076 ]) 3016 ])
3077 self.m.prepare_trades_to_sell_all.assert_called_with(base_currency="BTC") 3017 self.m.prepare_trades.assert_called_with(base_currency="BTC",
3018 liquidity="medium", repartition={'BTC': (1, 'long')})
3078 self.m.trades.prepare_orders.assert_called_with(compute_value="average") 3019 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
3079 self.m.trades.run_orders.assert_called() 3020 self.m.trades.run_orders.assert_called()
3080 self.m.follow_orders.assert_called() 3021 self.m.follow_orders.assert_called()