aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-04 14:49:54 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-03-04 14:49:54 +0100
commit66f1aed55a17d65e0b5993c59688769dcc2b7b58 (patch)
treee04b4899d9181fa72d16fa409271ae9d3a83faeb
parent17598517c544a3dda8b9f773dfeb669c886ea92b (diff)
downloadTrader-66f1aed55a17d65e0b5993c59688769dcc2b7b58.tar.gz
Trader-66f1aed55a17d65e0b5993c59688769dcc2b7b58.tar.zst
Trader-66f1aed55a17d65e0b5993c59688769dcc2b7b58.zip
Write tests for Processor classv0.4
Make default repartition less sensitive to BTC Automatic signature for methods Fixes https://git.immae.eu/mantisbt/view.php?id=49 https://git.immae.eu/mantisbt/view.php?id=39
-rw-r--r--helper.py96
-rw-r--r--test.py230
2 files changed, 206 insertions, 120 deletions
diff --git a/helper.py b/helper.py
index 95b233a..8f726d5 100644
--- a/helper.py
+++ b/helper.py
@@ -202,7 +202,7 @@ class Processor:
202 "before": True, 202 "before": True,
203 "after": False, 203 "after": False,
204 "fetch_balances": ["begin", "end"], 204 "fetch_balances": ["begin", "end"],
205 "prepare_trades": { "repartition": { "BTC": (1, "long") } }, 205 "prepare_trades": { "repartition": { "base_currency": (1, "long") } },
206 "prepare_orders": { "compute_value": "average" }, 206 "prepare_orders": { "compute_value": "average" },
207 "run_orders": {}, 207 "run_orders": {},
208 "follow_orders": {}, 208 "follow_orders": {},
@@ -226,18 +226,15 @@ class Processor:
226 "move_balances": {}, 226 "move_balances": {},
227 "run_orders": {}, 227 "run_orders": {},
228 "follow_orders": {}, 228 "follow_orders": {},
229 "close_trades": {},
229 }, 230 },
230 ] 231 ]
231 } 232 }
232 233
233 allowed_arguments = { 234 ordered_actions = [
234 "wait_for_recent": ["delta"], 235 "wait_for_recent", "prepare_trades", "prepare_orders",
235 "prepare_trades": ["base_currency", "liquidity", "compute_value", "repartition", "only"], 236 "move_balances", "run_orders", "follow_orders",
236 "prepare_orders": ["only", "compute_value"], 237 "close_trades"]
237 "move_balances": [],
238 "run_orders": [],
239 "follow_orders": ["sleep"],
240 }
241 238
242 def __init__(self, market): 239 def __init__(self, market):
243 self.market = market 240 self.market = market
@@ -264,57 +261,60 @@ class Processor:
264 for step in steps: 261 for step in steps:
265 selected_steps += self.select_steps(scenario, step) 262 selected_steps += self.select_steps(scenario, step)
266 for step in selected_steps: 263 for step in selected_steps:
267 self.process_step(scenario_name, step, **kwargs) 264 self.process_step(scenario_name, step, kwargs)
268 265
269 def process_step(self, scenario_name, step, **kwargs): 266 def process_step(self, scenario_name, step, kwargs):
270 process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"]) 267 process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"])
271 self.market.report.log_stage("{}_begin".format(process_name)) 268 self.market.report.log_stage("{}_begin".format(process_name))
272 if "begin" in step.get("fetch_balances", []): 269 if "begin" in step.get("fetch_balances", []):
273 self.market.balances.fetch_balances(tag="{}_begin".format(process_name)) 270 self.market.balances.fetch_balances(tag="{}_begin".format(process_name))
274 271
275 for action in ["wait_for_recent", "prepare_trades", 272 for action in self.ordered_actions:
276 "prepare_orders", "move_balances", "run_orders",
277 "follow_orders"]:
278 if action in step: 273 if action in step:
279 self.run_action(action, step[action], **kwargs) 274 self.run_action(action, step[action], kwargs)
280 275
281 if "end" in step.get("fetch_balances", []): 276 if "end" in step.get("fetch_balances", []):
282 self.market.balances.fetch_balances(tag="{}_end".format(process_name)) 277 self.market.balances.fetch_balances(tag="{}_end".format(process_name))
283 self.market.report.log_stage("{}_end".format(process_name)) 278 self.market.report.log_stage("{}_end".format(process_name))
284 279
285 def run_action(self, action, default_args, **kwargs): 280 def method_arguments(self, action):
286 args = {k: v for k, v in {**default_args, **kwargs}.items() if k in self.allowed_arguments[action] } 281 import inspect
287 282
288 if action == "wait_for_recent": 283 if action == "wait_for_recent":
289 portfolio.Portfolio.wait_for_recent(self.market, **args) 284 method = portfolio.Portfolio.wait_for_recent
290 if action == "prepare_trades": 285 elif action == "prepare_trades":
291 self.market.prepare_trades(**args) 286 method = self.market.prepare_trades
292 if action == "prepare_orders": 287 elif action == "prepare_orders":
293 self.market.trades.prepare_orders(**args) 288 method = self.market.trades.prepare_orders
294 if action == "move_balances": 289 elif action == "move_balances":
295 self.market.move_balances(**args) 290 method = self.market.move_balances
296 if action == "run_orders": 291 elif action == "run_orders":
297 self.market.trades.run_orders(**args) 292 method = self.market.trades.run_orders
298 if action == "follow_orders": 293 elif action == "follow_orders":
299 self.market.follow_orders(**args) 294 method = self.market.follow_orders
300 295 elif action == "close_trades":
301def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC"): 296 method = self.market.trades.close_trades
302 Processor(market).process("sell_needed", steps="sell", 297
303 liquidity=liquidity, base_currency=base_currency) 298 signature = inspect.getfullargspec(method)
304 299 defaults = signature.defaults or []
305def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC"): 300 kwargs = signature.args[-len(defaults):]
306 Processor(market).process("sell_needed", steps="buy", 301
307 liquidity=liquidity, base_currency=base_currency) 302 return [method, kwargs]
308 303
309def process_sell_all__1_all_sell(market, base_currency="BTC", liquidity="medium"): 304 def parse_args(self, action, default_args, kwargs):
310 Processor(market).process("sell_all", steps="all_sell", 305 method, allowed_arguments = self.method_arguments(action)
311 liquidity=liquidity, base_currency=base_currency) 306 args = {k: v for k, v in {**default_args, **kwargs}.items() if k in allowed_arguments }
312 307
313def process_sell_all__2_wait(market, liquidity="medium", base_currency="BTC"): 308 if "repartition" in args and "base_currency" in args["repartition"]:
314 Processor(market).process("sell_all", steps="wait", 309 r = args["repartition"]
315 liquidity=liquidity, base_currency=base_currency) 310 r[args.get("base_currency", "BTC")] = r.pop("base_currency")
316 311
317def process_sell_all__3_all_buy(market, base_currency="BTC", liquidity="medium"): 312 return method, args
318 Processor(market).process("sell_all", steps="all_buy", 313
319 liquidity=liquidity, base_currency=base_currency) 314 def run_action(self, action, default_args, kwargs):
315 method, args = self.parse_args(action, default_args, kwargs)
320 316
317 if action == "wait_for_recent":
318 method(self.market, **args)
319 else:
320 method(**args)
diff --git a/test.py b/test.py
index 78de76e..a894503 100644
--- a/test.py
+++ b/test.py
@@ -3014,87 +3014,173 @@ class HelperTest(WebMockTestCase):
3014 mock.call(portfolio.Amount("BTC", "0.95")), 3014 mock.call(portfolio.Amount("BTC", "0.95")),
3015 ]) 3015 ])
3016 3016
3017 def test_process_sell_needed__1_sell(self): 3017@unittest.skipUnless("unit" in limits, "Unit skipped")
3018 helper.process_sell_needed__1_sell(self.m) 3018class ProcessorTest(WebMockTestCase):
3019 def test_values(self):
3020 processor = helper.Processor(self.m)
3019 3021
3020 self.m.balances.fetch_balances.assert_has_calls([ 3022 self.assertEqual(self.m, processor.market)
3021 mock.call(tag="process_sell_needed__1_sell_begin"),
3022 mock.call(tag="process_sell_needed__1_sell_end"),
3023 ])
3024 self.m.prepare_trades.assert_called_with(base_currency="BTC",
3025 liquidity="medium")
3026 self.m.trades.prepare_orders.assert_called_with(compute_value="average",
3027 only="dispose")
3028 self.m.trades.run_orders.assert_called()
3029 self.m.follow_orders.assert_called()
3030 self.m.report.log_stage.assert_has_calls([
3031 mock.call("process_sell_needed__1_sell_begin"),
3032 mock.call("process_sell_needed__1_sell_end")
3033 ])
3034 3023
3035 def test_process_sell_needed__2_buy(self): 3024 def test_run_action(self):
3036 helper.process_sell_needed__2_buy(self.m) 3025 processor = helper.Processor(self.m)
3037 3026
3038 self.m.balances.fetch_balances.assert_has_calls([ 3027 with mock.patch.object(processor, "parse_args") as parse_args:
3039 mock.call(tag="process_sell_needed__2_buy_begin"), 3028 method_mock = mock.Mock()
3040 mock.call(tag="process_sell_needed__2_buy_end"), 3029 parse_args.return_value = [method_mock, { "foo": "bar" }]
3041 ])
3042 self.m.prepare_trades.assert_called_with(base_currency="BTC",
3043 liquidity="medium", only="acquire")
3044 self.m.trades.prepare_orders.assert_called_with(compute_value="average",
3045 only="acquire")
3046 self.m.move_balances.assert_called_with()
3047 self.m.trades.run_orders.assert_called()
3048 self.m.follow_orders.assert_called()
3049 self.m.report.log_stage.assert_has_calls([
3050 mock.call("process_sell_needed__2_buy_begin"),
3051 mock.call("process_sell_needed__2_buy_end")
3052 ])
3053 3030
3054 def test_process_sell_all__1_sell(self): 3031 processor.run_action("foo", "bar", "baz")
3055 helper.process_sell_all__1_all_sell(self.m)
3056 3032
3057 self.m.balances.fetch_balances.assert_has_calls([ 3033 parse_args.assert_called_with("foo", "bar", "baz")
3058 mock.call(tag="process_sell_all__1_all_sell_begin"),
3059 mock.call(tag="process_sell_all__1_all_sell_end"),
3060 ])
3061 self.m.prepare_trades.assert_called_with(base_currency="BTC",
3062 liquidity="medium", repartition={'BTC': (1, 'long')})
3063 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
3064 self.m.trades.run_orders.assert_called()
3065 self.m.follow_orders.assert_called()
3066 self.m.report.log_stage.assert_has_calls([
3067 mock.call("process_sell_all__1_all_sell_begin"),
3068 mock.call("process_sell_all__1_all_sell_end")
3069 ])
3070 3034
3071 @mock.patch("portfolio.Portfolio.wait_for_recent") 3035 method_mock.assert_called_with(foo="bar")
3072 def test_process_sell_all__2_wait(self, wait):
3073 helper.process_sell_all__2_wait(self.m)
3074 3036
3075 wait.assert_called_once_with(self.m) 3037 processor.run_action("wait_for_recent", "bar", "baz")
3076 self.m.report.log_stage.assert_has_calls([
3077 mock.call("process_sell_all__2_wait_begin"),
3078 mock.call("process_sell_all__2_wait_end")
3079 ])
3080 3038
3081 def test_process_sell_all__3_all_buy(self): 3039 method_mock.assert_called_with(self.m, foo="bar")
3082 helper.process_sell_all__3_all_buy(self.m) 3040
3041 def test_select_step(self):
3042 processor = helper.Processor(self.m)
3043
3044 scenario = processor.scenarios["sell_all"]
3045
3046 self.assertEqual(scenario, processor.select_steps(scenario, "all"))
3047 self.assertEqual(["all_sell"], list(map(lambda x: x["name"], processor.select_steps(scenario, "before"))))
3048 self.assertEqual(["wait", "all_buy"], list(map(lambda x: x["name"], processor.select_steps(scenario, "after"))))
3049 self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, 2))))
3050 self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, "wait"))))
3051
3052 with self.assertRaises(TypeError):
3053 processor.select_steps(scenario, ["wait"])
3054
3055 @mock.patch("helper.Processor.process_step")
3056 def test_process(self, process_step):
3057 processor = helper.Processor(self.m)
3058
3059 processor.process("sell_all", foo="bar")
3060 self.assertEqual(3, process_step.call_count)
3061
3062 steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls))
3063 scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls))
3064 kwargs = list(map(lambda x: x[1][2], process_step.mock_calls))
3065 self.assertEqual(["all_sell", "wait", "all_buy"], steps)
3066 self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names)
3067 self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs)
3068
3069 process_step.reset_mock()
3070
3071 processor.process("sell_needed", steps=["before", "after"])
3072 self.assertEqual(3, process_step.call_count)
3073
3074 def test_method_arguments(self):
3075 ccxt = mock.Mock(spec=market.ccxt.poloniexE)
3076 m = market.Market(ccxt)
3077
3078 processor = helper.Processor(m)
3079
3080 method, arguments = processor.method_arguments("wait_for_recent")
3081 self.assertEqual(portfolio.Portfolio.wait_for_recent, method)
3082 self.assertEqual(["delta"], arguments)
3083
3084 method, arguments = processor.method_arguments("prepare_trades")
3085 self.assertEqual(m.prepare_trades, method)
3086 self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments)
3087
3088 method, arguments = processor.method_arguments("prepare_orders")
3089 self.assertEqual(m.trades.prepare_orders, method)
3090
3091 method, arguments = processor.method_arguments("move_balances")
3092 self.assertEqual(m.move_balances, method)
3093
3094 method, arguments = processor.method_arguments("run_orders")
3095 self.assertEqual(m.trades.run_orders, method)
3096
3097 method, arguments = processor.method_arguments("follow_orders")
3098 self.assertEqual(m.follow_orders, method)
3099
3100 method, arguments = processor.method_arguments("close_trades")
3101 self.assertEqual(m.trades.close_trades, method)
3102
3103 def test_process_step(self):
3104 processor = helper.Processor(self.m)
3105
3106 with mock.patch.object(processor, "run_action") as run_action:
3107 step = processor.scenarios["sell_needed"][1]
3108
3109 processor.process_step("foo", step, {"foo":"bar"})
3110
3111 self.m.report.log_stage.assert_has_calls([
3112 mock.call("process_foo__1_sell_begin"),
3113 mock.call("process_foo__1_sell_end"),
3114 ])
3115 self.m.balances.fetch_balances.assert_has_calls([
3116 mock.call(tag="process_foo__1_sell_begin"),
3117 mock.call(tag="process_foo__1_sell_end"),
3118 ])
3119
3120 self.assertEqual(5, run_action.call_count)
3121
3122 run_action.assert_has_calls([
3123 mock.call('prepare_trades', {}, {'foo': 'bar'}),
3124 mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}),
3125 mock.call('run_orders', {}, {'foo': 'bar'}),
3126 mock.call('follow_orders', {}, {'foo': 'bar'}),
3127 mock.call('close_trades', {}, {'foo': 'bar'}),
3128 ])
3129
3130 self.m.reset_mock()
3131 with mock.patch.object(processor, "run_action") as run_action:
3132 step = processor.scenarios["sell_needed"][0]
3133
3134 processor.process_step("foo", step, {"foo":"bar"})
3135 self.m.balances.fetch_balances.assert_not_called()
3136
3137 def test_parse_args(self):
3138 processor = helper.Processor(self.m)
3139
3140 with mock.patch.object(processor, "method_arguments") as method_arguments:
3141 method_mock = mock.Mock()
3142 method_arguments.return_value = [
3143 method_mock,
3144 ["foo2", "foo"]
3145 ]
3146 method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"})
3147
3148 self.assertEqual(method_mock, method)
3149 self.assertEqual({"foo": "bar2", "foo2": "bar"}, args)
3150
3151 with mock.patch.object(processor, "method_arguments") as method_arguments:
3152 method_mock = mock.Mock()
3153 method_arguments.return_value = [
3154 method_mock,
3155 ["repartition"]
3156 ]
3157 method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {})
3158
3159 self.assertEqual(1, len(args["repartition"]))
3160 self.assertIn("BTC", args["repartition"])
3161
3162 with mock.patch.object(processor, "method_arguments") as method_arguments:
3163 method_mock = mock.Mock()
3164 method_arguments.return_value = [
3165 method_mock,
3166 ["repartition", "base_currency"]
3167 ]
3168 method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {"base_currency": "USDT"})
3169
3170 self.assertEqual(1, len(args["repartition"]))
3171 self.assertIn("USDT", args["repartition"])
3172
3173 with mock.patch.object(processor, "method_arguments") as method_arguments:
3174 method_mock = mock.Mock()
3175 method_arguments.return_value = [
3176 method_mock,
3177 ["repartition", "base_currency"]
3178 ]
3179 method, args = processor.parse_args("action", {"repartition": { "ETH": 1 }}, {"base_currency": "USDT"})
3180
3181 self.assertEqual(1, len(args["repartition"]))
3182 self.assertIn("ETH", args["repartition"])
3083 3183
3084 self.m.balances.fetch_balances.assert_has_calls([
3085 mock.call(tag="process_sell_all__3_all_buy_begin"),
3086 mock.call(tag="process_sell_all__3_all_buy_end"),
3087 ])
3088 self.m.prepare_trades.assert_called_with(base_currency="BTC",
3089 liquidity="medium")
3090 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
3091 self.m.move_balances.assert_called_with()
3092 self.m.trades.run_orders.assert_called()
3093 self.m.follow_orders.assert_called()
3094 self.m.report.log_stage.assert_has_calls([
3095 mock.call("process_sell_all__3_all_buy_begin"),
3096 mock.call("process_sell_all__3_all_buy_end")
3097 ])
3098 3184
3099@unittest.skipUnless("acceptance" in limits, "Acceptance skipped") 3185@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
3100class AcceptanceTest(WebMockTestCase): 3186class AcceptanceTest(WebMockTestCase):