aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-08-04 20:12:33 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-08-04 20:12:33 +0200
commitef8fa5e5454a17c49fe14f6a2c1cffa9cd985bdb (patch)
tree999ec8a712c72887befb186c22fbb646149704e4
parent51bc7cdec15d093272c259e793a9c691775b5194 (diff)
downloadTrader-ef8fa5e5454a17c49fe14f6a2c1cffa9cd985bdb.tar.gz
Trader-ef8fa5e5454a17c49fe14f6a2c1cffa9cd985bdb.tar.zst
Trader-ef8fa5e5454a17c49fe14f6a2c1cffa9cd985bdb.zip
Add liquidity option for cryptoportfolio users
-rw-r--r--main.py29
-rw-r--r--market.py4
-rw-r--r--tests/test_main.py65
-rw-r--r--tests/test_market.py5
4 files changed, 73 insertions, 30 deletions
diff --git a/main.py b/main.py
index ab523be..a7ad8f7 100644
--- a/main.py
+++ b/main.py
@@ -64,19 +64,30 @@ def get_user_market(config_path, user_id, debug=False):
64 args.append("--debug") 64 args.append("--debug")
65 args = parse_args(args) 65 args = parse_args(args)
66 parse_config(args) 66 parse_config(args)
67 market_id, market_config, user_id = list(fetch_markets(str(user_id)))[0] 67 market_id, market_config, user_id, options = list(fetch_markets(str(user_id)))[0]
68 return market.Market.from_config(market_config, args, user_id=user_id) 68 return market.Market.from_config(market_config, args, user_id=user_id, options=options)
69 69
70def fetch_markets(user): 70def fetch_markets(user):
71 cursor = dbs.psql.cursor() 71 cursor = dbs.psql.cursor()
72 72
73 if user is None: 73 if user is None:
74 cursor.execute("SELECT id,config,user_id FROM market_configs WHERE status='enabled'") 74 cursor.execute("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled'")
75 else: 75 else:
76 cursor.execute("SELECT id,config,user_id FROM market_configs WHERE status='enabled' AND user_id = %s", [user]) 76 cursor.execute("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled' AND user_id = %s", [user])
77 77
78 for row in cursor: 78 for row in cursor:
79 yield row 79 options = {
80 "liquidity": parse_liquidity(row[3])
81 }
82 yield row[0:3] + (options,)
83
84def parse_liquidity(value):
85 if value == "high-liquidity":
86 return "high"
87 elif value == "medium-liquidity":
88 return "medium"
89 else:
90 return None
80 91
81def parse_config(args): 92def parse_config(args):
82 if args.db_host is not None: 93 if args.db_host is not None:
@@ -152,11 +163,11 @@ def parse_args(argv):
152 parsed.action = ["sell_all"] 163 parsed.action = ["sell_all"]
153 return parsed 164 return parsed
154 165
155def process(market_config, market_id, user_id, args): 166def process(market_config, market_id, user_id, args, options):
156 try: 167 try:
157 market.Market\ 168 market.Market\
158 .from_config(market_config, args, market_id=market_id, 169 .from_config(market_config, args, market_id=market_id,
159 user_id=user_id)\ 170 user_id=user_id, options=options)\
160 .process(args.action, before=args.before, after=args.after) 171 .process(args.action, before=args.before, after=args.after)
161 except Exception as e: 172 except Exception as e:
162 print("{}: {}".format(e.__class__.__name__, e)) 173 print("{}: {}".format(e.__class__.__name__, e))
@@ -180,8 +191,8 @@ def main(argv):
180 else: 191 else:
181 process_ = process 192 process_ = process
182 193
183 for market_id, market_config, user_id in fetch_markets(args.user): 194 for market_id, market_config, user_id, options in fetch_markets(args.user):
184 process_(market_config, market_id, user_id, args) 195 process_(market_config, market_id, user_id, args, options)
185 196
186 if args.parallel: 197 if args.parallel:
187 for thread in threads: 198 for thread in threads:
diff --git a/market.py b/market.py
index 41c4c9c..82df34f 100644
--- a/market.py
+++ b/market.py
@@ -15,6 +15,7 @@ class Market:
15 report = None 15 report = None
16 trades = None 16 trades = None
17 balances = None 17 balances = None
18 options = None
18 19
19 def __init__(self, ccxt_instance, args, **kwargs): 20 def __init__(self, ccxt_instance, args, **kwargs):
20 self.args = args 21 self.args = args
@@ -26,6 +27,7 @@ class Market:
26 self.balances = BalanceStore(self) 27 self.balances = BalanceStore(self)
27 self.processor = Processor(self) 28 self.processor = Processor(self)
28 29
30 self.options = kwargs.get("options", {})
29 for key in ["user_id", "market_id"]: 31 for key in ["user_id", "market_id"]:
30 setattr(self, key, kwargs.get(key, None)) 32 setattr(self, key, kwargs.get(key, None))
31 33
@@ -466,7 +468,7 @@ class Processor:
466 468
467 def parse_args(self, action, default_args, kwargs): 469 def parse_args(self, action, default_args, kwargs):
468 method, allowed_arguments = self.method_arguments(action) 470 method, allowed_arguments = self.method_arguments(action)
469 args = {k: v for k, v in {**default_args, **kwargs}.items() if k in allowed_arguments } 471 args = {k: v for k, v in {**default_args, **kwargs, **self.market.options}.items() if k in allowed_arguments }
470 472
471 if "repartition" in args and "base_currency" in args["repartition"]: 473 if "repartition" in args and "base_currency" in args["repartition"]:
472 r = args["repartition"] 474 r = args["repartition"]
diff --git a/tests/test_main.py b/tests/test_main.py
index 0b4745f..3735a3b 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -103,23 +103,29 @@ class MainTest(WebMockTestCase):
103 mock.patch("main.parse_config") as main_parse_config: 103 mock.patch("main.parse_config") as main_parse_config:
104 with self.subTest(debug=False): 104 with self.subTest(debug=False):
105 main_parse_args.return_value = self.market_args() 105 main_parse_args.return_value = self.market_args()
106 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] 106 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3, { "foo": "bar" })]
107 m = main.get_user_market("config_path.ini", 1) 107 m = main.get_user_market("config_path.ini", 1)
108 108
109 self.assertIsInstance(m, market.Market) 109 self.assertIsInstance(m, market.Market)
110 self.assertFalse(m.debug) 110 self.assertFalse(m.debug)
111 self.assertEqual("bar", m.options["foo"])
111 main_parse_args.assert_called_once_with(["--config", "config_path.ini"]) 112 main_parse_args.assert_called_once_with(["--config", "config_path.ini"])
112 113
113 main_parse_args.reset_mock() 114 main_parse_args.reset_mock()
114 with self.subTest(debug=True): 115 with self.subTest(debug=True):
115 main_parse_args.return_value = self.market_args(debug=True) 116 main_parse_args.return_value = self.market_args(debug=True)
116 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] 117 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3, { "foo": "bar" })]
117 m = main.get_user_market("config_path.ini", 1, debug=True) 118 m = main.get_user_market("config_path.ini", 1, debug=True)
118 119
119 self.assertIsInstance(m, market.Market) 120 self.assertIsInstance(m, market.Market)
120 self.assertTrue(m.debug) 121 self.assertTrue(m.debug)
121 main_parse_args.assert_called_once_with(["--config", "config_path.ini", "--debug"]) 122 main_parse_args.assert_called_once_with(["--config", "config_path.ini", "--debug"])
122 123
124 def test_parse_liquidity(self):
125 self.assertEqual("high", main.parse_liquidity("high-liquidity"))
126 self.assertEqual("medium", main.parse_liquidity("medium-liquidity"))
127 self.assertIsNone(main.parse_liquidity("foo"))
128
123 def test_process(self): 129 def test_process(self):
124 with mock.patch("market.Market") as market_mock,\ 130 with mock.patch("market.Market") as market_mock,\
125 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 131 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
@@ -133,16 +139,16 @@ class MainTest(WebMockTestCase):
133 args_mock.after = "after" 139 args_mock.after = "after"
134 self.assertEqual("", stdout_mock.getvalue()) 140 self.assertEqual("", stdout_mock.getvalue())
135 141
136 main.process("config", 3, 1, args_mock) 142 main.process("config", 3, 1, args_mock, "options")
137 143
138 market_mock.from_config.assert_has_calls([ 144 market_mock.from_config.assert_has_calls([
139 mock.call("config", args_mock, market_id=3, user_id=1), 145 mock.call("config", args_mock, market_id=3, user_id=1, options="options"),
140 mock.call().process("action", before="before", after="after"), 146 mock.call().process("action", before="before", after="after"),
141 ]) 147 ])
142 148
143 with self.subTest(exception=True): 149 with self.subTest(exception=True):
144 market_mock.from_config.side_effect = Exception("boo") 150 market_mock.from_config.side_effect = Exception("boo")
145 main.process(3, "config", 1, args_mock) 151 main.process(3, "config", 1, args_mock, "options")
146 self.assertEqual("Exception: boo\n", stdout_mock.getvalue()) 152 self.assertEqual("Exception: boo\n", stdout_mock.getvalue())
147 153
148 def test_main(self): 154 def test_main(self):
@@ -157,7 +163,10 @@ class MainTest(WebMockTestCase):
157 args_mock.user = "user" 163 args_mock.user = "user"
158 parse_args.return_value = args_mock 164 parse_args.return_value = args_mock
159 165
160 fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]] 166 fetch_markets.return_value = [
167 [3, "config1", 1, "options"],
168 [1, "config2", 2, "options"]
169 ]
161 170
162 main.main(["Foo", "Bar"]) 171 main.main(["Foo", "Bar"])
163 172
@@ -167,9 +176,10 @@ class MainTest(WebMockTestCase):
167 176
168 self.assertEqual(2, process.call_count) 177 self.assertEqual(2, process.call_count)
169 process.assert_has_calls([ 178 process.assert_has_calls([
170 mock.call("config1", 3, 1, args_mock), 179 mock.call("config1", 3, 1, args_mock, "options"),
171 mock.call("config2", 1, 2, args_mock), 180 mock.call("config2", 1, 2, args_mock, "options"),
172 ]) 181 ])
182
173 with self.subTest(parallel=True): 183 with self.subTest(parallel=True):
174 with mock.patch("main.parse_args") as parse_args,\ 184 with mock.patch("main.parse_args") as parse_args,\
175 mock.patch("main.parse_config") as parse_config,\ 185 mock.patch("main.parse_config") as parse_config,\
@@ -183,7 +193,10 @@ class MainTest(WebMockTestCase):
183 args_mock.user = "user" 193 args_mock.user = "user"
184 parse_args.return_value = args_mock 194 parse_args.return_value = args_mock
185 195
186 fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]] 196 fetch_markets.return_value = [
197 [3, "config1", 1, "options"],
198 [1, "config2", 2, "options"]
199 ]
187 200
188 main.main(["Foo", "Bar"]) 201 main.main(["Foo", "Bar"])
189 202
@@ -196,10 +209,11 @@ class MainTest(WebMockTestCase):
196 self.assertEqual(2, process.call_count) 209 self.assertEqual(2, process.call_count)
197 process.assert_has_calls([ 210 process.assert_has_calls([
198 mock.call.__bool__(), 211 mock.call.__bool__(),
199 mock.call("config1", 3, 1, args_mock), 212 mock.call("config1", 3, 1, args_mock, "options"),
200 mock.call.__bool__(), 213 mock.call.__bool__(),
201 mock.call("config2", 1, 2, args_mock), 214 mock.call("config2", 1, 2, args_mock, "options"),
202 ]) 215 ])
216
203 with self.subTest(quiet=True): 217 with self.subTest(quiet=True):
204 with mock.patch("main.parse_args") as parse_args,\ 218 with mock.patch("main.parse_args") as parse_args,\
205 mock.patch("main.parse_config") as parse_config,\ 219 mock.patch("main.parse_config") as parse_config,\
@@ -213,7 +227,10 @@ class MainTest(WebMockTestCase):
213 args_mock.user = "user" 227 args_mock.user = "user"
214 parse_args.return_value = args_mock 228 parse_args.return_value = args_mock
215 229
216 fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]] 230 fetch_markets.return_value = [
231 [3, "config1", 1, "options"],
232 [1, "config2", 2, "options"]
233 ]
217 234
218 main.main(["Foo", "Bar"]) 235 main.main(["Foo", "Bar"])
219 236
@@ -232,7 +249,10 @@ class MainTest(WebMockTestCase):
232 args_mock.user = "user" 249 args_mock.user = "user"
233 parse_args.return_value = args_mock 250 parse_args.return_value = args_mock
234 251
235 fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]] 252 fetch_markets.return_value = [
253 [3, "config1", 1, "options"],
254 [1, "config2", 2, "options"]
255 ]
236 256
237 main.main(["Foo", "Bar"]) 257 main.main(["Foo", "Bar"])
238 258
@@ -318,23 +338,32 @@ class MainTest(WebMockTestCase):
318 @mock.patch.object(main.dbs, "psql") 338 @mock.patch.object(main.dbs, "psql")
319 def test_fetch_markets(self, psql): 339 def test_fetch_markets(self, psql):
320 cursor_mock = mock.MagicMock() 340 cursor_mock = mock.MagicMock()
321 cursor_mock.__iter__.return_value = ["row_1", "row_2"] 341 cursor_mock.__iter__.return_value = [
342 (1, "cfg", 1, "high-liquidity"),
343 (2, "cfg2", 3, "medium-liquidity")
344 ]
322 345
323 psql.cursor.return_value = cursor_mock 346 psql.cursor.return_value = cursor_mock
324 347
325 with self.subTest(user=None): 348 with self.subTest(user=None):
326 rows = list(main.fetch_markets(None)) 349 rows = list(main.fetch_markets(None))
327 350
328 cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs WHERE status='enabled'") 351 cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled'")
329 352
330 self.assertEqual(["row_1", "row_2"], rows) 353 self.assertEqual([
354 (1, 'cfg', 1, {'liquidity': 'high'}),
355 (2, 'cfg2', 3, {'liquidity': 'medium'})
356 ], rows)
331 357
332 cursor_mock.execute.reset_mock() 358 cursor_mock.execute.reset_mock()
333 with self.subTest(user=1): 359 with self.subTest(user=1):
334 rows = list(main.fetch_markets(1)) 360 rows = list(main.fetch_markets(1))
335 361
336 cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs WHERE status='enabled' AND user_id = %s", [1]) 362 cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled' AND user_id = %s", [1])
337 363
338 self.assertEqual(["row_1", "row_2"], rows) 364 self.assertEqual([
365 (1, 'cfg', 1, {'liquidity': 'high'}),
366 (2, 'cfg2', 3, {'liquidity': 'medium'})
367 ], rows)
339 368
340 369
diff --git a/tests/test_market.py b/tests/test_market.py
index c029686..aeb9f8e 100644
--- a/tests/test_market.py
+++ b/tests/test_market.py
@@ -1126,12 +1126,13 @@ class ProcessorTest(WebMockTestCase):
1126 method_mock = mock.Mock() 1126 method_mock = mock.Mock()
1127 method_arguments.return_value = [ 1127 method_arguments.return_value = [
1128 method_mock, 1128 method_mock,
1129 ["foo2", "foo"] 1129 ["foo2", "foo", "foo3"]
1130 ] 1130 ]
1131 self.m.options = { "foo3": "coucou"}
1131 method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"}) 1132 method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"})
1132 1133
1133 self.assertEqual(method_mock, method) 1134 self.assertEqual(method_mock, method)
1134 self.assertEqual({"foo": "bar2", "foo2": "bar"}, args) 1135 self.assertEqual({"foo": "bar2", "foo2": "bar", "foo3": "coucou"}, args)
1135 1136
1136 with mock.patch.object(processor, "method_arguments") as method_arguments: 1137 with mock.patch.object(processor, "method_arguments") as method_arguments:
1137 method_mock = mock.Mock() 1138 method_mock = mock.Mock()