aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helper.py295
-rw-r--r--main.py66
-rw-r--r--market.py157
-rw-r--r--portfolio.py81
-rw-r--r--store.py230
-rw-r--r--test.py1730
6 files changed, 1337 insertions, 1222 deletions
diff --git a/helper.py b/helper.py
index fa92ac7..4b9ce0d 100644
--- a/helper.py
+++ b/helper.py
@@ -1,178 +1,133 @@
1import time 1from datetime import datetime
2from ccxt import ExchangeError 2import argparse
3from store import * 3import configparser
4 4import psycopg2
5def move_balances(market, debug=False): 5import os
6 needed_in_margin = {} 6import sys
7 moving_to_margin = {} 7
8 8import portfolio
9 for currency in BalanceStore.all: 9
10 if BalanceStore.all[currency].margin_free != 0: 10def main_parse_args(argv):
11 needed_in_margin[currency] = 0 11 parser = argparse.ArgumentParser(
12 for trade in TradeStore.all: 12 description="Run the trade bot")
13 if trade.value_to.currency not in needed_in_margin: 13
14 needed_in_margin[trade.value_to.currency] = 0 14 parser.add_argument("-c", "--config",
15 if trade.trade_type == "short": 15 default="config.ini",
16 needed_in_margin[trade.value_to.currency] += abs(trade.value_to) 16 required=False,
17 for currency, needed in needed_in_margin.items(): 17 help="Config file to load (default: config.ini)")
18 current_balance = BalanceStore.all[currency].margin_free 18 parser.add_argument("--before",
19 moving_to_margin[currency] = (needed - current_balance) 19 default=False, action='store_const', const=True,
20 delta = moving_to_margin[currency].value 20 help="Run the steps before the cryptoportfolio update")
21 if debug: 21 parser.add_argument("--after",
22 ReportStore.log_debug_action("Moving {} from exchange to margin".format(moving_to_margin[currency])) 22 default=False, action='store_const', const=True,
23 continue 23 help="Run the steps after the cryptoportfolio update")
24 if delta > 0: 24 parser.add_argument("--debug",
25 market.transfer_balance(currency, delta, "exchange", "margin") 25 default=False, action='store_const', const=True,
26 elif delta < 0: 26 help="Run in debug mode")
27 market.transfer_balance(currency, -delta, "margin", "exchange") 27
28 ReportStore.log_move_balances(needed_in_margin, moving_to_margin, debug) 28 args = parser.parse_args(argv)
29 29
30 BalanceStore.fetch_balances(market) 30 if not os.path.exists(args.config):
31 31 print("no config file found, exiting")
32ticker_cache = {} 32 sys.exit(1)
33ticker_cache_timestamp = time.time() 33
34def get_ticker(c1, c2, market, refresh=False): 34 return args
35 global ticker_cache, ticker_cache_timestamp 35
36 def invert(ticker): 36def main_parse_config(config_file):
37 return { 37 config = configparser.ConfigParser()
38 "inverted": True, 38 config.read(config_file)
39 "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2, 39
40 "original": ticker, 40 if "postgresql" not in config:
41 } 41 print("no configuration for postgresql in config file")
42 def augment_ticker(ticker): 42 sys.exit(1)
43 ticker.update({ 43
44 "inverted": False, 44 if "app" in config and "report_path" in config["app"]:
45 "average": (ticker["bid"] + ticker["ask"] ) / 2, 45 report_path = config["app"]["report_path"]
46 }) 46
47 47 if not os.path.exists(report_path):
48 if time.time() - ticker_cache_timestamp > 5: 48 os.makedirs(report_path)
49 ticker_cache = {} 49 else:
50 ticker_cache_timestamp = time.time() 50 report_path = None
51 elif not refresh: 51
52 if (c1, c2, market.__class__) in ticker_cache: 52 return [config["postgresql"], report_path]
53 return ticker_cache[(c1, c2, market.__class__)] 53
54 if (c2, c1, market.__class__) in ticker_cache: 54def main_fetch_markets(pg_config):
55 return invert(ticker_cache[(c2, c1, market.__class__)]) 55 connection = psycopg2.connect(**pg_config)
56 56 cursor = connection.cursor()
57
58 cursor.execute("SELECT config,user_id FROM market_configs")
59
60 for row in cursor:
61 yield row
62
63def main_process_market(user_market, before=False, after=False):
64 if before:
65 process_sell_all__1_all_sell(user_market)
66 if after:
67 portfolio.Portfolio.wait_for_recent(user_market)
68 process_sell_all__2_all_buy(user_market)
69
70def main_store_report(report_path, user_id, user_market):
57 try: 71 try:
58 ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2)) 72 if report_path is not None:
59 augment_ticker(ticker_cache[(c1, c2, market.__class__)]) 73 report_file = "{}/{}_{}.json".format(report_path, datetime.now().isoformat(), user_id)
60 except ExchangeError: 74 with open(report_file, "w") as f:
61 try: 75 f.write(user_market.report.to_json())
62 ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1)) 76 except Exception as e:
63 augment_ticker(ticker_cache[(c2, c1, market.__class__)]) 77 print("impossible to store report file: {}; {}".format(e.__class__.__name__, e))
64 except ExchangeError:
65 ticker_cache[(c1, c2, market.__class__)] = None
66 return get_ticker(c1, c2, market)
67
68fees_cache = {}
69def fetch_fees(market):
70 global fees_cache
71 if market.__class__ not in fees_cache:
72 fees_cache[market.__class__] = market.fetch_fees()
73 return fees_cache[market.__class__]
74
75def prepare_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", debug=False):
76 ReportStore.log_stage("prepare_trades")
77 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
78 total_base_value = sum(values_in_base.values())
79 new_repartition = BalanceStore.dispatch_assets(total_base_value, liquidity=liquidity)
80 # Recompute it in case we have new currencies
81 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
82 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
83
84def update_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", only=None, debug=False):
85 ReportStore.log_stage("update_trades")
86 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
87 total_base_value = sum(values_in_base.values())
88 new_repartition = BalanceStore.dispatch_assets(total_base_value, liquidity=liquidity)
89 TradeStore.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)
90
91def prepare_trades_to_sell_all(market, base_currency="BTC", compute_value="average", debug=False):
92 ReportStore.log_stage("prepare_trades_to_sell_all")
93 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
94 total_base_value = sum(values_in_base.values())
95 new_repartition = BalanceStore.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
96 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
97
98def follow_orders(sleep=None):
99 if sleep is None:
100 sleep = 7 if TradeStore.debug else 30
101 if TradeStore.debug:
102 ReportStore.log_debug_action("Set follow_orders tick to {}s".format(sleep))
103 tick = 0
104 ReportStore.log_stage("follow_orders_begin")
105 while len(TradeStore.all_orders(state="open")) > 0:
106 time.sleep(sleep)
107 tick += 1
108 open_orders = TradeStore.all_orders(state="open")
109 ReportStore.log_stage("follow_orders_tick_{}".format(tick))
110 ReportStore.log_orders(open_orders, tick=tick)
111 for order in open_orders:
112 if order.get_status() != "open":
113 ReportStore.log_order(order, tick, finished=True)
114 else:
115 order.trade.update_order(order, tick)
116 ReportStore.log_stage("follow_orders_end")
117 78
118def print_orders(market, base_currency="BTC"): 79def print_orders(market, base_currency="BTC"):
119 ReportStore.log_stage("print_orders") 80 market.report.log_stage("print_orders")
120 BalanceStore.fetch_balances(market, tag="print_orders") 81 market.balances.fetch_balances(tag="print_orders")
121 prepare_trades(market, base_currency=base_currency, compute_value="average", debug=True) 82 market.prepare_trades(base_currency=base_currency, compute_value="average")
122 TradeStore.prepare_orders(compute_value="average") 83 market.trades.prepare_orders(compute_value="average")
123 84
124def print_balances(market, base_currency="BTC"): 85def print_balances(market, base_currency="BTC"):
125 BalanceStore.fetch_balances(market) 86 market.balances.fetch_balances()
126 if base_currency is not None: 87 if base_currency is not None:
127 ReportStore.print_log("total:") 88 market.report.print_log("total:")
128 ReportStore.print_log(sum(BalanceStore.in_currency(base_currency, market).values())) 89 market.report.print_log(sum(market.balances.in_currency(base_currency).values()))
129 90
130def reset_all(): 91def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC"):
131 # use them as regular classes, sub-object of market 92 market.report.log_stage("process_sell_needed__1_sell_begin")
132 ReportStore.logs = [] 93 market.balances.fetch_balances(tag="process_sell_needed__1_sell_begin")
133 BalanceStore.all = {} 94 market.prepare_trades(liquidity=liquidity, base_currency=base_currency)
134 TradeStore.all = [] 95 market.trades.prepare_orders(compute_value="average", only="dispose")
135 96 market.trades.run_orders()
136def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC", debug=False): 97 market.follow_orders()
137 ReportStore.log_stage("process_sell_needed__1_sell_begin") 98 market.balances.fetch_balances(tag="process_sell_needed__1_sell_end")
138 BalanceStore.fetch_balances(market, tag="process_sell_needed__1_sell_begin") 99 market.report.log_stage("process_sell_needed__1_sell_end")
139 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug) 100
140 TradeStore.prepare_orders(compute_value="average", only="dispose") 101def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC"):
141 TradeStore.run_orders() 102 market.report.log_stage("process_sell_needed__2_buy_begin")
142 follow_orders() 103 market.balances.fetch_balances(tag="process_sell_needed__2_buy_begin")
143 BalanceStore.fetch_balances(market, tag="process_sell_needed__1_sell_end") 104 market.update_trades(base_currency=base_currency, liquidity=liquidity, only="acquire")
144 ReportStore.log_stage("process_sell_needed__1_sell_end") 105 market.trades.prepare_orders(compute_value="average", only="acquire")
145 106 market.move_balances()
146def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC", debug=False): 107 market.trades.run_orders()
147 ReportStore.log_stage("process_sell_needed__2_buy_begin") 108 market.follow_orders()
148 BalanceStore.fetch_balances(market, tag="process_sell_needed__2_buy_begin") 109 market.balances.fetch_balances(tag="process_sell_needed__2_buy_end")
149 update_trades(market, base_currency=base_currency, liquidity=liquidity, debug=debug, only="acquire") 110 market.report.log_stage("process_sell_needed__2_buy_end")
150 TradeStore.prepare_orders(compute_value="average", only="acquire") 111
151 move_balances(market, debug=debug) 112def process_sell_all__1_all_sell(market, base_currency="BTC", liquidity="medium"):
152 TradeStore.run_orders() 113 market.report.log_stage("process_sell_all__1_all_sell_begin")
153 follow_orders() 114 market.balances.fetch_balances(tag="process_sell_all__1_all_sell_begin")
154 BalanceStore.fetch_balances(market, tag="process_sell_needed__2_buy_end") 115 market.prepare_trades_to_sell_all(base_currency=base_currency)
155 ReportStore.log_stage("process_sell_needed__2_buy_end") 116 market.trades.prepare_orders(compute_value="average")
156 117 market.trades.run_orders()
157def process_sell_all__1_all_sell(market, base_currency="BTC", debug=False, liquidity="medium"): 118 market.follow_orders()
158 ReportStore.log_stage("process_sell_all__1_all_sell_begin") 119 market.balances.fetch_balances(tag="process_sell_all__1_all_sell_end")
159 BalanceStore.fetch_balances(market, tag="process_sell_all__1_all_sell_begin") 120 market.report.log_stage("process_sell_all__1_all_sell_end")
160 prepare_trades_to_sell_all(market, base_currency=base_currency, debug=debug) 121
161 TradeStore.prepare_orders(compute_value="average") 122def process_sell_all__2_all_buy(market, base_currency="BTC", liquidity="medium"):
162 TradeStore.run_orders() 123 market.report.log_stage("process_sell_all__2_all_buy_begin")
163 follow_orders() 124 market.balances.fetch_balances(tag="process_sell_all__2_all_buy_begin")
164 BalanceStore.fetch_balances(market, tag="process_sell_all__1_all_sell_end") 125 market.prepare_trades(liquidity=liquidity, base_currency=base_currency)
165 ReportStore.log_stage("process_sell_all__1_all_sell_end") 126 market.trades.prepare_orders(compute_value="average")
166 127 market.move_balances()
167def process_sell_all__2_all_buy(market, base_currency="BTC", debug=False, liquidity="medium"): 128 market.trades.run_orders()
168 ReportStore.log_stage("process_sell_all__2_all_buy_begin") 129 market.follow_orders()
169 BalanceStore.fetch_balances(market, tag="process_sell_all__2_all_buy_begin") 130 market.balances.fetch_balances(tag="process_sell_all__2_all_buy_end")
170 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug) 131 market.report.log_stage("process_sell_all__2_all_buy_end")
171 TradeStore.prepare_orders(compute_value="average")
172 move_balances(market, debug=debug)
173 TradeStore.run_orders()
174 follow_orders()
175 BalanceStore.fetch_balances(market, tag="process_sell_all__2_all_buy_end")
176 ReportStore.log_stage("process_sell_all__2_all_buy_end")
177 132
178 133
diff --git a/main.py b/main.py
index 41abe9e..e7cdcf0 100644
--- a/main.py
+++ b/main.py
@@ -1,64 +1,16 @@
1import os
2import sys 1import sys
3import configparser 2import helper, market
4import psycopg2
5import argparse
6from datetime import datetime
7 3
8import portfolio, market 4args = helper.main_parse_args(sys.argv[1:])
9 5
10parser = argparse.ArgumentParser( 6pg_config, report_path = helper.main_parse_config(args.config)
11 description="Run the trade bot")
12 7
13parser.add_argument("-c", "--config", 8for market_config, user_id in helper.main_fetch_markets(pg_config):
14 default="config.ini",
15 required=False,
16 help="Config file to load (default: config.ini)")
17parser.add_argument("--before",
18 default=False, action='store_const', const=True,
19 help="Run the steps before the cryptoportfolio update")
20parser.add_argument("--after",
21 default=False, action='store_const', const=True,
22 help="Run the steps after the cryptoportfolio update")
23parser.add_argument("--debug",
24 default=False, action='store_const', const=True,
25 help="Run in debug mode")
26
27args = parser.parse_args()
28
29if not os.path.exists(args.config):
30 print("no config file found, exiting")
31 sys.exit(1)
32
33config = configparser.ConfigParser()
34config.read(args.config)
35
36pg_config = config["postgresql"]
37
38connection = psycopg2.connect(**pg_config)
39cursor = connection.cursor()
40
41cursor.execute("SELECT config,user_id FROM market_configs")
42
43report_path = config["app"]["report_path"]
44if not os.path.exists(report_path):
45 os.makedirs(report_path)
46
47for row in cursor:
48 market_config, user_id = row
49 try: 9 try:
50 user_market = market.get_market(market_config) 10 market_config["apiKey"] = market_config.pop("key")
51 if args.before: 11 user_market = market.Market.from_config(market_config, debug=args.debug)
52 portfolio.h.process_sell_all__1_all_sell(user_market, debug=args.debug) 12 helper.main_process_market(user_market, before=args.before, after=args.after)
53 if args.after:
54 portfolio.Portfolio.wait_for_recent()
55 portfolio.h.process_sell_all__2_all_buy(user_market, debug=args.debug)
56 except Exception as e: 13 except Exception as e:
57 print(e) 14 print("{}: {}".format(e.__class__.__name__, e))
58 pass
59 finally: 15 finally:
60 report_file = "{}/{}_{}.json".format(report_path, datetime.now().isoformat(), user_id) 16 helper.main_store_report(report_path, user_id, user_market)
61 with open(report_file, "w") as f:
62 f.write(portfolio.ReportStore.to_json())
63 portfolio.h.reset_all()
64
diff --git a/market.py b/market.py
index 224cc32..931de09 100644
--- a/market.py
+++ b/market.py
@@ -1,17 +1,150 @@
1from ccxt import ExchangeError
1import ccxt_wrapper as ccxt 2import ccxt_wrapper as ccxt
2from store import ReportStore 3import time
4from store import *
3 5
4def get_market(config): 6class 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
diff --git a/portfolio.py b/portfolio.py
index f9423b9..43a39c4 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -1,13 +1,10 @@
1import time 1import time
2from datetime import datetime, timedelta 2from datetime import datetime, timedelta
3from decimal import Decimal as D, ROUND_DOWN 3from decimal import Decimal as D, ROUND_DOWN
4# Put your poloniex api key in market.py
5from json import JSONDecodeError 4from json import JSONDecodeError
6from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError 5from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError
7from ccxt import ExchangeError, ExchangeNotAvailable 6from ccxt import ExchangeError, ExchangeNotAvailable
8import requests 7import requests
9import helper as h
10from store import *
11 8
12# FIXME: correctly handle web call timeouts 9# FIXME: correctly handle web call timeouts
13 10
@@ -18,26 +15,27 @@ class Portfolio:
18 last_date = None 15 last_date = None
19 16
20 @classmethod 17 @classmethod
21 def wait_for_recent(cls, delta=4): 18 def wait_for_recent(cls, market, delta=4):
22 cls.repartition(refetch=True) 19 cls.repartition(market, refetch=True)
23 while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): 20 while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta):
24 time.sleep(30) 21 time.sleep(30)
25 cls.repartition(refetch=True) 22 market.report.print_log("Attempt to fetch up-to-date cryptoportfolio")
23 cls.repartition(market, refetch=True)
26 24
27 @classmethod 25 @classmethod
28 def repartition(cls, liquidity="medium", refetch=False): 26 def repartition(cls, market, liquidity="medium", refetch=False):
29 cls.parse_cryptoportfolio(refetch=refetch) 27 cls.parse_cryptoportfolio(market, refetch=refetch)
30 liquidities = cls.liquidities[liquidity] 28 liquidities = cls.liquidities[liquidity]
31 return liquidities[cls.last_date] 29 return liquidities[cls.last_date]
32 30
33 @classmethod 31 @classmethod
34 def get_cryptoportfolio(cls): 32 def get_cryptoportfolio(cls, market):
35 try: 33 try:
36 r = requests.get(cls.URL) 34 r = requests.get(cls.URL)
37 ReportStore.log_http_request(r.request.method, 35 market.report.log_http_request(r.request.method,
38 r.request.url, r.request.body, r.request.headers, r) 36 r.request.url, r.request.body, r.request.headers, r)
39 except Exception as e: 37 except Exception as e:
40 ReportStore.log_error("get_cryptoportfolio", exception=e) 38 market.report.log_error("get_cryptoportfolio", exception=e)
41 return 39 return
42 try: 40 try:
43 cls.data = r.json(parse_int=D, parse_float=D) 41 cls.data = r.json(parse_int=D, parse_float=D)
@@ -45,9 +43,9 @@ class Portfolio:
45 cls.data = None 43 cls.data = None
46 44
47 @classmethod 45 @classmethod
48 def parse_cryptoportfolio(cls, refetch=False): 46 def parse_cryptoportfolio(cls, market, refetch=False):
49 if refetch or cls.data is None: 47 if refetch or cls.data is None:
50 cls.get_cryptoportfolio() 48 cls.get_cryptoportfolio(market)
51 49
52 def filter_weights(weight_hash): 50 def filter_weights(weight_hash):
53 if weight_hash[1][0] == 0: 51 if weight_hash[1][0] == 0:
@@ -118,7 +116,7 @@ class Amount:
118 self.value * rate, 116 self.value * rate,
119 linked_to=self, 117 linked_to=self,
120 rate=rate) 118 rate=rate)
121 asset_ticker = h.get_ticker(self.currency, other_currency, market) 119 asset_ticker = market.get_ticker(self.currency, other_currency)
122 if asset_ticker is not None: 120 if asset_ticker is not None:
123 rate = Computation.compute_value(asset_ticker, action, compute_value=compute_value) 121 rate = Computation.compute_value(asset_ticker, action, compute_value=compute_value)
124 return Amount( 122 return Amount(
@@ -283,7 +281,7 @@ class Balance:
283 return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")" 281 return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")"
284 282
285class Trade: 283class Trade:
286 def __init__(self, value_from, value_to, currency, market=None): 284 def __init__(self, value_from, value_to, currency, market):
287 # We have value_from of currency, and want to finish with value_to of 285 # We have value_from of currency, and want to finish with value_to of
288 # that currency. value_* may not be in currency's terms 286 # that currency. value_* may not be in currency's terms
289 self.currency = currency 287 self.currency = currency
@@ -353,18 +351,18 @@ class Trade:
353 if tick == 7: 351 if tick == 7:
354 update = "market_fallback" 352 update = "market_fallback"
355 353
356 ReportStore.log_order(order, tick, update=update, 354 self.market.report.log_order(order, tick, update=update,
357 compute_value=compute_value, new_order=new_order) 355 compute_value=compute_value, new_order=new_order)
358 356
359 if new_order is not None: 357 if new_order is not None:
360 order.cancel() 358 order.cancel()
361 new_order.run() 359 new_order.run()
362 ReportStore.log_order(order, tick, new_order=new_order) 360 self.market.report.log_order(order, tick, new_order=new_order)
363 361
364 def prepare_order(self, compute_value="default"): 362 def prepare_order(self, compute_value="default"):
365 if self.action is None: 363 if self.action is None:
366 return None 364 return None
367 ticker = h.get_ticker(self.currency, self.base_currency, self.market) 365 ticker = self.market.get_ticker(self.currency, self.base_currency)
368 inverted = ticker["inverted"] 366 inverted = ticker["inverted"]
369 if inverted: 367 if inverted:
370 ticker = ticker["original"] 368 ticker = ticker["original"]
@@ -430,7 +428,7 @@ class Trade:
430 close_if_possible = (self.value_to == 0) 428 close_if_possible = (self.value_to == 0)
431 429
432 if delta <= 0: 430 if delta <= 0:
433 ReportStore.log_error("prepare_order", message="Less to do than already filled: {}".format(delta)) 431 self.market.report.log_error("prepare_order", message="Less to do than already filled: {}".format(delta))
434 return None 432 return None
435 433
436 order = Order(self.order_action(inverted), 434 order = Order(self.order_action(inverted),
@@ -456,11 +454,11 @@ class Trade:
456 self.action) 454 self.action)
457 455
458 def print_with_order(self, ind=""): 456 def print_with_order(self, ind=""):
459 ReportStore.print_log("{}{}".format(ind, self)) 457 self.market.report.print_log("{}{}".format(ind, self))
460 for order in self.orders: 458 for order in self.orders:
461 ReportStore.print_log("{}\t{}".format(ind, order)) 459 self.market.report.print_log("{}\t{}".format(ind, order))
462 for mouvement in order.mouvements: 460 for mouvement in order.mouvements:
463 ReportStore.print_log("{}\t\t{}".format(ind, mouvement)) 461 self.market.report.print_log("{}\t\t{}".format(ind, mouvement))
464 462
465class Order: 463class Order:
466 def __init__(self, action, amount, rate, base_currency, trade_type, market, 464 def __init__(self, action, amount, rate, base_currency, trade_type, market,
@@ -525,15 +523,15 @@ class Order:
525 523
526 def run(self): 524 def run(self):
527 symbol = "{}/{}".format(self.amount.currency, self.base_currency) 525 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
528 amount = round(self.amount, self.market.order_precision(symbol)).value 526 amount = round(self.amount, self.market.ccxt.order_precision(symbol)).value
529 527
530 if TradeStore.debug: 528 if self.market.debug:
531 ReportStore.log_debug_action("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 529 self.market.report.log_debug_action("market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
532 symbol, self.action, amount, self.rate, self.account)) 530 symbol, self.action, amount, self.rate, self.account))
533 self.results.append({"debug": True, "id": -1}) 531 self.results.append({"debug": True, "id": -1})
534 else: 532 else:
535 try: 533 try:
536 self.results.append(self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account)) 534 self.results.append(self.market.ccxt.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account))
537 except ExchangeNotAvailable: 535 except ExchangeNotAvailable:
538 # Impossible to honor the order (dust amount) 536 # Impossible to honor the order (dust amount)
539 self.status = "closed" 537 self.status = "closed"
@@ -541,15 +539,15 @@ class Order:
541 return 539 return
542 except Exception as e: 540 except Exception as e:
543 self.status = "error" 541 self.status = "error"
544 action = "market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account) 542 action = "market.ccxt.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account)
545 ReportStore.log_error(action, exception=e) 543 self.market.report.log_error(action, exception=e)
546 return 544 return
547 self.id = self.results[0]["id"] 545 self.id = self.results[0]["id"]
548 self.status = "open" 546 self.status = "open"
549 547
550 def get_status(self): 548 def get_status(self):
551 if TradeStore.debug: 549 if self.market.debug:
552 ReportStore.log_debug_action("Getting {} status".format(self)) 550 self.market.report.log_debug_action("Getting {} status".format(self))
553 return self.status 551 return self.status
554 # other states are "closed" and "canceled" 552 # other states are "closed" and "canceled"
555 if not self.finished: 553 if not self.finished:
@@ -559,23 +557,23 @@ class Order:
559 return self.status 557 return self.status
560 558
561 def mark_finished_order(self): 559 def mark_finished_order(self):
562 if TradeStore.debug: 560 if self.market.debug:
563 ReportStore.log_debug_action("Mark {} as finished".format(self)) 561 self.market.report.log_debug_action("Mark {} as finished".format(self))
564 return 562 return
565 if self.status == "closed": 563 if self.status == "closed":
566 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: 564 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible:
567 self.market.close_margin_position(self.amount.currency, self.base_currency) 565 self.market.ccxt.close_margin_position(self.amount.currency, self.base_currency)
568 566
569 def fetch(self, force=False): 567 def fetch(self, force=False):
570 if TradeStore.debug: 568 if self.market.debug:
571 ReportStore.log_debug_action("Fetching {}".format(self)) 569 self.market.report.log_debug_action("Fetching {}".format(self))
572 return 570 return
573 if (not force and self.fetch_cache_timestamp is not None 571 if (not force and self.fetch_cache_timestamp is not None
574 and time.time() - self.fetch_cache_timestamp < 10): 572 and time.time() - self.fetch_cache_timestamp < 10):
575 return 573 return
576 self.fetch_cache_timestamp = time.time() 574 self.fetch_cache_timestamp = time.time()
577 575
578 result = self.market.fetch_order(self.id) 576 result = self.market.ccxt.fetch_order(self.id)
579 self.results.append(result) 577 self.results.append(result)
580 578
581 self.status = result["status"] 579 self.status = result["status"]
@@ -606,7 +604,7 @@ class Order:
606 604
607 def fetch_mouvements(self): 605 def fetch_mouvements(self):
608 try: 606 try:
609 mouvements = self.market.privatePostReturnOrderTrades({"orderNumber": self.id}) 607 mouvements = self.market.ccxt.privatePostReturnOrderTrades({"orderNumber": self.id})
610 except ExchangeError: 608 except ExchangeError:
611 mouvements = [] 609 mouvements = []
612 self.mouvements = [] 610 self.mouvements = []
@@ -616,11 +614,11 @@ class Order:
616 self.base_currency, mouvement_hash)) 614 self.base_currency, mouvement_hash))
617 615
618 def cancel(self): 616 def cancel(self):
619 if TradeStore.debug: 617 if self.market.debug:
620 ReportStore.log_debug_action("Mark {} as cancelled".format(self)) 618 self.market.report.log_debug_action("Mark {} as cancelled".format(self))
621 self.status = "canceled" 619 self.status = "canceled"
622 return 620 return
623 self.market.cancel_order(self.id) 621 self.market.ccxt.cancel_order(self.id)
624 self.fetch() 622 self.fetch()
625 623
626class Mouvement: 624class Mouvement:
@@ -663,6 +661,3 @@ class Mouvement:
663 date, self.action, self.total, self.total_in_base, 661 date, self.action, self.total, self.total_in_base,
664 fee_rate) 662 fee_rate)
665 663
666if __name__ == '__main__': # pragma: no cover
667 from market import market
668 h.print_orders(market)
diff --git a/store.py b/store.py
index a7aad22..c6cddab 100644
--- a/store.py
+++ b/store.py
@@ -6,63 +6,59 @@ from datetime import date, datetime
6__all__ = ["BalanceStore", "ReportStore", "TradeStore"] 6__all__ = ["BalanceStore", "ReportStore", "TradeStore"]
7 7
8class ReportStore: 8class ReportStore:
9 logs = [] 9 def __init__(self, market, verbose_print=True):
10 verbose_print = True 10 self.market = market
11 self.verbose_print = verbose_print
11 12
12 @classmethod 13 self.logs = []
13 def print_log(cls, message): 14
15 def print_log(self, message):
14 message = str(message) 16 message = str(message)
15 if cls.verbose_print: 17 if self.verbose_print:
16 print(message) 18 print(message)
17 19
18 @classmethod 20 def add_log(self, hash_):
19 def add_log(cls, hash_):
20 hash_["date"] = datetime.now() 21 hash_["date"] = datetime.now()
21 cls.logs.append(hash_) 22 self.logs.append(hash_)
22 23
23 @classmethod 24 def to_json(self):
24 def to_json(cls):
25 def default_json_serial(obj): 25 def default_json_serial(obj):
26 if isinstance(obj, (datetime, date)): 26 if isinstance(obj, (datetime, date)):
27 return obj.isoformat() 27 return obj.isoformat()
28 raise TypeError ("Type %s not serializable" % type(obj)) 28 raise TypeError ("Type %s not serializable" % type(obj))
29 return json.dumps(cls.logs, default=default_json_serial) 29 return json.dumps(self.logs, default=default_json_serial)
30 30
31 @classmethod 31 def set_verbose(self, verbose_print):
32 def set_verbose(cls, verbose_print): 32 self.verbose_print = verbose_print
33 cls.verbose_print = verbose_print
34 33
35 @classmethod 34 def log_stage(self, stage):
36 def log_stage(cls, stage): 35 self.print_log("-" * (len(stage) + 8))
37 cls.print_log("-" * (len(stage) + 8)) 36 self.print_log("[Stage] {}".format(stage))
38 cls.print_log("[Stage] {}".format(stage))
39 37
40 cls.add_log({ 38 self.add_log({
41 "type": "stage", 39 "type": "stage",
42 "stage": stage, 40 "stage": stage,
43 }) 41 })
44 42
45 @classmethod 43 def log_balances(self, tag=None):
46 def log_balances(cls, market, tag=None): 44 self.print_log("[Balance]")
47 cls.print_log("[Balance]") 45 for currency, balance in self.market.balances.all.items():
48 for currency, balance in BalanceStore.all.items(): 46 self.print_log("\t{}".format(balance))
49 cls.print_log("\t{}".format(balance))
50 47
51 cls.add_log({ 48 self.add_log({
52 "type": "balance", 49 "type": "balance",
53 "tag": tag, 50 "tag": tag,
54 "balances": BalanceStore.as_json() 51 "balances": self.market.balances.as_json()
55 }) 52 })
56 53
57 @classmethod 54 def log_tickers(self, amounts, other_currency,
58 def log_tickers(cls, market, amounts, other_currency,
59 compute_value, type): 55 compute_value, type):
60 values = {} 56 values = {}
61 rates = {} 57 rates = {}
62 for currency, amount in amounts.items(): 58 for currency, amount in amounts.items():
63 values[currency] = amount.as_json()["value"] 59 values[currency] = amount.as_json()["value"]
64 rates[currency] = amount.rate 60 rates[currency] = amount.rate
65 cls.add_log({ 61 self.add_log({
66 "type": "tickers", 62 "type": "tickers",
67 "compute_value": compute_value, 63 "compute_value": compute_value,
68 "balance_type": type, 64 "balance_type": type,
@@ -72,9 +68,8 @@ class ReportStore:
72 "total": sum(amounts.values()).as_json()["value"] 68 "total": sum(amounts.values()).as_json()["value"]
73 }) 69 })
74 70
75 @classmethod 71 def log_dispatch(self, amount, amounts, liquidity, repartition):
76 def log_dispatch(cls, amount, amounts, liquidity, repartition): 72 self.add_log({
77 cls.add_log({
78 "type": "dispatch", 73 "type": "dispatch",
79 "liquidity": liquidity, 74 "liquidity": liquidity,
80 "repartition_ratio": repartition, 75 "repartition_ratio": repartition,
@@ -82,26 +77,24 @@ class ReportStore:
82 "repartition": { k: v.as_json()["value"] for k, v in amounts.items() } 77 "repartition": { k: v.as_json()["value"] for k, v in amounts.items() }
83 }) 78 })
84 79
85 @classmethod 80 def log_trades(self, matching_and_trades, only):
86 def log_trades(cls, matching_and_trades, only, debug):
87 trades = [] 81 trades = []
88 for matching, trade in matching_and_trades: 82 for matching, trade in matching_and_trades:
89 trade_json = trade.as_json() 83 trade_json = trade.as_json()
90 trade_json["skipped"] = not matching 84 trade_json["skipped"] = not matching
91 trades.append(trade_json) 85 trades.append(trade_json)
92 86
93 cls.add_log({ 87 self.add_log({
94 "type": "trades", 88 "type": "trades",
95 "only": only, 89 "only": only,
96 "debug": debug, 90 "debug": self.market.debug,
97 "trades": trades 91 "trades": trades
98 }) 92 })
99 93
100 @classmethod 94 def log_orders(self, orders, tick=None, only=None, compute_value=None):
101 def log_orders(cls, orders, tick=None, only=None, compute_value=None): 95 self.print_log("[Orders]")
102 cls.print_log("[Orders]") 96 self.market.trades.print_all_with_order(ind="\t")
103 TradeStore.print_all_with_order(ind="\t") 97 self.add_log({
104 cls.add_log({
105 "type": "orders", 98 "type": "orders",
106 "only": only, 99 "only": only,
107 "compute_value": compute_value, 100 "compute_value": compute_value,
@@ -109,21 +102,20 @@ class ReportStore:
109 "orders": [order.as_json() for order in orders if order is not None] 102 "orders": [order.as_json() for order in orders if order is not None]
110 }) 103 })
111 104
112 @classmethod 105 def log_order(self, order, tick, finished=False, update=None,
113 def log_order(cls, order, tick, finished=False, update=None,
114 new_order=None, compute_value=None): 106 new_order=None, compute_value=None):
115 if finished: 107 if finished:
116 cls.print_log("[Order] Finished {}".format(order)) 108 self.print_log("[Order] Finished {}".format(order))
117 elif update == "waiting": 109 elif update == "waiting":
118 cls.print_log("[Order] {}, tick {}, waiting".format(order, tick)) 110 self.print_log("[Order] {}, tick {}, waiting".format(order, tick))
119 elif update == "adjusting": 111 elif update == "adjusting":
120 cls.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) 112 self.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
121 elif update == "market_fallback": 113 elif update == "market_fallback":
122 cls.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick)) 114 self.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick))
123 elif update == "market_adjust": 115 elif update == "market_adjust":
124 cls.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) 116 self.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))
125 117
126 cls.add_log({ 118 self.add_log({
127 "type": "order", 119 "type": "order",
128 "tick": tick, 120 "tick": tick,
129 "update": update, 121 "update": update,
@@ -132,18 +124,16 @@ class ReportStore:
132 "new_order": new_order.as_json() if new_order is not None else None 124 "new_order": new_order.as_json() if new_order is not None else None
133 }) 125 })
134 126
135 @classmethod 127 def log_move_balances(self, needed, moving):
136 def log_move_balances(cls, needed, moving, debug): 128 self.add_log({
137 cls.add_log({
138 "type": "move_balances", 129 "type": "move_balances",
139 "debug": debug, 130 "debug": self.market.debug,
140 "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() }, 131 "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() },
141 "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() }, 132 "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() },
142 }) 133 })
143 134
144 @classmethod 135 def log_http_request(self, method, url, body, headers, response):
145 def log_http_request(cls, method, url, body, headers, response): 136 self.add_log({
146 cls.add_log({
147 "type": "http_request", 137 "type": "http_request",
148 "method": method, 138 "method": method,
149 "url": url, 139 "url": url,
@@ -153,15 +143,14 @@ class ReportStore:
153 "response": response.text 143 "response": response.text
154 }) 144 })
155 145
156 @classmethod 146 def log_error(self, action, message=None, exception=None):
157 def log_error(cls, action, message=None, exception=None): 147 self.print_log("[Error] {}".format(action))
158 cls.print_log("[Error] {}".format(action))
159 if exception is not None: 148 if exception is not None:
160 cls.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception))) 149 self.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception)))
161 if message is not None: 150 if message is not None:
162 cls.print_log("\t{}".format(message)) 151 self.print_log("\t{}".format(message))
163 152
164 cls.add_log({ 153 self.add_log({
165 "type": "error", 154 "type": "error",
166 "action": action, 155 "action": action,
167 "exception_class": exception.__class__.__name__ if exception is not None else None, 156 "exception_class": exception.__class__.__name__ if exception is not None else None,
@@ -169,132 +158,121 @@ class ReportStore:
169 "message": message, 158 "message": message,
170 }) 159 })
171 160
172 @classmethod 161 def log_debug_action(self, action):
173 def log_debug_action(cls, action): 162 self.print_log("[Debug] {}".format(action))
174 cls.print_log("[Debug] {}".format(action))
175 163
176 cls.add_log({ 164 self.add_log({
177 "type": "debug_action", 165 "type": "debug_action",
178 "action": action, 166 "action": action,
179 }) 167 })
180 168
181class BalanceStore: 169class BalanceStore:
182 all = {} 170 def __init__(self, market):
171 self.market = market
172 self.all = {}
183 173
184 @classmethod 174 def currencies(self):
185 def currencies(cls): 175 return self.all.keys()
186 return cls.all.keys()
187 176
188 @classmethod 177 def in_currency(self, other_currency, compute_value="average", type="total"):
189 def in_currency(cls, other_currency, market, compute_value="average", type="total"):
190 amounts = {} 178 amounts = {}
191 for currency, balance in cls.all.items(): 179 for currency, balance in self.all.items():
192 other_currency_amount = getattr(balance, type)\ 180 other_currency_amount = getattr(balance, type)\
193 .in_currency(other_currency, market, compute_value=compute_value) 181 .in_currency(other_currency, self.market, compute_value=compute_value)
194 amounts[currency] = other_currency_amount 182 amounts[currency] = other_currency_amount
195 ReportStore.log_tickers(market, amounts, other_currency, 183 self.market.report.log_tickers(amounts, other_currency,
196 compute_value, type) 184 compute_value, type)
197 return amounts 185 return amounts
198 186
199 @classmethod 187 def fetch_balances(self, tag=None):
200 def fetch_balances(cls, market, tag=None): 188 all_balances = self.market.ccxt.fetch_all_balances()
201 all_balances = market.fetch_all_balances()
202 for currency, balance in all_balances.items(): 189 for currency, balance in all_balances.items():
203 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ 190 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
204 currency in cls.all: 191 currency in self.all:
205 cls.all[currency] = portfolio.Balance(currency, balance) 192 self.all[currency] = portfolio.Balance(currency, balance)
206 ReportStore.log_balances(market, tag=tag) 193 self.market.report.log_balances(tag=tag)
207 194
208 @classmethod 195 def dispatch_assets(self, amount, liquidity="medium", repartition=None):
209 def dispatch_assets(cls, amount, liquidity="medium", repartition=None):
210 if repartition is None: 196 if repartition is None:
211 repartition = portfolio.Portfolio.repartition(liquidity=liquidity) 197 repartition = portfolio.Portfolio.repartition(self.market, liquidity=liquidity)
212 sum_ratio = sum([v[0] for k, v in repartition.items()]) 198 sum_ratio = sum([v[0] for k, v in repartition.items()])
213 amounts = {} 199 amounts = {}
214 for currency, (ptt, trade_type) in repartition.items(): 200 for currency, (ptt, trade_type) in repartition.items():
215 amounts[currency] = ptt * amount / sum_ratio 201 amounts[currency] = ptt * amount / sum_ratio
216 if trade_type == "short": 202 if trade_type == "short":
217 amounts[currency] = - amounts[currency] 203 amounts[currency] = - amounts[currency]
218 if currency not in BalanceStore.all: 204 if currency not in self.all:
219 cls.all[currency] = portfolio.Balance(currency, {}) 205 self.all[currency] = portfolio.Balance(currency, {})
220 ReportStore.log_dispatch(amount, amounts, liquidity, repartition) 206 self.market.report.log_dispatch(amount, amounts, liquidity, repartition)
221 return amounts 207 return amounts
222 208
223 @classmethod 209 def as_json(self):
224 def as_json(cls): 210 return { k: v.as_json() for k, v in self.all.items() }
225 return { k: v.as_json() for k, v in cls.all.items() }
226 211
227class TradeStore: 212class TradeStore:
228 all = [] 213 def __init__(self, market):
229 debug = False 214 self.market = market
215 self.all = []
230 216
231 @classmethod 217 def compute_trades(self, values_in_base, new_repartition, only=None):
232 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
233 computed_trades = [] 218 computed_trades = []
234 cls.debug = cls.debug or debug
235 base_currency = sum(values_in_base.values()).currency 219 base_currency = sum(values_in_base.values()).currency
236 for currency in BalanceStore.currencies(): 220 for currency in self.market.balances.currencies():
237 if currency == base_currency: 221 if currency == base_currency:
238 continue 222 continue
239 value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0)) 223 value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0))
240 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) 224 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0))
241 225
242 if value_from.value * value_to.value < 0: 226 if value_from.value * value_to.value < 0:
243 computed_trades.append(cls.trade_if_matching( 227 computed_trades.append(self.trade_if_matching(
244 value_from, portfolio.Amount(base_currency, 0), 228 value_from, portfolio.Amount(base_currency, 0),
245 currency, only=only, market=market)) 229 currency, only=only))
246 computed_trades.append(cls.trade_if_matching( 230 computed_trades.append(self.trade_if_matching(
247 portfolio.Amount(base_currency, 0), value_to, 231 portfolio.Amount(base_currency, 0), value_to,
248 currency, only=only, market=market)) 232 currency, only=only))
249 else: 233 else:
250 computed_trades.append(cls.trade_if_matching( 234 computed_trades.append(self.trade_if_matching(
251 value_from, value_to, 235 value_from, value_to,
252 currency, only=only, market=market)) 236 currency, only=only))
253 for matching, trade in computed_trades: 237 for matching, trade in computed_trades:
254 if matching: 238 if matching:
255 cls.all.append(trade) 239 self.all.append(trade)
256 ReportStore.log_trades(computed_trades, only, cls.debug) 240 self.market.report.log_trades(computed_trades, only)
257 241
258 @classmethod 242 def trade_if_matching(self, value_from, value_to, currency,
259 def trade_if_matching(cls, value_from, value_to, currency, 243 only=None):
260 only=None, market=None):
261 trade = portfolio.Trade(value_from, value_to, currency, 244 trade = portfolio.Trade(value_from, value_to, currency,
262 market=market) 245 self.market)
263 matching = only is None or trade.action == only 246 matching = only is None or trade.action == only
264 return [matching, trade] 247 return [matching, trade]
265 248
266 @classmethod 249 def prepare_orders(self, only=None, compute_value="default"):
267 def prepare_orders(cls, only=None, compute_value="default"):
268 orders = [] 250 orders = []
269 for trade in cls.all: 251 for trade in self.all:
270 if only is None or trade.action == only: 252 if only is None or trade.action == only:
271 orders.append(trade.prepare_order(compute_value=compute_value)) 253 orders.append(trade.prepare_order(compute_value=compute_value))
272 ReportStore.log_orders(orders, only, compute_value) 254 self.market.report.log_orders(orders, only, compute_value)
273 255
274 @classmethod 256 def print_all_with_order(self, ind=""):
275 def print_all_with_order(cls, ind=""): 257 for trade in self.all:
276 for trade in cls.all:
277 trade.print_with_order(ind=ind) 258 trade.print_with_order(ind=ind)
278 259
279 @classmethod 260 def run_orders(self):
280 def run_orders(cls): 261 orders = self.all_orders(state="pending")
281 orders = cls.all_orders(state="pending")
282 for order in orders: 262 for order in orders:
283 order.run() 263 order.run()
284 ReportStore.log_stage("run_orders") 264 self.market.report.log_stage("run_orders")
285 ReportStore.log_orders(orders) 265 self.market.report.log_orders(orders)
286 266
287 @classmethod 267 def all_orders(self, state=None):
288 def all_orders(cls, state=None): 268 all_orders = sum(map(lambda v: v.orders, self.all), [])
289 all_orders = sum(map(lambda v: v.orders, cls.all), [])
290 if state is None: 269 if state is None:
291 return all_orders 270 return all_orders
292 else: 271 else:
293 return list(filter(lambda o: o.status == state, all_orders)) 272 return list(filter(lambda o: o.status == state, all_orders))
294 273
295 @classmethod 274 def update_all_orders_status(self):
296 def update_all_orders_status(cls): 275 for order in self.all_orders(state="open"):
297 for order in cls.all_orders(state="open"):
298 order.get_status() 276 order.get_status()
299 277
300 278
diff --git a/test.py b/test.py
index fc331c3..a9cae94 100644
--- a/test.py
+++ b/test.py
@@ -1,12 +1,13 @@
1import sys 1import sys
2import portfolio 2import portfolio
3import unittest 3import unittest
4import datetime
4from decimal import Decimal as D 5from decimal import Decimal as D
5from unittest import mock 6from unittest import mock
6import requests 7import requests
7import requests_mock 8import requests_mock
8from io import StringIO 9from io import StringIO
9import helper 10import portfolio, helper, market
10 11
11limits = ["acceptance", "unit"] 12limits = ["acceptance", "unit"]
12for test_type in limits: 13for test_type in limits:
@@ -26,18 +27,15 @@ class WebMockTestCase(unittest.TestCase):
26 self.wm = requests_mock.Mocker() 27 self.wm = requests_mock.Mocker()
27 self.wm.start() 28 self.wm.start()
28 29
30 # market
31 self.m = mock.Mock(name="Market", spec=market.Market)
32 self.m.debug = False
33
29 self.patchers = [ 34 self.patchers = [
30 mock.patch.multiple(portfolio.ReportStore,
31 logs=[], verbose_print=True),
32 mock.patch.multiple(portfolio.BalanceStore,
33 all={},),
34 mock.patch.multiple(portfolio.TradeStore,
35 all=[],
36 debug=False),
37 mock.patch.multiple(portfolio.Portfolio, last_date=None, data=None, liquidities={}), 35 mock.patch.multiple(portfolio.Portfolio, last_date=None, data=None, liquidities={}),
38 mock.patch.multiple(portfolio.Computation, 36 mock.patch.multiple(portfolio.Computation,
39 computations=portfolio.Computation.computations), 37 computations=portfolio.Computation.computations),
40 mock.patch.multiple(helper, 38 mock.patch.multiple(market.Market,
41 fees_cache={}, 39 fees_cache={},
42 ticker_cache={}, 40 ticker_cache={},
43 ticker_cache_timestamp=self.time.time()), 41 ticker_cache_timestamp=self.time.time()),
@@ -65,41 +63,39 @@ class PortfolioTest(WebMockTestCase):
65 63
66 self.wm.get(portfolio.Portfolio.URL, text=self.json_response) 64 self.wm.get(portfolio.Portfolio.URL, text=self.json_response)
67 65
68 @mock.patch("portfolio.ReportStore") 66 def test_get_cryptoportfolio(self):
69 def test_get_cryptoportfolio(self, report_store):
70 self.wm.get(portfolio.Portfolio.URL, [ 67 self.wm.get(portfolio.Portfolio.URL, [
71 {"text":'{ "foo": "bar" }', "status_code": 200}, 68 {"text":'{ "foo": "bar" }', "status_code": 200},
72 {"text": "System Error", "status_code": 500}, 69 {"text": "System Error", "status_code": 500},
73 {"exc": requests.exceptions.ConnectTimeout}, 70 {"exc": requests.exceptions.ConnectTimeout},
74 ]) 71 ])
75 portfolio.Portfolio.get_cryptoportfolio() 72 portfolio.Portfolio.get_cryptoportfolio(self.m)
76 self.assertIn("foo", portfolio.Portfolio.data) 73 self.assertIn("foo", portfolio.Portfolio.data)
77 self.assertEqual("bar", portfolio.Portfolio.data["foo"]) 74 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
78 self.assertTrue(self.wm.called) 75 self.assertTrue(self.wm.called)
79 self.assertEqual(1, self.wm.call_count) 76 self.assertEqual(1, self.wm.call_count)
80 report_store.log_error.assert_not_called() 77 self.m.report.log_error.assert_not_called()
81 report_store.log_http_request.assert_called_once() 78 self.m.report.log_http_request.assert_called_once()
82 report_store.log_http_request.reset_mock() 79 self.m.report.log_http_request.reset_mock()
83 80
84 portfolio.Portfolio.get_cryptoportfolio() 81 portfolio.Portfolio.get_cryptoportfolio(self.m)
85 self.assertIsNone(portfolio.Portfolio.data) 82 self.assertIsNone(portfolio.Portfolio.data)
86 self.assertEqual(2, self.wm.call_count) 83 self.assertEqual(2, self.wm.call_count)
87 report_store.log_error.assert_not_called() 84 self.m.report.log_error.assert_not_called()
88 report_store.log_http_request.assert_called_once() 85 self.m.report.log_http_request.assert_called_once()
89 report_store.log_http_request.reset_mock() 86 self.m.report.log_http_request.reset_mock()
90 87
91 88
92 portfolio.Portfolio.data = "Foo" 89 portfolio.Portfolio.data = "Foo"
93 portfolio.Portfolio.get_cryptoportfolio() 90 portfolio.Portfolio.get_cryptoportfolio(self.m)
94 self.assertEqual("Foo", portfolio.Portfolio.data) 91 self.assertEqual("Foo", portfolio.Portfolio.data)
95 self.assertEqual(3, self.wm.call_count) 92 self.assertEqual(3, self.wm.call_count)
96 report_store.log_error.assert_called_once_with("get_cryptoportfolio", 93 self.m.report.log_error.assert_called_once_with("get_cryptoportfolio",
97 exception=mock.ANY) 94 exception=mock.ANY)
98 report_store.log_http_request.assert_not_called() 95 self.m.report.log_http_request.assert_not_called()
99 96
100 @mock.patch("portfolio.ReportStore") 97 def test_parse_cryptoportfolio(self):
101 def test_parse_cryptoportfolio(self, report_store): 98 portfolio.Portfolio.parse_cryptoportfolio(self.m)
102 portfolio.Portfolio.parse_cryptoportfolio()
103 99
104 self.assertListEqual( 100 self.assertListEqual(
105 ["medium", "high"], 101 ["medium", "high"],
@@ -135,22 +131,21 @@ class PortfolioTest(WebMockTestCase):
135 self.assertDictEqual(expected, liquidities["medium"][date]) 131 self.assertDictEqual(expected, liquidities["medium"][date])
136 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date) 132 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)
137 133
138 report_store.log_http_request.assert_called_once_with("GET", 134 self.m.report.log_http_request.assert_called_once_with("GET",
139 portfolio.Portfolio.URL, None, mock.ANY, mock.ANY) 135 portfolio.Portfolio.URL, None, mock.ANY, mock.ANY)
140 report_store.log_http_request.reset_mock() 136 self.m.report.log_http_request.reset_mock()
141 137
142 # It doesn't refetch the data when available 138 # It doesn't refetch the data when available
143 portfolio.Portfolio.parse_cryptoportfolio() 139 portfolio.Portfolio.parse_cryptoportfolio(self.m)
144 report_store.log_http_request.assert_not_called() 140 self.m.report.log_http_request.assert_not_called()
145 141
146 self.assertEqual(1, self.wm.call_count) 142 self.assertEqual(1, self.wm.call_count)
147 143
148 portfolio.Portfolio.parse_cryptoportfolio(refetch=True) 144 portfolio.Portfolio.parse_cryptoportfolio(self.m, refetch=True)
149 self.assertEqual(2, self.wm.call_count) 145 self.assertEqual(2, self.wm.call_count)
150 report_store.log_http_request.assert_called_once() 146 self.m.report.log_http_request.assert_called_once()
151 147
152 @mock.patch("portfolio.ReportStore") 148 def test_repartition(self):
153 def test_repartition(self, report_store):
154 expected_medium = { 149 expected_medium = {
155 'BTC': (D("1.1102e-16"), "long"), 150 'BTC': (D("1.1102e-16"), "long"),
156 'USDT': (D("0.1"), "long"), 151 'USDT': (D("0.1"), "long"),
@@ -173,25 +168,26 @@ class PortfolioTest(WebMockTestCase):
173 'GAS': (D("0.1308"), "long"), 168 'GAS': (D("0.1308"), "long"),
174 } 169 }
175 170
176 self.assertEqual(expected_medium, portfolio.Portfolio.repartition()) 171 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(self.m))
177 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium")) 172 self.assertEqual(expected_medium, portfolio.Portfolio.repartition(self.m, liquidity="medium"))
178 self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high")) 173 self.assertEqual(expected_high, portfolio.Portfolio.repartition(self.m, liquidity="high"))
179 174
180 self.assertEqual(1, self.wm.call_count) 175 self.assertEqual(1, self.wm.call_count)
181 176
182 portfolio.Portfolio.repartition() 177 portfolio.Portfolio.repartition(self.m)
183 self.assertEqual(1, self.wm.call_count) 178 self.assertEqual(1, self.wm.call_count)
184 179
185 portfolio.Portfolio.repartition(refetch=True) 180 portfolio.Portfolio.repartition(self.m, refetch=True)
186 self.assertEqual(2, self.wm.call_count) 181 self.assertEqual(2, self.wm.call_count)
187 report_store.log_http_request.assert_called() 182 self.m.report.log_http_request.assert_called()
188 self.assertEqual(2, report_store.log_http_request.call_count) 183 self.assertEqual(2, self.m.report.log_http_request.call_count)
189 184
190 @mock.patch.object(portfolio.time, "sleep") 185 @mock.patch.object(portfolio.time, "sleep")
191 @mock.patch.object(portfolio.Portfolio, "repartition") 186 @mock.patch.object(portfolio.Portfolio, "repartition")
192 def test_wait_for_recent(self, repartition, sleep): 187 def test_wait_for_recent(self, repartition, sleep):
193 self.call_count = 0 188 self.call_count = 0
194 def _repartition(refetch): 189 def _repartition(market, refetch):
190 self.assertEqual(self.m, market)
195 self.assertTrue(refetch) 191 self.assertTrue(refetch)
196 self.call_count += 1 192 self.call_count += 1
197 portfolio.Portfolio.last_date = portfolio.datetime.now()\ 193 portfolio.Portfolio.last_date = portfolio.datetime.now()\
@@ -199,16 +195,17 @@ class PortfolioTest(WebMockTestCase):
199 + portfolio.timedelta(self.call_count) 195 + portfolio.timedelta(self.call_count)
200 repartition.side_effect = _repartition 196 repartition.side_effect = _repartition
201 197
202 portfolio.Portfolio.wait_for_recent() 198 portfolio.Portfolio.wait_for_recent(self.m)
203 sleep.assert_called_with(30) 199 sleep.assert_called_with(30)
204 self.assertEqual(6, sleep.call_count) 200 self.assertEqual(6, sleep.call_count)
205 self.assertEqual(7, repartition.call_count) 201 self.assertEqual(7, repartition.call_count)
202 self.m.report.print_log.assert_called_with("Attempt to fetch up-to-date cryptoportfolio")
206 203
207 sleep.reset_mock() 204 sleep.reset_mock()
208 repartition.reset_mock() 205 repartition.reset_mock()
209 portfolio.Portfolio.last_date = None 206 portfolio.Portfolio.last_date = None
210 self.call_count = 0 207 self.call_count = 0
211 portfolio.Portfolio.wait_for_recent(delta=15) 208 portfolio.Portfolio.wait_for_recent(self.m, delta=15)
212 sleep.assert_not_called() 209 sleep.assert_not_called()
213 self.assertEqual(1, repartition.call_count) 210 self.assertEqual(1, repartition.call_count)
214 211
@@ -216,7 +213,7 @@ class PortfolioTest(WebMockTestCase):
216 repartition.reset_mock() 213 repartition.reset_mock()
217 portfolio.Portfolio.last_date = None 214 portfolio.Portfolio.last_date = None
218 self.call_count = 0 215 self.call_count = 0
219 portfolio.Portfolio.wait_for_recent(delta=1) 216 portfolio.Portfolio.wait_for_recent(self.m, delta=1)
220 sleep.assert_called_with(30) 217 sleep.assert_called_with(30)
221 self.assertEqual(9, sleep.call_count) 218 self.assertEqual(9, sleep.call_count)
222 self.assertEqual(10, repartition.call_count) 219 self.assertEqual(10, repartition.call_count)
@@ -231,35 +228,34 @@ class AmountTest(WebMockTestCase):
231 def test_in_currency(self): 228 def test_in_currency(self):
232 amount = portfolio.Amount("ETC", 10) 229 amount = portfolio.Amount("ETC", 10)
233 230
234 self.assertEqual(amount, amount.in_currency("ETC", None)) 231 self.assertEqual(amount, amount.in_currency("ETC", self.m))
235 232
236 ticker_mock = unittest.mock.Mock() 233 with self.subTest(desc="no ticker for currency"):
237 with mock.patch.object(helper, 'get_ticker', new=ticker_mock): 234 self.m.get_ticker.return_value = None
238 ticker_mock.return_value = None
239 235
240 self.assertRaises(Exception, amount.in_currency, "ETH", None) 236 self.assertRaises(Exception, amount.in_currency, "ETH", self.m)
241 237
242 with mock.patch.object(helper, 'get_ticker', new=ticker_mock): 238 with self.subTest(desc="nominal case"):
243 ticker_mock.return_value = { 239 self.m.get_ticker.return_value = {
244 "bid": D("0.2"), 240 "bid": D("0.2"),
245 "ask": D("0.4"), 241 "ask": D("0.4"),
246 "average": D("0.3"), 242 "average": D("0.3"),
247 "foo": "bar", 243 "foo": "bar",
248 } 244 }
249 converted_amount = amount.in_currency("ETH", None) 245 converted_amount = amount.in_currency("ETH", self.m)
250 246
251 self.assertEqual(D("3.0"), converted_amount.value) 247 self.assertEqual(D("3.0"), converted_amount.value)
252 self.assertEqual("ETH", converted_amount.currency) 248 self.assertEqual("ETH", converted_amount.currency)
253 self.assertEqual(amount, converted_amount.linked_to) 249 self.assertEqual(amount, converted_amount.linked_to)
254 self.assertEqual("bar", converted_amount.ticker["foo"]) 250 self.assertEqual("bar", converted_amount.ticker["foo"])
255 251
256 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default") 252 converted_amount = amount.in_currency("ETH", self.m, action="bid", compute_value="default")
257 self.assertEqual(D("2"), converted_amount.value) 253 self.assertEqual(D("2"), converted_amount.value)
258 254
259 converted_amount = amount.in_currency("ETH", None, compute_value="ask") 255 converted_amount = amount.in_currency("ETH", self.m, compute_value="ask")
260 self.assertEqual(D("4"), converted_amount.value) 256 self.assertEqual(D("4"), converted_amount.value)
261 257
262 converted_amount = amount.in_currency("ETH", None, rate=D("0.02")) 258 converted_amount = amount.in_currency("ETH", self.m, rate=D("0.02"))
263 self.assertEqual(D("0.2"), converted_amount.value) 259 self.assertEqual(D("0.2"), converted_amount.value)
264 260
265 def test__round(self): 261 def test__round(self):
@@ -318,7 +314,7 @@ class AmountTest(WebMockTestCase):
318 with self.assertRaises(Exception): 314 with self.assertRaises(Exception):
319 3 - amount 315 3 - amount
320 316
321 self.assertEqual(portfolio.Amount("ETH", "-1.6"), -amount) 317 self.assertEqual(portfolio.Amount("ETH", "-1.6"), 0-amount)
322 318
323 def test__mul(self): 319 def test__mul(self):
324 amount = portfolio.Amount("XEM", 11) 320 amount = portfolio.Amount("XEM", 11)
@@ -544,34 +540,76 @@ class BalanceTest(WebMockTestCase):
544 self.assertEqual(D(0), as_json["margin_borrowed"]) 540 self.assertEqual(D(0), as_json["margin_borrowed"])
545 541
546@unittest.skipUnless("unit" in limits, "Unit skipped") 542@unittest.skipUnless("unit" in limits, "Unit skipped")
547class HelperTest(WebMockTestCase): 543class MarketTest(WebMockTestCase):
544 def setUp(self):
545 super(MarketTest, self).setUp()
546
547 self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
548
549 def test_values(self):
550 m = market.Market(self.ccxt)
551
552 self.assertEqual(self.ccxt, m.ccxt)
553 self.assertFalse(m.debug)
554 self.assertIsInstance(m.report, market.ReportStore)
555 self.assertIsInstance(m.trades, market.TradeStore)
556 self.assertIsInstance(m.balances, market.BalanceStore)
557 self.assertEqual(m, m.report.market)
558 self.assertEqual(m, m.trades.market)
559 self.assertEqual(m, m.balances.market)
560 self.assertEqual(m, m.ccxt._market)
561
562 m = market.Market(self.ccxt, debug=True)
563 self.assertTrue(m.debug)
564
565 m = market.Market(self.ccxt, debug=False)
566 self.assertFalse(m.debug)
567
568 @mock.patch("market.ccxt")
569 def test_from_config(self, ccxt):
570 with mock.patch("market.ReportStore"):
571 ccxt.poloniexE.return_value = self.ccxt
572 self.ccxt.session.request.return_value = "response"
573
574 m = market.Market.from_config("config")
575
576 self.assertEqual(self.ccxt, m.ccxt)
577
578 self.ccxt.session.request("GET", "URL", data="data",
579 headers="headers")
580 m.report.log_http_request.assert_called_with('GET', 'URL', 'data',
581 'headers', 'response')
582
583 m = market.Market.from_config("config", debug=True)
584 self.assertEqual(True, m.debug)
585
548 def test_get_ticker(self): 586 def test_get_ticker(self):
549 market = mock.Mock() 587 m = market.Market(self.ccxt)
550 market.fetch_ticker.side_effect = [ 588 self.ccxt.fetch_ticker.side_effect = [
551 { "bid": 1, "ask": 3 }, 589 { "bid": 1, "ask": 3 },
552 helper.ExchangeError("foo"), 590 market.ExchangeError("foo"),
553 { "bid": 10, "ask": 40 }, 591 { "bid": 10, "ask": 40 },
554 helper.ExchangeError("foo"), 592 market.ExchangeError("foo"),
555 helper.ExchangeError("foo"), 593 market.ExchangeError("foo"),
556 ] 594 ]
557 595
558 ticker = helper.get_ticker("ETH", "ETC", market) 596 ticker = m.get_ticker("ETH", "ETC")
559 market.fetch_ticker.assert_called_with("ETH/ETC") 597 self.ccxt.fetch_ticker.assert_called_with("ETH/ETC")
560 self.assertEqual(1, ticker["bid"]) 598 self.assertEqual(1, ticker["bid"])
561 self.assertEqual(3, ticker["ask"]) 599 self.assertEqual(3, ticker["ask"])
562 self.assertEqual(2, ticker["average"]) 600 self.assertEqual(2, ticker["average"])
563 self.assertFalse(ticker["inverted"]) 601 self.assertFalse(ticker["inverted"])
564 602
565 ticker = helper.get_ticker("ETH", "XVG", market) 603 ticker = m.get_ticker("ETH", "XVG")
566 self.assertEqual(0.0625, ticker["average"]) 604 self.assertEqual(0.0625, ticker["average"])
567 self.assertTrue(ticker["inverted"]) 605 self.assertTrue(ticker["inverted"])
568 self.assertIn("original", ticker) 606 self.assertIn("original", ticker)
569 self.assertEqual(10, ticker["original"]["bid"]) 607 self.assertEqual(10, ticker["original"]["bid"])
570 608
571 ticker = helper.get_ticker("XVG", "XMR", market) 609 ticker = m.get_ticker("XVG", "XMR")
572 self.assertIsNone(ticker) 610 self.assertIsNone(ticker)
573 611
574 market.fetch_ticker.assert_has_calls([ 612 self.ccxt.fetch_ticker.assert_has_calls([
575 mock.call("ETH/ETC"), 613 mock.call("ETH/ETC"),
576 mock.call("ETH/XVG"), 614 mock.call("ETH/XVG"),
577 mock.call("XVG/ETH"), 615 mock.call("XVG/ETH"),
@@ -579,56 +617,62 @@ class HelperTest(WebMockTestCase):
579 mock.call("XMR/XVG"), 617 mock.call("XMR/XVG"),
580 ]) 618 ])
581 619
582 market2 = mock.Mock() 620 self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
583 market2.fetch_ticker.side_effect = [ 621 m1b = market.Market(self.ccxt)
622 m1b.get_ticker("ETH", "ETC")
623 self.ccxt.fetch_ticker.assert_not_called()
624
625 self.ccxt = mock.Mock(spec=market.ccxt.poloniex)
626 m2 = market.Market(self.ccxt)
627 self.ccxt.fetch_ticker.side_effect = [
584 { "bid": 1, "ask": 3 }, 628 { "bid": 1, "ask": 3 },
585 { "bid": 1.2, "ask": 3.5 }, 629 { "bid": 1.2, "ask": 3.5 },
586 ] 630 ]
587 ticker1 = helper.get_ticker("ETH", "ETC", market2) 631 ticker1 = m2.get_ticker("ETH", "ETC")
588 ticker2 = helper.get_ticker("ETH", "ETC", market2) 632 ticker2 = m2.get_ticker("ETH", "ETC")
589 ticker3 = helper.get_ticker("ETC", "ETH", market2) 633 ticker3 = m2.get_ticker("ETC", "ETH")
590 market2.fetch_ticker.assert_called_once_with("ETH/ETC") 634 self.ccxt.fetch_ticker.assert_called_once_with("ETH/ETC")
591 self.assertEqual(1, ticker1["bid"]) 635 self.assertEqual(1, ticker1["bid"])
592 self.assertDictEqual(ticker1, ticker2) 636 self.assertDictEqual(ticker1, ticker2)
593 self.assertDictEqual(ticker1, ticker3["original"]) 637 self.assertDictEqual(ticker1, ticker3["original"])
594 638
595 ticker4 = helper.get_ticker("ETH", "ETC", market2, refresh=True) 639 ticker4 = m2.get_ticker("ETH", "ETC", refresh=True)
596 ticker5 = helper.get_ticker("ETH", "ETC", market2) 640 ticker5 = m2.get_ticker("ETH", "ETC")
597 self.assertEqual(1.2, ticker4["bid"]) 641 self.assertEqual(1.2, ticker4["bid"])
598 self.assertDictEqual(ticker4, ticker5) 642 self.assertDictEqual(ticker4, ticker5)
599 643
600 market3 = mock.Mock() 644 self.ccxt = mock.Mock(spec=market.ccxt.binance)
601 market3.fetch_ticker.side_effect = [ 645 m3 = market.Market(self.ccxt)
646 self.ccxt.fetch_ticker.side_effect = [
602 { "bid": 1, "ask": 3 }, 647 { "bid": 1, "ask": 3 },
603 { "bid": 1.2, "ask": 3.5 }, 648 { "bid": 1.2, "ask": 3.5 },
604 ] 649 ]
605 ticker6 = helper.get_ticker("ETH", "ETC", market3) 650 ticker6 = m3.get_ticker("ETH", "ETC")
606 helper.ticker_cache_timestamp -= 4 651 m3.ticker_cache_timestamp -= 4
607 ticker7 = helper.get_ticker("ETH", "ETC", market3) 652 ticker7 = m3.get_ticker("ETH", "ETC")
608 helper.ticker_cache_timestamp -= 2 653 m3.ticker_cache_timestamp -= 2
609 ticker8 = helper.get_ticker("ETH", "ETC", market3) 654 ticker8 = m3.get_ticker("ETH", "ETC")
610 self.assertDictEqual(ticker6, ticker7) 655 self.assertDictEqual(ticker6, ticker7)
611 self.assertEqual(1.2, ticker8["bid"]) 656 self.assertEqual(1.2, ticker8["bid"])
612 657
613 def test_fetch_fees(self): 658 def test_fetch_fees(self):
614 market = mock.Mock() 659 m = market.Market(self.ccxt)
615 market.fetch_fees.return_value = "Foo" 660 self.ccxt.fetch_fees.return_value = "Foo"
616 self.assertEqual("Foo", helper.fetch_fees(market)) 661 self.assertEqual("Foo", m.fetch_fees())
617 market.fetch_fees.assert_called_once() 662 self.ccxt.fetch_fees.assert_called_once()
618 self.assertEqual("Foo", helper.fetch_fees(market)) 663 self.ccxt.reset_mock()
619 market.fetch_fees.assert_called_once() 664 self.assertEqual("Foo", m.fetch_fees())
665 self.ccxt.fetch_fees.assert_not_called()
620 666
621 @mock.patch.object(portfolio.Portfolio, "repartition") 667 @mock.patch.object(portfolio.Portfolio, "repartition")
622 @mock.patch.object(helper, "get_ticker") 668 @mock.patch.object(market.Market, "get_ticker")
623 @mock.patch.object(portfolio.TradeStore, "compute_trades") 669 @mock.patch.object(market.TradeStore, "compute_trades")
624 @mock.patch("store.ReportStore") 670 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
625 @mock.patch("helper.ReportStore")
626 def test_prepare_trades(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
627 repartition.return_value = { 671 repartition.return_value = {
628 "XEM": (D("0.75"), "long"), 672 "XEM": (D("0.75"), "long"),
629 "BTC": (D("0.25"), "long"), 673 "BTC": (D("0.25"), "long"),
630 } 674 }
631 def _get_ticker(c1, c2, market): 675 def _get_ticker(c1, c2):
632 if c1 == "USDT" and c2 == "BTC": 676 if c1 == "USDT" and c2 == "BTC":
633 return { "average": D("0.0001") } 677 return { "average": D("0.0001") }
634 if c1 == "XVG" and c2 == "BTC": 678 if c1 == "XVG" and c2 == "BTC":
@@ -638,46 +682,45 @@ class HelperTest(WebMockTestCase):
638 self.fail("Should be called with {}, {}".format(c1, c2)) 682 self.fail("Should be called with {}, {}".format(c1, c2))
639 get_ticker.side_effect = _get_ticker 683 get_ticker.side_effect = _get_ticker
640 684
641 market = mock.Mock() 685 with mock.patch("market.ReportStore"):
642 market.fetch_all_balances.return_value = { 686 m = market.Market(self.ccxt)
643 "USDT": { 687 self.ccxt.fetch_all_balances.return_value = {
644 "exchange_free": D("10000.0"), 688 "USDT": {
645 "exchange_used": D("0.0"), 689 "exchange_free": D("10000.0"),
646 "exchange_total": D("10000.0"), 690 "exchange_used": D("0.0"),
647 "total": D("10000.0") 691 "exchange_total": D("10000.0"),
648 }, 692 "total": D("10000.0")
649 "XVG": { 693 },
650 "exchange_free": D("10000.0"), 694 "XVG": {
651 "exchange_used": D("0.0"), 695 "exchange_free": D("10000.0"),
652 "exchange_total": D("10000.0"), 696 "exchange_used": D("0.0"),
653 "total": D("10000.0") 697 "exchange_total": D("10000.0"),
654 }, 698 "total": D("10000.0")
655 } 699 },
656 portfolio.BalanceStore.fetch_balances(market, tag="tag") 700 }
657 701
658 helper.prepare_trades(market) 702 m.balances.fetch_balances(tag="tag")
659 compute_trades.assert_called()
660 703
661 call = compute_trades.call_args 704 m.prepare_trades()
662 self.assertEqual(market, call[1]["market"]) 705 compute_trades.assert_called()
663 self.assertEqual(1, call[0][0]["USDT"].value) 706
664 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 707 call = compute_trades.call_args
665 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value) 708 self.assertEqual(1, call[0][0]["USDT"].value)
666 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 709 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
667 report_store_h.log_stage.assert_called_once_with("prepare_trades") 710 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
668 report_store.log_balances.assert_called_once_with(market, tag="tag") 711 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
712 m.report.log_stage.assert_called_once_with("prepare_trades")
713 m.report.log_balances.assert_called_once_with(tag="tag")
669 714
670 @mock.patch.object(portfolio.Portfolio, "repartition") 715 @mock.patch.object(portfolio.Portfolio, "repartition")
671 @mock.patch.object(helper, "get_ticker") 716 @mock.patch.object(market.Market, "get_ticker")
672 @mock.patch.object(portfolio.TradeStore, "compute_trades") 717 @mock.patch.object(market.TradeStore, "compute_trades")
673 @mock.patch("store.ReportStore") 718 def test_update_trades(self, compute_trades, get_ticker, repartition):
674 @mock.patch("helper.ReportStore")
675 def test_update_trades(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
676 repartition.return_value = { 719 repartition.return_value = {
677 "XEM": (D("0.75"), "long"), 720 "XEM": (D("0.75"), "long"),
678 "BTC": (D("0.25"), "long"), 721 "BTC": (D("0.25"), "long"),
679 } 722 }
680 def _get_ticker(c1, c2, market): 723 def _get_ticker(c1, c2):
681 if c1 == "USDT" and c2 == "BTC": 724 if c1 == "USDT" and c2 == "BTC":
682 return { "average": D("0.0001") } 725 return { "average": D("0.0001") }
683 if c1 == "XVG" and c2 == "BTC": 726 if c1 == "XVG" and c2 == "BTC":
@@ -687,42 +730,41 @@ class HelperTest(WebMockTestCase):
687 self.fail("Should be called with {}, {}".format(c1, c2)) 730 self.fail("Should be called with {}, {}".format(c1, c2))
688 get_ticker.side_effect = _get_ticker 731 get_ticker.side_effect = _get_ticker
689 732
690 market = mock.Mock() 733 with mock.patch("market.ReportStore"):
691 market.fetch_all_balances.return_value = { 734 m = market.Market(self.ccxt)
692 "USDT": { 735 self.ccxt.fetch_all_balances.return_value = {
693 "exchange_free": D("10000.0"), 736 "USDT": {
694 "exchange_used": D("0.0"), 737 "exchange_free": D("10000.0"),
695 "exchange_total": D("10000.0"), 738 "exchange_used": D("0.0"),
696 "total": D("10000.0") 739 "exchange_total": D("10000.0"),
697 }, 740 "total": D("10000.0")
698 "XVG": { 741 },
699 "exchange_free": D("10000.0"), 742 "XVG": {
700 "exchange_used": D("0.0"), 743 "exchange_free": D("10000.0"),
701 "exchange_total": D("10000.0"), 744 "exchange_used": D("0.0"),
702 "total": D("10000.0") 745 "exchange_total": D("10000.0"),
703 }, 746 "total": D("10000.0")
704 } 747 },
705 portfolio.BalanceStore.fetch_balances(market, tag="tag") 748 }
749
750 m.balances.fetch_balances(tag="tag")
706 751
707 helper.update_trades(market) 752 m.update_trades()
708 compute_trades.assert_called() 753 compute_trades.assert_called()
709 754
710 call = compute_trades.call_args 755 call = compute_trades.call_args
711 self.assertEqual(market, call[1]["market"]) 756 self.assertEqual(1, call[0][0]["USDT"].value)
712 self.assertEqual(1, call[0][0]["USDT"].value) 757 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
713 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 758 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
714 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value) 759 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
715 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 760 m.report.log_stage.assert_called_once_with("update_trades")
716 report_store_h.log_stage.assert_called_once_with("update_trades") 761 m.report.log_balances.assert_called_once_with(tag="tag")
717 report_store.log_balances.assert_called_once_with(market, tag="tag")
718 762
719 @mock.patch.object(portfolio.Portfolio, "repartition") 763 @mock.patch.object(portfolio.Portfolio, "repartition")
720 @mock.patch.object(helper, "get_ticker") 764 @mock.patch.object(market.Market, "get_ticker")
721 @mock.patch.object(portfolio.TradeStore, "compute_trades") 765 @mock.patch.object(market.TradeStore, "compute_trades")
722 @mock.patch("store.ReportStore") 766 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
723 @mock.patch("helper.ReportStore") 767 def _get_ticker(c1, c2):
724 def test_prepare_trades_to_sell_all(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
725 def _get_ticker(c1, c2, market):
726 if c1 == "USDT" and c2 == "BTC": 768 if c1 == "USDT" and c2 == "BTC":
727 return { "average": D("0.0001") } 769 return { "average": D("0.0001") }
728 if c1 == "XVG" and c2 == "BTC": 770 if c1 == "XVG" and c2 == "BTC":
@@ -730,44 +772,47 @@ class HelperTest(WebMockTestCase):
730 self.fail("Should be called with {}, {}".format(c1, c2)) 772 self.fail("Should be called with {}, {}".format(c1, c2))
731 get_ticker.side_effect = _get_ticker 773 get_ticker.side_effect = _get_ticker
732 774
733 market = mock.Mock() 775 with mock.patch("market.ReportStore"):
734 market.fetch_all_balances.return_value = { 776 m = market.Market(self.ccxt)
735 "USDT": { 777 self.ccxt.fetch_all_balances.return_value = {
736 "exchange_free": D("10000.0"), 778 "USDT": {
737 "exchange_used": D("0.0"), 779 "exchange_free": D("10000.0"),
738 "exchange_total": D("10000.0"), 780 "exchange_used": D("0.0"),
739 "total": D("10000.0") 781 "exchange_total": D("10000.0"),
740 }, 782 "total": D("10000.0")
741 "XVG": { 783 },
742 "exchange_free": D("10000.0"), 784 "XVG": {
743 "exchange_used": D("0.0"), 785 "exchange_free": D("10000.0"),
744 "exchange_total": D("10000.0"), 786 "exchange_used": D("0.0"),
745 "total": D("10000.0") 787 "exchange_total": D("10000.0"),
746 }, 788 "total": D("10000.0")
747 } 789 },
748 portfolio.BalanceStore.fetch_balances(market, tag="tag") 790 }
791
792 m.balances.fetch_balances(tag="tag")
793
794 m.prepare_trades_to_sell_all()
749 795
750 helper.prepare_trades_to_sell_all(market) 796 repartition.assert_not_called()
751 repartition.assert_not_called() 797 compute_trades.assert_called()
752 compute_trades.assert_called()
753 798
754 call = compute_trades.call_args 799 call = compute_trades.call_args
755 self.assertEqual(market, call[1]["market"]) 800 self.assertEqual(1, call[0][0]["USDT"].value)
756 self.assertEqual(1, call[0][0]["USDT"].value) 801 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
757 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 802 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
758 self.assertEqual(D("1.01"), call[0][1]["BTC"].value) 803 m.report.log_stage.assert_called_once_with("prepare_trades_to_sell_all")
759 report_store_h.log_stage.assert_called_once_with("prepare_trades_to_sell_all") 804 m.report.log_balances.assert_called_once_with(tag="tag")
760 report_store.log_balances.assert_called_once_with(market, tag="tag")
761 805
762 @mock.patch.object(portfolio.time, "sleep") 806 @mock.patch.object(portfolio.time, "sleep")
763 @mock.patch.object(portfolio.TradeStore, "all_orders") 807 @mock.patch.object(market.TradeStore, "all_orders")
764 def test_follow_orders(self, all_orders, time_mock): 808 def test_follow_orders(self, all_orders, time_mock):
765 for debug, sleep in [ 809 for debug, sleep in [
766 (False, None), (True, None), 810 (False, None), (True, None),
767 (False, 12), (True, 12)]: 811 (False, 12), (True, 12)]:
768 with self.subTest(sleep=sleep, debug=debug), \ 812 with self.subTest(sleep=sleep, debug=debug), \
769 mock.patch("helper.ReportStore") as report_store: 813 mock.patch("market.ReportStore"):
770 portfolio.TradeStore.debug = debug 814 m = market.Market(self.ccxt, debug=debug)
815
771 order_mock1 = mock.Mock() 816 order_mock1 = mock.Mock()
772 order_mock2 = mock.Mock() 817 order_mock2 = mock.Mock()
773 order_mock3 = mock.Mock() 818 order_mock3 = mock.Mock()
@@ -792,7 +837,7 @@ class HelperTest(WebMockTestCase):
792 order_mock2.trade = mock.Mock() 837 order_mock2.trade = mock.Mock()
793 order_mock3.trade = mock.Mock() 838 order_mock3.trade = mock.Mock()
794 839
795 helper.follow_orders(sleep=sleep) 840 m.follow_orders(sleep=sleep)
796 841
797 order_mock1.trade.update_order.assert_any_call(order_mock1, 1) 842 order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
798 order_mock1.trade.update_order.assert_any_call(order_mock1, 2) 843 order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
@@ -806,7 +851,7 @@ class HelperTest(WebMockTestCase):
806 order_mock3.trade.update_order.assert_any_call(order_mock3, 2) 851 order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
807 self.assertEqual(1, order_mock3.trade.update_order.call_count) 852 self.assertEqual(1, order_mock3.trade.update_order.call_count)
808 self.assertEqual(2, order_mock3.get_status.call_count) 853 self.assertEqual(2, order_mock3.get_status.call_count)
809 report_store.log_stage.assert_called() 854 m.report.log_stage.assert_called()
810 calls = [ 855 calls = [
811 mock.call("follow_orders_begin"), 856 mock.call("follow_orders_begin"),
812 mock.call("follow_orders_tick_1"), 857 mock.call("follow_orders_tick_1"),
@@ -814,285 +859,75 @@ class HelperTest(WebMockTestCase):
814 mock.call("follow_orders_tick_3"), 859 mock.call("follow_orders_tick_3"),
815 mock.call("follow_orders_end"), 860 mock.call("follow_orders_end"),
816 ] 861 ]
817 report_store.log_stage.assert_has_calls(calls) 862 m.report.log_stage.assert_has_calls(calls)
818 report_store.log_orders.assert_called() 863 m.report.log_orders.assert_called()
819 self.assertEqual(3, report_store.log_orders.call_count) 864 self.assertEqual(3, m.report.log_orders.call_count)
820 calls = [ 865 calls = [
821 mock.call([order_mock1, order_mock2], tick=1), 866 mock.call([order_mock1, order_mock2], tick=1),
822 mock.call([order_mock1, order_mock3], tick=2), 867 mock.call([order_mock1, order_mock3], tick=2),
823 mock.call([order_mock1, order_mock3], tick=3), 868 mock.call([order_mock1, order_mock3], tick=3),
824 ] 869 ]
825 report_store.log_orders.assert_has_calls(calls) 870 m.report.log_orders.assert_has_calls(calls)
826 calls = [ 871 calls = [
827 mock.call(order_mock1, 3, finished=True), 872 mock.call(order_mock1, 3, finished=True),
828 mock.call(order_mock3, 3, finished=True), 873 mock.call(order_mock3, 3, finished=True),
829 ] 874 ]
830 report_store.log_order.assert_has_calls(calls) 875 m.report.log_order.assert_has_calls(calls)
831 876
832 if sleep is None: 877 if sleep is None:
833 if debug: 878 if debug:
834 report_store.log_debug_action.assert_called_with("Set follow_orders tick to 7s") 879 m.report.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
835 time_mock.assert_called_with(7) 880 time_mock.assert_called_with(7)
836 else: 881 else:
837 time_mock.assert_called_with(30) 882 time_mock.assert_called_with(30)
838 else: 883 else:
839 time_mock.assert_called_with(sleep) 884 time_mock.assert_called_with(sleep)
840 885
841 @mock.patch.object(portfolio.BalanceStore, "fetch_balances") 886 @mock.patch.object(market.BalanceStore, "fetch_balances")
842 def test_move_balance(self, fetch_balances): 887 def test_move_balance(self, fetch_balances):
843 for debug in [True, False]: 888 for debug in [True, False]:
844 with self.subTest(debug=debug),\ 889 with self.subTest(debug=debug),\
845 mock.patch("helper.ReportStore") as report_store: 890 mock.patch("market.ReportStore"):
891 m = market.Market(self.ccxt, debug=debug)
892
846 value_from = portfolio.Amount("BTC", "1.0") 893 value_from = portfolio.Amount("BTC", "1.0")
847 value_from.linked_to = portfolio.Amount("ETH", "10.0") 894 value_from.linked_to = portfolio.Amount("ETH", "10.0")
848 value_to = portfolio.Amount("BTC", "10.0") 895 value_to = portfolio.Amount("BTC", "10.0")
849 trade1 = portfolio.Trade(value_from, value_to, "ETH") 896 trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
850 897
851 value_from = portfolio.Amount("BTC", "0.0") 898 value_from = portfolio.Amount("BTC", "0.0")
852 value_from.linked_to = portfolio.Amount("ETH", "0.0") 899 value_from.linked_to = portfolio.Amount("ETH", "0.0")
853 value_to = portfolio.Amount("BTC", "-3.0") 900 value_to = portfolio.Amount("BTC", "-3.0")
854 trade2 = portfolio.Trade(value_from, value_to, "ETH") 901 trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
855 902
856 value_from = portfolio.Amount("USDT", "0.0") 903 value_from = portfolio.Amount("USDT", "0.0")
857 value_from.linked_to = portfolio.Amount("XVG", "0.0") 904 value_from.linked_to = portfolio.Amount("XVG", "0.0")
858 value_to = portfolio.Amount("USDT", "-50.0") 905 value_to = portfolio.Amount("USDT", "-50.0")
859 trade3 = portfolio.Trade(value_from, value_to, "XVG") 906 trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
860 907
861 portfolio.TradeStore.all = [trade1, trade2, trade3] 908 m.trades.all = [trade1, trade2, trade3]
862 balance1 = portfolio.Balance("BTC", { "margin_free": "0" }) 909 balance1 = portfolio.Balance("BTC", { "margin_free": "0" })
863 balance2 = portfolio.Balance("USDT", { "margin_free": "100" }) 910 balance2 = portfolio.Balance("USDT", { "margin_free": "100" })
864 balance3 = portfolio.Balance("ETC", { "margin_free": "10" }) 911 balance3 = portfolio.Balance("ETC", { "margin_free": "10" })
865 portfolio.BalanceStore.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3} 912 m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
866
867 market = mock.Mock()
868 913
869 helper.move_balances(market, debug=debug) 914 m.move_balances()
870 915
871 fetch_balances.assert_called_with(market) 916 fetch_balances.assert_called_with()
872 report_store.log_move_balances.assert_called_once() 917 m.report.log_move_balances.assert_called_once()
873 918
874 if debug: 919 if debug:
875 report_store.log_debug_action.assert_called() 920 m.report.log_debug_action.assert_called()
876 self.assertEqual(3, report_store.log_debug_action.call_count) 921 self.assertEqual(3, m.report.log_debug_action.call_count)
877 else: 922 else:
878 market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin") 923 self.ccxt.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
879 market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange") 924 self.ccxt.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
880 market.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange") 925 self.ccxt.transfer_balance.assert_any_call("ETC", 10, "margin", "exchange")
881 926
882 @mock.patch.object(helper, "prepare_trades")
883 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
884 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
885 @mock.patch.object(portfolio.ReportStore, "log_stage")
886 def test_print_orders(self, log_stage, fetch_balances, prepare_orders, prepare_trades):
887 market = mock.Mock()
888 portfolio.BalanceStore.all = {
889 "BTC": portfolio.Balance("BTC", {
890 "total": "0.65",
891 "exchange_total":"0.65",
892 "exchange_free": "0.35",
893 "exchange_used": "0.30"}),
894 "ETH": portfolio.Balance("ETH", {
895 "total": 3,
896 "exchange_total": 3,
897 "exchange_free": 3,
898 "exchange_used": 0}),
899 }
900
901 helper.print_orders(market)
902 fetch_balances.assert_called_with(market, tag="print_orders")
903 prepare_trades.assert_called_with(market, base_currency="BTC",
904 compute_value="average", debug=True)
905 prepare_orders.assert_called_with(compute_value="average")
906 log_stage.assert_called_with("print_orders")
907
908 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
909 @mock.patch.object(portfolio.BalanceStore, "in_currency")
910 @mock.patch.object(helper.ReportStore, "print_log")
911 def test_print_balances(self, print_log, in_currency, fetch_balances):
912 market = mock.Mock()
913 portfolio.BalanceStore.all = {
914 "BTC": portfolio.Balance("BTC", {
915 "total": "0.65",
916 "exchange_total":"0.65",
917 "exchange_free": "0.35",
918 "exchange_used": "0.30"}),
919 "ETH": portfolio.Balance("ETH", {
920 "total": 3,
921 "exchange_total": 3,
922 "exchange_free": 3,
923 "exchange_used": 0}),
924 }
925 in_currency.return_value = {
926 "BTC": portfolio.Amount("BTC", "0.65"),
927 "ETH": portfolio.Amount("BTC", "0.3"),
928 }
929 helper.print_balances(market)
930 fetch_balances.assert_called_with(market)
931 print_log.assert_has_calls([
932 mock.call("total:"),
933 mock.call(portfolio.Amount("BTC", "0.95")),
934 ])
935
936 @mock.patch.object(helper, "prepare_trades")
937 @mock.patch.object(helper, "follow_orders")
938 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
939 @mock.patch.object(portfolio.TradeStore, "run_orders")
940 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
941 @mock.patch.object(portfolio.ReportStore, "log_stage")
942 def test_process_sell_needed__1_sell(self, log_stage,
943 fetch_balances, run_orders, prepare_orders, follow_orders,
944 prepare_trades):
945 market = mock.Mock()
946 portfolio.BalanceStore.all = {
947 "BTC": portfolio.Balance("BTC", {
948 "total": "0.65",
949 "exchange_total":"0.65",
950 "exchange_free": "0.35",
951 "exchange_used": "0.30"}),
952 "ETH": portfolio.Balance("ETH", {
953 "total": 3,
954 "exchange_total": 3,
955 "exchange_free": 3,
956 "exchange_used": 0}),
957 }
958 helper.process_sell_needed__1_sell(market)
959 fetch_balances.assert_has_calls([
960 mock.call(market, tag="process_sell_needed__1_sell_begin"),
961 mock.call(market, tag="process_sell_needed__1_sell_end"),
962 ])
963 prepare_trades.assert_called_with(market, base_currency="BTC",
964 liquidity="medium", debug=False)
965 prepare_orders.assert_called_with(compute_value="average",
966 only="dispose")
967 run_orders.assert_called()
968 follow_orders.assert_called()
969 log_stage.assert_called_with("process_sell_needed__1_sell_end")
970
971 @mock.patch.object(helper, "update_trades")
972 @mock.patch.object(helper, "follow_orders")
973 @mock.patch.object(helper, "move_balances")
974 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
975 @mock.patch.object(portfolio.TradeStore, "run_orders")
976 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
977 @mock.patch.object(portfolio.ReportStore, "log_stage")
978 def test_process_sell_needed__2_buy(self, log_stage, fetch_balances,
979 run_orders, prepare_orders, move_balances, follow_orders,
980 update_trades):
981 market = mock.Mock()
982 portfolio.BalanceStore.all = {
983 "BTC": portfolio.Balance("BTC", {
984 "total": "0.65",
985 "exchange_total":"0.65",
986 "exchange_free": "0.35",
987 "exchange_used": "0.30"}),
988 "ETH": portfolio.Balance("ETH", {
989 "total": 3,
990 "exchange_total": 3,
991 "exchange_free": 3,
992 "exchange_used": 0}),
993 }
994 helper.process_sell_needed__2_buy(market)
995 fetch_balances.assert_has_calls([
996 mock.call(market, tag="process_sell_needed__2_buy_begin"),
997 mock.call(market, tag="process_sell_needed__2_buy_end"),
998 ])
999 update_trades.assert_called_with(market, base_currency="BTC",
1000 debug=False, liquidity="medium", only="acquire")
1001 prepare_orders.assert_called_with(compute_value="average",
1002 only="acquire")
1003 move_balances.assert_called_with(market, debug=False)
1004 run_orders.assert_called()
1005 follow_orders.assert_called()
1006 log_stage.assert_called_with("process_sell_needed__2_buy_end")
1007
1008 @mock.patch.object(helper, "prepare_trades_to_sell_all")
1009 @mock.patch.object(helper, "follow_orders")
1010 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
1011 @mock.patch.object(portfolio.TradeStore, "run_orders")
1012 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
1013 @mock.patch.object(portfolio.ReportStore, "log_stage")
1014 def test_process_sell_all__1_sell(self, log_stage, fetch_balances,
1015 run_orders, prepare_orders, follow_orders,
1016 prepare_trades_to_sell_all):
1017 market = mock.Mock()
1018 portfolio.BalanceStore.all = {
1019 "BTC": portfolio.Balance("BTC", {
1020 "total": "0.65",
1021 "exchange_total":"0.65",
1022 "exchange_free": "0.35",
1023 "exchange_used": "0.30"}),
1024 "ETH": portfolio.Balance("ETH", {
1025 "total": 3,
1026 "exchange_total": 3,
1027 "exchange_free": 3,
1028 "exchange_used": 0}),
1029 }
1030 helper.process_sell_all__1_all_sell(market)
1031 fetch_balances.assert_has_calls([
1032 mock.call(market, tag="process_sell_all__1_all_sell_begin"),
1033 mock.call(market, tag="process_sell_all__1_all_sell_end"),
1034 ])
1035 prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
1036 debug=False)
1037 prepare_orders.assert_called_with(compute_value="average")
1038 run_orders.assert_called()
1039 follow_orders.assert_called()
1040 log_stage.assert_called_with("process_sell_all__1_all_sell_end")
1041
1042 @mock.patch.object(helper, "prepare_trades")
1043 @mock.patch.object(helper, "follow_orders")
1044 @mock.patch.object(helper, "move_balances")
1045 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
1046 @mock.patch.object(portfolio.TradeStore, "run_orders")
1047 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
1048 @mock.patch.object(portfolio.ReportStore, "log_stage")
1049 def test_process_sell_all__2_all_buy(self, log_stage,
1050 fetch_balances, run_orders, prepare_orders, move_balances,
1051 follow_orders, prepare_trades):
1052 market = mock.Mock()
1053 portfolio.BalanceStore.all = {
1054 "BTC": portfolio.Balance("BTC", {
1055 "total": "0.65",
1056 "exchange_total":"0.65",
1057 "exchange_free": "0.35",
1058 "exchange_used": "0.30"}),
1059 "ETH": portfolio.Balance("ETH", {
1060 "total": 3,
1061 "exchange_total": 3,
1062 "exchange_free": 3,
1063 "exchange_used": 0}),
1064 }
1065 helper.process_sell_all__2_all_buy(market)
1066 fetch_balances.assert_has_calls([
1067 mock.call(market, tag="process_sell_all__2_all_buy_begin"),
1068 mock.call(market, tag="process_sell_all__2_all_buy_end"),
1069 ])
1070 prepare_trades.assert_called_with(market, base_currency="BTC",
1071 liquidity="medium", debug=False)
1072 prepare_orders.assert_called_with(compute_value="average")
1073 move_balances.assert_called_with(market, debug=False)
1074 run_orders.assert_called()
1075 follow_orders.assert_called()
1076 log_stage.assert_called_with("process_sell_all__2_all_buy_end")
1077
1078 def test_reset_all(self):
1079 portfolio.BalanceStore.all = { "foo": "bar" }
1080 portfolio.ReportStore.logs.append("hey")
1081 portfolio.TradeStore.all.append("bouh")
1082
1083 helper.reset_all()
1084
1085 self.assertEqual(0, len(portfolio.BalanceStore.all))
1086 self.assertEqual(0, len(portfolio.ReportStore.logs))
1087 self.assertEqual(0, len(portfolio.TradeStore.all))
1088
1089@unittest.skipUnless("unit" in limits, "Unit skipped") 927@unittest.skipUnless("unit" in limits, "Unit skipped")
1090class TradeStoreTest(WebMockTestCase): 928class TradeStoreTest(WebMockTestCase):
1091 @mock.patch.object(portfolio.BalanceStore, "currencies") 929 def test_compute_trades(self):
1092 @mock.patch.object(portfolio.TradeStore, "trade_if_matching") 930 self.m.balances.currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
1093 @mock.patch.object(portfolio.ReportStore, "log_trades")
1094 def test_compute_trades(self, log_trades, trade_if_matching, currencies):
1095 currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
1096 931
1097 values_in_base = { 932 values_in_base = {
1098 "XMR": portfolio.Amount("BTC", D("0.9")), 933 "XMR": portfolio.Amount("BTC", D("0.9")),
@@ -1113,112 +948,121 @@ class TradeStoreTest(WebMockTestCase):
1113 (True, 4), 948 (True, 4),
1114 (True, 5) 949 (True, 5)
1115 ] 950 ]
1116 trade_if_matching.side_effect = side_effect
1117 951
1118 portfolio.TradeStore.compute_trades(values_in_base, 952 with mock.patch.object(market.TradeStore, "trade_if_matching") as trade_if_matching:
1119 new_repartition, only="only", market="market") 953 trade_store = market.TradeStore(self.m)
954 trade_if_matching.side_effect = side_effect
1120 955
1121 self.assertEqual(5, trade_if_matching.call_count) 956 trade_store.compute_trades(values_in_base,
1122 self.assertEqual(3, len(portfolio.TradeStore.all)) 957 new_repartition, only="only")
1123 self.assertEqual([1, 4, 5], portfolio.TradeStore.all) 958
1124 log_trades.assert_called_with(side_effect, "only", False) 959 self.assertEqual(5, trade_if_matching.call_count)
960 self.assertEqual(3, len(trade_store.all))
961 self.assertEqual([1, 4, 5], trade_store.all)
962 self.m.report.log_trades.assert_called_with(side_effect, "only")
1125 963
1126 def test_trade_if_matching(self): 964 def test_trade_if_matching(self):
1127 result = portfolio.TradeStore.trade_if_matching( 965
1128 portfolio.Amount("BTC", D("0")), 966 with self.subTest(only="nope"):
1129 portfolio.Amount("BTC", D("0.3")), 967 trade_store = market.TradeStore(self.m)
1130 "ETH", only="nope", market="market" 968 result = trade_store.trade_if_matching(
1131 ) 969 portfolio.Amount("BTC", D("0")),
1132 self.assertEqual(False, result[0]) 970 portfolio.Amount("BTC", D("0.3")),
1133 self.assertIsInstance(result[1], portfolio.Trade) 971 "ETH", only="nope")
1134 972 self.assertEqual(False, result[0])
1135 portfolio.TradeStore.all = [] 973 self.assertIsInstance(result[1], portfolio.Trade)
1136 result = portfolio.TradeStore.trade_if_matching( 974
1137 portfolio.Amount("BTC", D("0")), 975 with self.subTest(only=None):
1138 portfolio.Amount("BTC", D("0.3")), 976 trade_store = market.TradeStore(self.m)
1139 "ETH", only=None, market="market" 977 result = trade_store.trade_if_matching(
1140 ) 978 portfolio.Amount("BTC", D("0")),
1141 self.assertEqual(True, result[0]) 979 portfolio.Amount("BTC", D("0.3")),
1142 980 "ETH", only=None)
1143 portfolio.TradeStore.all = [] 981 self.assertEqual(True, result[0])
1144 result = portfolio.TradeStore.trade_if_matching( 982
1145 portfolio.Amount("BTC", D("0")), 983 with self.subTest(only="acquire"):
1146 portfolio.Amount("BTC", D("0.3")), 984 trade_store = market.TradeStore(self.m)
1147 "ETH", only="acquire", market="market" 985 result = trade_store.trade_if_matching(
1148 ) 986 portfolio.Amount("BTC", D("0")),
1149 self.assertEqual(True, result[0]) 987 portfolio.Amount("BTC", D("0.3")),
1150 988 "ETH", only="acquire")
1151 portfolio.TradeStore.all = [] 989 self.assertEqual(True, result[0])
1152 result = portfolio.TradeStore.trade_if_matching( 990
1153 portfolio.Amount("BTC", D("0")), 991 with self.subTest(only="dispose"):
1154 portfolio.Amount("BTC", D("0.3")), 992 trade_store = market.TradeStore(self.m)
1155 "ETH", only="dispose", market="market" 993 result = trade_store.trade_if_matching(
1156 ) 994 portfolio.Amount("BTC", D("0")),
1157 self.assertEqual(False, result[0]) 995 portfolio.Amount("BTC", D("0.3")),
1158 996 "ETH", only="dispose")
1159 @mock.patch.object(portfolio.ReportStore, "log_orders") 997 self.assertEqual(False, result[0])
1160 def test_prepare_orders(self, log_orders): 998
999 def test_prepare_orders(self):
1000 trade_store = market.TradeStore(self.m)
1001
1161 trade_mock1 = mock.Mock() 1002 trade_mock1 = mock.Mock()
1162 trade_mock2 = mock.Mock() 1003 trade_mock2 = mock.Mock()
1163 1004
1164 trade_mock1.prepare_order.return_value = 1 1005 trade_mock1.prepare_order.return_value = 1
1165 trade_mock2.prepare_order.return_value = 2 1006 trade_mock2.prepare_order.return_value = 2
1166 1007
1167 portfolio.TradeStore.all.append(trade_mock1) 1008 trade_store.all.append(trade_mock1)
1168 portfolio.TradeStore.all.append(trade_mock2) 1009 trade_store.all.append(trade_mock2)
1169 1010
1170 portfolio.TradeStore.prepare_orders() 1011 trade_store.prepare_orders()
1171 trade_mock1.prepare_order.assert_called_with(compute_value="default") 1012 trade_mock1.prepare_order.assert_called_with(compute_value="default")
1172 trade_mock2.prepare_order.assert_called_with(compute_value="default") 1013 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1173 log_orders.assert_called_once_with([1, 2], None, "default") 1014 self.m.report.log_orders.assert_called_once_with([1, 2], None, "default")
1174 1015
1175 log_orders.reset_mock() 1016 self.m.report.log_orders.reset_mock()
1176 1017
1177 portfolio.TradeStore.prepare_orders(compute_value="bla") 1018 trade_store.prepare_orders(compute_value="bla")
1178 trade_mock1.prepare_order.assert_called_with(compute_value="bla") 1019 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
1179 trade_mock2.prepare_order.assert_called_with(compute_value="bla") 1020 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
1180 log_orders.assert_called_once_with([1, 2], None, "bla") 1021 self.m.report.log_orders.assert_called_once_with([1, 2], None, "bla")
1181 1022
1182 trade_mock1.prepare_order.reset_mock() 1023 trade_mock1.prepare_order.reset_mock()
1183 trade_mock2.prepare_order.reset_mock() 1024 trade_mock2.prepare_order.reset_mock()
1184 log_orders.reset_mock() 1025 self.m.report.log_orders.reset_mock()
1185 1026
1186 trade_mock1.action = "foo" 1027 trade_mock1.action = "foo"
1187 trade_mock2.action = "bar" 1028 trade_mock2.action = "bar"
1188 portfolio.TradeStore.prepare_orders(only="bar") 1029 trade_store.prepare_orders(only="bar")
1189 trade_mock1.prepare_order.assert_not_called() 1030 trade_mock1.prepare_order.assert_not_called()
1190 trade_mock2.prepare_order.assert_called_with(compute_value="default") 1031 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1191 log_orders.assert_called_once_with([2], "bar", "default") 1032 self.m.report.log_orders.assert_called_once_with([2], "bar", "default")
1192 1033
1193 def test_print_all_with_order(self): 1034 def test_print_all_with_order(self):
1194 trade_mock1 = mock.Mock() 1035 trade_mock1 = mock.Mock()
1195 trade_mock2 = mock.Mock() 1036 trade_mock2 = mock.Mock()
1196 trade_mock3 = mock.Mock() 1037 trade_mock3 = mock.Mock()
1197 portfolio.TradeStore.all = [trade_mock1, trade_mock2, trade_mock3] 1038 trade_store = market.TradeStore(self.m)
1039 trade_store.all = [trade_mock1, trade_mock2, trade_mock3]
1198 1040
1199 portfolio.TradeStore.print_all_with_order() 1041 trade_store.print_all_with_order()
1200 1042
1201 trade_mock1.print_with_order.assert_called() 1043 trade_mock1.print_with_order.assert_called()
1202 trade_mock2.print_with_order.assert_called() 1044 trade_mock2.print_with_order.assert_called()
1203 trade_mock3.print_with_order.assert_called() 1045 trade_mock3.print_with_order.assert_called()
1204 1046
1205 @mock.patch.object(portfolio.ReportStore, "log_stage") 1047 def test_run_orders(self):
1206 @mock.patch.object(portfolio.ReportStore, "log_orders") 1048 with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
1207 @mock.patch.object(portfolio.TradeStore, "all_orders") 1049 order_mock1 = mock.Mock()
1208 def test_run_orders(self, all_orders, log_orders, log_stage): 1050 order_mock2 = mock.Mock()
1209 order_mock1 = mock.Mock() 1051 order_mock3 = mock.Mock()
1210 order_mock2 = mock.Mock() 1052 trade_store = market.TradeStore(self.m)
1211 order_mock3 = mock.Mock() 1053
1212 all_orders.return_value = [order_mock1, order_mock2, order_mock3] 1054 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1213 portfolio.TradeStore.run_orders() 1055
1214 all_orders.assert_called_with(state="pending") 1056 trade_store.run_orders()
1057
1058 all_orders.assert_called_with(state="pending")
1215 1059
1216 order_mock1.run.assert_called() 1060 order_mock1.run.assert_called()
1217 order_mock2.run.assert_called() 1061 order_mock2.run.assert_called()
1218 order_mock3.run.assert_called() 1062 order_mock3.run.assert_called()
1219 1063
1220 log_stage.assert_called_with("run_orders") 1064 self.m.report.log_stage.assert_called_with("run_orders")
1221 log_orders.assert_called_with([order_mock1, order_mock2, 1065 self.m.report.log_orders.assert_called_with([order_mock1, order_mock2,
1222 order_mock3]) 1066 order_mock3])
1223 1067
1224 def test_all_orders(self): 1068 def test_all_orders(self):
@@ -1236,28 +1080,33 @@ class TradeStoreTest(WebMockTestCase):
1236 order_mock2.status = "open" 1080 order_mock2.status = "open"
1237 order_mock3.status = "open" 1081 order_mock3.status = "open"
1238 1082
1239 portfolio.TradeStore.all.append(trade_mock1) 1083 trade_store = market.TradeStore(self.m)
1240 portfolio.TradeStore.all.append(trade_mock2) 1084 trade_store.all.append(trade_mock1)
1085 trade_store.all.append(trade_mock2)
1241 1086
1242 orders = portfolio.TradeStore.all_orders() 1087 orders = trade_store.all_orders()
1243 self.assertEqual(3, len(orders)) 1088 self.assertEqual(3, len(orders))
1244 1089
1245 open_orders = portfolio.TradeStore.all_orders(state="open") 1090 open_orders = trade_store.all_orders(state="open")
1246 self.assertEqual(2, len(open_orders)) 1091 self.assertEqual(2, len(open_orders))
1247 self.assertEqual([order_mock2, order_mock3], open_orders) 1092 self.assertEqual([order_mock2, order_mock3], open_orders)
1248 1093
1249 @mock.patch.object(portfolio.TradeStore, "all_orders") 1094 def test_update_all_orders_status(self):
1250 def test_update_all_orders_status(self, all_orders): 1095 with mock.patch.object(market.TradeStore, "all_orders") as all_orders:
1251 order_mock1 = mock.Mock() 1096 order_mock1 = mock.Mock()
1252 order_mock2 = mock.Mock() 1097 order_mock2 = mock.Mock()
1253 order_mock3 = mock.Mock() 1098 order_mock3 = mock.Mock()
1254 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1255 portfolio.TradeStore.update_all_orders_status()
1256 all_orders.assert_called_with(state="open")
1257 1099
1258 order_mock1.get_status.assert_called() 1100 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
1259 order_mock2.get_status.assert_called() 1101
1260 order_mock3.get_status.assert_called() 1102 trade_store = market.TradeStore(self.m)
1103
1104 trade_store.update_all_orders_status()
1105 all_orders.assert_called_with(state="open")
1106
1107 order_mock1.get_status.assert_called()
1108 order_mock2.get_status.assert_called()
1109 order_mock3.get_status.assert_called()
1261 1110
1262 1111
1263@unittest.skipUnless("unit" in limits, "Unit skipped") 1112@unittest.skipUnless("unit" in limits, "Unit skipped")
@@ -1293,10 +1142,15 @@ class BalanceStoreTest(WebMockTestCase):
1293 }, 1142 },
1294 } 1143 }
1295 1144
1296 @mock.patch.object(helper, "get_ticker") 1145 def test_in_currency(self):
1297 @mock.patch("portfolio.ReportStore.log_tickers") 1146 self.m.get_ticker.return_value = {
1298 def test_in_currency(self, log_tickers, get_ticker): 1147 "bid": D("0.09"),
1299 portfolio.BalanceStore.all = { 1148 "ask": D("0.11"),
1149 "average": D("0.1"),
1150 }
1151
1152 balance_store = market.BalanceStore(self.m)
1153 balance_store.all = {
1300 "BTC": portfolio.Balance("BTC", { 1154 "BTC": portfolio.Balance("BTC", {
1301 "total": "0.65", 1155 "total": "0.65",
1302 "exchange_total":"0.65", 1156 "exchange_total":"0.65",
@@ -1308,61 +1162,54 @@ class BalanceStoreTest(WebMockTestCase):
1308 "exchange_free": 3, 1162 "exchange_free": 3,
1309 "exchange_used": 0}), 1163 "exchange_used": 0}),
1310 } 1164 }
1311 market = mock.Mock()
1312 get_ticker.return_value = {
1313 "bid": D("0.09"),
1314 "ask": D("0.11"),
1315 "average": D("0.1"),
1316 }
1317 1165
1318 amounts = portfolio.BalanceStore.in_currency("BTC", market) 1166 amounts = balance_store.in_currency("BTC")
1319 self.assertEqual("BTC", amounts["ETH"].currency) 1167 self.assertEqual("BTC", amounts["ETH"].currency)
1320 self.assertEqual(D("0.65"), amounts["BTC"].value) 1168 self.assertEqual(D("0.65"), amounts["BTC"].value)
1321 self.assertEqual(D("0.30"), amounts["ETH"].value) 1169 self.assertEqual(D("0.30"), amounts["ETH"].value)
1322 log_tickers.assert_called_once_with(market, amounts, "BTC", 1170 self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
1323 "average", "total") 1171 "average", "total")
1324 log_tickers.reset_mock() 1172 self.m.report.log_tickers.reset_mock()
1325 1173
1326 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid") 1174 amounts = balance_store.in_currency("BTC", compute_value="bid")
1327 self.assertEqual(D("0.65"), amounts["BTC"].value) 1175 self.assertEqual(D("0.65"), amounts["BTC"].value)
1328 self.assertEqual(D("0.27"), amounts["ETH"].value) 1176 self.assertEqual(D("0.27"), amounts["ETH"].value)
1329 log_tickers.assert_called_once_with(market, amounts, "BTC", 1177 self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
1330 "bid", "total") 1178 "bid", "total")
1331 log_tickers.reset_mock() 1179 self.m.report.log_tickers.reset_mock()
1332 1180
1333 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used") 1181 amounts = balance_store.in_currency("BTC", compute_value="bid", type="exchange_used")
1334 self.assertEqual(D("0.30"), amounts["BTC"].value) 1182 self.assertEqual(D("0.30"), amounts["BTC"].value)
1335 self.assertEqual(0, amounts["ETH"].value) 1183 self.assertEqual(0, amounts["ETH"].value)
1336 log_tickers.assert_called_once_with(market, amounts, "BTC", 1184 self.m.report.log_tickers.assert_called_once_with(amounts, "BTC",
1337 "bid", "exchange_used") 1185 "bid", "exchange_used")
1338 log_tickers.reset_mock() 1186 self.m.report.log_tickers.reset_mock()
1339 1187
1340 @mock.patch.object(portfolio.ReportStore, "log_balances") 1188 def test_fetch_balances(self):
1341 def test_fetch_balances(self, log_balances): 1189 self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance
1342 market = mock.Mock()
1343 market.fetch_all_balances.return_value = self.fetch_balance
1344 1190
1345 portfolio.BalanceStore.fetch_balances(market) 1191 balance_store = market.BalanceStore(self.m)
1346 self.assertNotIn("ETC", portfolio.BalanceStore.currencies())
1347 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.BalanceStore.currencies()))
1348 1192
1349 portfolio.BalanceStore.all["ETC"] = portfolio.Balance("ETC", { 1193 balance_store.fetch_balances()
1194 self.assertNotIn("ETC", balance_store.currencies())
1195 self.assertListEqual(["USDT", "XVG", "XMR"], list(balance_store.currencies()))
1196
1197 balance_store.all["ETC"] = portfolio.Balance("ETC", {
1350 "exchange_total": "1", "exchange_free": "0", 1198 "exchange_total": "1", "exchange_free": "0",
1351 "exchange_used": "1" }) 1199 "exchange_used": "1" })
1352 portfolio.BalanceStore.fetch_balances(market, tag="foo") 1200 balance_store.fetch_balances(tag="foo")
1353 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total) 1201 self.assertEqual(0, balance_store.all["ETC"].total)
1354 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies())) 1202 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies()))
1355 log_balances.assert_called_with(market, tag="foo") 1203 self.m.report.log_balances.assert_called_with(tag="foo")
1356 1204
1357 @mock.patch.object(portfolio.Portfolio, "repartition") 1205 @mock.patch.object(portfolio.Portfolio, "repartition")
1358 @mock.patch.object(portfolio.ReportStore, "log_balances") 1206 def test_dispatch_assets(self, repartition):
1359 @mock.patch("store.ReportStore.log_dispatch") 1207 self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance
1360 def test_dispatch_assets(self, log_dispatch, log_balances, repartition):
1361 market = mock.Mock()
1362 market.fetch_all_balances.return_value = self.fetch_balance
1363 portfolio.BalanceStore.fetch_balances(market)
1364 1208
1365 self.assertNotIn("XEM", portfolio.BalanceStore.currencies()) 1209 balance_store = market.BalanceStore(self.m)
1210 balance_store.fetch_balances()
1211
1212 self.assertNotIn("XEM", balance_store.currencies())
1366 1213
1367 repartition_hash = { 1214 repartition_hash = {
1368 "XEM": (D("0.75"), "long"), 1215 "XEM": (D("0.75"), "long"),
@@ -1371,18 +1218,20 @@ class BalanceStoreTest(WebMockTestCase):
1371 } 1218 }
1372 repartition.return_value = repartition_hash 1219 repartition.return_value = repartition_hash
1373 1220
1374 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1")) 1221 amounts = balance_store.dispatch_assets(portfolio.Amount("BTC", "11.1"))
1375 repartition.assert_called_with(liquidity="medium") 1222 repartition.assert_called_with(self.m, liquidity="medium")
1376 self.assertIn("XEM", portfolio.BalanceStore.currencies()) 1223 self.assertIn("XEM", balance_store.currencies())
1377 self.assertEqual(D("2.6"), amounts["BTC"].value) 1224 self.assertEqual(D("2.6"), amounts["BTC"].value)
1378 self.assertEqual(D("7.5"), amounts["XEM"].value) 1225 self.assertEqual(D("7.5"), amounts["XEM"].value)
1379 self.assertEqual(D("-1.0"), amounts["DASH"].value) 1226 self.assertEqual(D("-1.0"), amounts["DASH"].value)
1380 log_balances.assert_called_with(market, tag=None) 1227 self.m.report.log_balances.assert_called_with(tag=None)
1381 log_dispatch.assert_called_once_with(portfolio.Amount("BTC", 1228 self.m.report.log_dispatch.assert_called_once_with(portfolio.Amount("BTC",
1382 "11.1"), amounts, "medium", repartition_hash) 1229 "11.1"), amounts, "medium", repartition_hash)
1383 1230
1384 def test_currencies(self): 1231 def test_currencies(self):
1385 portfolio.BalanceStore.all = { 1232 balance_store = market.BalanceStore(self.m)
1233
1234 balance_store.all = {
1386 "BTC": portfolio.Balance("BTC", { 1235 "BTC": portfolio.Balance("BTC", {
1387 "total": "0.65", 1236 "total": "0.65",
1388 "exchange_total":"0.65", 1237 "exchange_total":"0.65",
@@ -1394,7 +1243,7 @@ class BalanceStoreTest(WebMockTestCase):
1394 "exchange_free": 3, 1243 "exchange_free": 3,
1395 "exchange_used": 0}), 1244 "exchange_used": 0}),
1396 } 1245 }
1397 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies())) 1246 self.assertListEqual(["BTC", "ETH"], list(balance_store.currencies()))
1398 1247
1399 def test_as_json(self): 1248 def test_as_json(self):
1400 balance_mock1 = mock.Mock() 1249 balance_mock1 = mock.Mock()
@@ -1403,12 +1252,13 @@ class BalanceStoreTest(WebMockTestCase):
1403 balance_mock2 = mock.Mock() 1252 balance_mock2 = mock.Mock()
1404 balance_mock2.as_json.return_value = 2 1253 balance_mock2.as_json.return_value = 2
1405 1254
1406 portfolio.BalanceStore.all = { 1255 balance_store = market.BalanceStore(self.m)
1256 balance_store.all = {
1407 "BTC": balance_mock1, 1257 "BTC": balance_mock1,
1408 "ETH": balance_mock2, 1258 "ETH": balance_mock2,
1409 } 1259 }
1410 1260
1411 as_json = portfolio.BalanceStore.as_json() 1261 as_json = balance_store.as_json()
1412 self.assertEqual(1, as_json["BTC"]) 1262 self.assertEqual(1, as_json["BTC"])
1413 self.assertEqual(2, as_json["ETH"]) 1263 self.assertEqual(2, as_json["ETH"])
1414 1264
@@ -1445,49 +1295,50 @@ class TradeTest(WebMockTestCase):
1445 value_from = portfolio.Amount("BTC", "1.0") 1295 value_from = portfolio.Amount("BTC", "1.0")
1446 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1296 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1447 value_to = portfolio.Amount("BTC", "1.0") 1297 value_to = portfolio.Amount("BTC", "1.0")
1448 trade = portfolio.Trade(value_from, value_to, "ETH") 1298 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1449 self.assertEqual("BTC", trade.base_currency) 1299 self.assertEqual("BTC", trade.base_currency)
1450 self.assertEqual("ETH", trade.currency) 1300 self.assertEqual("ETH", trade.currency)
1301 self.assertEqual(self.m, trade.market)
1451 1302
1452 with self.assertRaises(AssertionError): 1303 with self.assertRaises(AssertionError):
1453 portfolio.Trade(value_from, value_to, "ETC") 1304 portfolio.Trade(value_from, value_to, "ETC", self.m)
1454 with self.assertRaises(AssertionError): 1305 with self.assertRaises(AssertionError):
1455 value_from.linked_to = None 1306 value_from.linked_to = None
1456 portfolio.Trade(value_from, value_to, "ETH") 1307 portfolio.Trade(value_from, value_to, "ETH", self.m)
1457 with self.assertRaises(AssertionError): 1308 with self.assertRaises(AssertionError):
1458 value_from.currency = "ETH" 1309 value_from.currency = "ETH"
1459 portfolio.Trade(value_from, value_to, "ETH") 1310 portfolio.Trade(value_from, value_to, "ETH", self.m)
1460 1311
1461 value_from = portfolio.Amount("BTC", 0) 1312 value_from = portfolio.Amount("BTC", 0)
1462 trade = portfolio.Trade(value_from, value_to, "ETH") 1313 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1463 self.assertEqual(0, trade.value_from.linked_to) 1314 self.assertEqual(0, trade.value_from.linked_to)
1464 1315
1465 def test_action(self): 1316 def test_action(self):
1466 value_from = portfolio.Amount("BTC", "1.0") 1317 value_from = portfolio.Amount("BTC", "1.0")
1467 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1318 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1468 value_to = portfolio.Amount("BTC", "1.0") 1319 value_to = portfolio.Amount("BTC", "1.0")
1469 trade = portfolio.Trade(value_from, value_to, "ETH") 1320 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1470 1321
1471 self.assertIsNone(trade.action) 1322 self.assertIsNone(trade.action)
1472 1323
1473 value_from = portfolio.Amount("BTC", "1.0") 1324 value_from = portfolio.Amount("BTC", "1.0")
1474 value_from.linked_to = portfolio.Amount("BTC", "1.0") 1325 value_from.linked_to = portfolio.Amount("BTC", "1.0")
1475 value_to = portfolio.Amount("BTC", "2.0") 1326 value_to = portfolio.Amount("BTC", "2.0")
1476 trade = portfolio.Trade(value_from, value_to, "BTC") 1327 trade = portfolio.Trade(value_from, value_to, "BTC", self.m)
1477 1328
1478 self.assertIsNone(trade.action) 1329 self.assertIsNone(trade.action)
1479 1330
1480 value_from = portfolio.Amount("BTC", "0.5") 1331 value_from = portfolio.Amount("BTC", "0.5")
1481 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1332 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1482 value_to = portfolio.Amount("BTC", "1.0") 1333 value_to = portfolio.Amount("BTC", "1.0")
1483 trade = portfolio.Trade(value_from, value_to, "ETH") 1334 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1484 1335
1485 self.assertEqual("acquire", trade.action) 1336 self.assertEqual("acquire", trade.action)
1486 1337
1487 value_from = portfolio.Amount("BTC", "0") 1338 value_from = portfolio.Amount("BTC", "0")
1488 value_from.linked_to = portfolio.Amount("ETH", "0") 1339 value_from.linked_to = portfolio.Amount("ETH", "0")
1489 value_to = portfolio.Amount("BTC", "-1.0") 1340 value_to = portfolio.Amount("BTC", "-1.0")
1490 trade = portfolio.Trade(value_from, value_to, "ETH") 1341 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1491 1342
1492 self.assertEqual("acquire", trade.action) 1343 self.assertEqual("acquire", trade.action)
1493 1344
@@ -1495,7 +1346,7 @@ class TradeTest(WebMockTestCase):
1495 value_from = portfolio.Amount("BTC", "0.5") 1346 value_from = portfolio.Amount("BTC", "0.5")
1496 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1347 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1497 value_to = portfolio.Amount("BTC", "1.0") 1348 value_to = portfolio.Amount("BTC", "1.0")
1498 trade = portfolio.Trade(value_from, value_to, "ETH") 1349 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1499 1350
1500 self.assertEqual("buy", trade.order_action(False)) 1351 self.assertEqual("buy", trade.order_action(False))
1501 self.assertEqual("sell", trade.order_action(True)) 1352 self.assertEqual("sell", trade.order_action(True))
@@ -1503,7 +1354,7 @@ class TradeTest(WebMockTestCase):
1503 value_from = portfolio.Amount("BTC", "0") 1354 value_from = portfolio.Amount("BTC", "0")
1504 value_from.linked_to = portfolio.Amount("ETH", "0") 1355 value_from.linked_to = portfolio.Amount("ETH", "0")
1505 value_to = portfolio.Amount("BTC", "-1.0") 1356 value_to = portfolio.Amount("BTC", "-1.0")
1506 trade = portfolio.Trade(value_from, value_to, "ETH") 1357 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1507 1358
1508 self.assertEqual("sell", trade.order_action(False)) 1359 self.assertEqual("sell", trade.order_action(False))
1509 self.assertEqual("buy", trade.order_action(True)) 1360 self.assertEqual("buy", trade.order_action(True))
@@ -1512,14 +1363,14 @@ class TradeTest(WebMockTestCase):
1512 value_from = portfolio.Amount("BTC", "0.5") 1363 value_from = portfolio.Amount("BTC", "0.5")
1513 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1364 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1514 value_to = portfolio.Amount("BTC", "1.0") 1365 value_to = portfolio.Amount("BTC", "1.0")
1515 trade = portfolio.Trade(value_from, value_to, "ETH") 1366 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1516 1367
1517 self.assertEqual("long", trade.trade_type) 1368 self.assertEqual("long", trade.trade_type)
1518 1369
1519 value_from = portfolio.Amount("BTC", "0") 1370 value_from = portfolio.Amount("BTC", "0")
1520 value_from.linked_to = portfolio.Amount("ETH", "0") 1371 value_from.linked_to = portfolio.Amount("ETH", "0")
1521 value_to = portfolio.Amount("BTC", "-1.0") 1372 value_to = portfolio.Amount("BTC", "-1.0")
1522 trade = portfolio.Trade(value_from, value_to, "ETH") 1373 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1523 1374
1524 self.assertEqual("short", trade.trade_type) 1375 self.assertEqual("short", trade.trade_type)
1525 1376
@@ -1527,7 +1378,7 @@ class TradeTest(WebMockTestCase):
1527 value_from = portfolio.Amount("BTC", "0.5") 1378 value_from = portfolio.Amount("BTC", "0.5")
1528 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1379 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1529 value_to = portfolio.Amount("BTC", "1.0") 1380 value_to = portfolio.Amount("BTC", "1.0")
1530 trade = portfolio.Trade(value_from, value_to, "ETH") 1381 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1531 1382
1532 order1 = mock.Mock() 1383 order1 = mock.Mock()
1533 order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3") 1384 order1.filled_amount.return_value = portfolio.Amount("ETH", "0.3")
@@ -1549,11 +1400,10 @@ class TradeTest(WebMockTestCase):
1549 order1.filled_amount.assert_called_with(in_base_currency=True) 1400 order1.filled_amount.assert_called_with(in_base_currency=True)
1550 order2.filled_amount.assert_called_with(in_base_currency=True) 1401 order2.filled_amount.assert_called_with(in_base_currency=True)
1551 1402
1552 @mock.patch.object(helper, "get_ticker")
1553 @mock.patch.object(portfolio.Computation, "compute_value") 1403 @mock.patch.object(portfolio.Computation, "compute_value")
1554 @mock.patch.object(portfolio.Trade, "filled_amount") 1404 @mock.patch.object(portfolio.Trade, "filled_amount")
1555 @mock.patch.object(portfolio, "Order") 1405 @mock.patch.object(portfolio, "Order")
1556 def test_prepare_order(self, Order, filled_amount, compute_value, get_ticker): 1406 def test_prepare_order(self, Order, filled_amount, compute_value):
1557 Order.return_value = "Order" 1407 Order.return_value = "Order"
1558 1408
1559 with self.subTest(desc="Nothing to do"): 1409 with self.subTest(desc="Nothing to do"):
@@ -1561,7 +1411,7 @@ class TradeTest(WebMockTestCase):
1561 value_from.rate = D("0.1") 1411 value_from.rate = D("0.1")
1562 value_from.linked_to = portfolio.Amount("FOO", "100") 1412 value_from.linked_to = portfolio.Amount("FOO", "100")
1563 value_to = portfolio.Amount("BTC", "10") 1413 value_to = portfolio.Amount("BTC", "10")
1564 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1414 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1565 1415
1566 trade.prepare_order() 1416 trade.prepare_order()
1567 1417
@@ -1570,9 +1420,8 @@ class TradeTest(WebMockTestCase):
1570 self.assertEqual(0, len(trade.orders)) 1420 self.assertEqual(0, len(trade.orders))
1571 Order.assert_not_called() 1421 Order.assert_not_called()
1572 1422
1573 get_ticker.return_value = { "inverted": False } 1423 self.m.get_ticker.return_value = { "inverted": False }
1574 with self.subTest(desc="Already filled"),\ 1424 with self.subTest(desc="Already filled"):
1575 mock.patch("portfolio.ReportStore") as report_store:
1576 filled_amount.return_value = portfolio.Amount("FOO", "100") 1425 filled_amount.return_value = portfolio.Amount("FOO", "100")
1577 compute_value.return_value = D("0.125") 1426 compute_value.return_value = D("0.125")
1578 1427
@@ -1580,14 +1429,14 @@ class TradeTest(WebMockTestCase):
1580 value_from.rate = D("0.1") 1429 value_from.rate = D("0.1")
1581 value_from.linked_to = portfolio.Amount("FOO", "100") 1430 value_from.linked_to = portfolio.Amount("FOO", "100")
1582 value_to = portfolio.Amount("BTC", "0") 1431 value_to = portfolio.Amount("BTC", "0")
1583 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1432 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1584 1433
1585 trade.prepare_order() 1434 trade.prepare_order()
1586 1435
1587 filled_amount.assert_called_with(in_base_currency=False) 1436 filled_amount.assert_called_with(in_base_currency=False)
1588 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default") 1437 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
1589 self.assertEqual(0, len(trade.orders)) 1438 self.assertEqual(0, len(trade.orders))
1590 report_store.log_error.assert_called_with("prepare_order", message=mock.ANY) 1439 self.m.report.log_error.assert_called_with("prepare_order", message=mock.ANY)
1591 Order.assert_not_called() 1440 Order.assert_not_called()
1592 1441
1593 with self.subTest(action="dispose", inverted=False): 1442 with self.subTest(action="dispose", inverted=False):
@@ -1598,15 +1447,15 @@ class TradeTest(WebMockTestCase):
1598 value_from.rate = D("0.1") 1447 value_from.rate = D("0.1")
1599 value_from.linked_to = portfolio.Amount("FOO", "100") 1448 value_from.linked_to = portfolio.Amount("FOO", "100")
1600 value_to = portfolio.Amount("BTC", "1") 1449 value_to = portfolio.Amount("BTC", "1")
1601 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1450 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1602 1451
1603 trade.prepare_order() 1452 trade.prepare_order()
1604 1453
1605 filled_amount.assert_called_with(in_base_currency=False) 1454 filled_amount.assert_called_with(in_base_currency=False)
1606 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default") 1455 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
1607 self.assertEqual(1, len(trade.orders)) 1456 self.assertEqual(1, len(trade.orders))
1608 Order.assert_called_with("sell", portfolio.Amount("FOO", 30), 1457 Order.assert_called_with("sell", portfolio.Amount("FOO", 30),
1609 D("0.125"), "BTC", "long", "market", 1458 D("0.125"), "BTC", "long", self.m,
1610 trade, close_if_possible=False) 1459 trade, close_if_possible=False)
1611 1460
1612 with self.subTest(action="acquire", inverted=False): 1461 with self.subTest(action="acquire", inverted=False):
@@ -1617,16 +1466,16 @@ class TradeTest(WebMockTestCase):
1617 value_from.rate = D("0.1") 1466 value_from.rate = D("0.1")
1618 value_from.linked_to = portfolio.Amount("FOO", "10") 1467 value_from.linked_to = portfolio.Amount("FOO", "10")
1619 value_to = portfolio.Amount("BTC", "10") 1468 value_to = portfolio.Amount("BTC", "10")
1620 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1469 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1621 1470
1622 trade.prepare_order() 1471 trade.prepare_order()
1623 1472
1624 filled_amount.assert_called_with(in_base_currency=True) 1473 filled_amount.assert_called_with(in_base_currency=True)
1625 compute_value.assert_called_with(get_ticker.return_value, "buy", compute_value="default") 1474 compute_value.assert_called_with(self.m.get_ticker.return_value, "buy", compute_value="default")
1626 self.assertEqual(1, len(trade.orders)) 1475 self.assertEqual(1, len(trade.orders))
1627 1476
1628 Order.assert_called_with("buy", portfolio.Amount("FOO", 48), 1477 Order.assert_called_with("buy", portfolio.Amount("FOO", 48),
1629 D("0.125"), "BTC", "long", "market", 1478 D("0.125"), "BTC", "long", self.m,
1630 trade, close_if_possible=False) 1479 trade, close_if_possible=False)
1631 1480
1632 with self.subTest(close_if_possible=True): 1481 with self.subTest(close_if_possible=True):
@@ -1637,18 +1486,18 @@ class TradeTest(WebMockTestCase):
1637 value_from.rate = D("0.1") 1486 value_from.rate = D("0.1")
1638 value_from.linked_to = portfolio.Amount("FOO", "100") 1487 value_from.linked_to = portfolio.Amount("FOO", "100")
1639 value_to = portfolio.Amount("BTC", "0") 1488 value_to = portfolio.Amount("BTC", "0")
1640 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1489 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1641 1490
1642 trade.prepare_order() 1491 trade.prepare_order()
1643 1492
1644 filled_amount.assert_called_with(in_base_currency=False) 1493 filled_amount.assert_called_with(in_base_currency=False)
1645 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default") 1494 compute_value.assert_called_with(self.m.get_ticker.return_value, "sell", compute_value="default")
1646 self.assertEqual(1, len(trade.orders)) 1495 self.assertEqual(1, len(trade.orders))
1647 Order.assert_called_with("sell", portfolio.Amount("FOO", 100), 1496 Order.assert_called_with("sell", portfolio.Amount("FOO", 100),
1648 D("0.125"), "BTC", "long", "market", 1497 D("0.125"), "BTC", "long", self.m,
1649 trade, close_if_possible=True) 1498 trade, close_if_possible=True)
1650 1499
1651 get_ticker.return_value = { "inverted": True, "original": {} } 1500 self.m.get_ticker.return_value = { "inverted": True, "original": {} }
1652 with self.subTest(action="dispose", inverted=True): 1501 with self.subTest(action="dispose", inverted=True):
1653 filled_amount.return_value = portfolio.Amount("FOO", "300") 1502 filled_amount.return_value = portfolio.Amount("FOO", "300")
1654 compute_value.return_value = D("125") 1503 compute_value.return_value = D("125")
@@ -1657,15 +1506,15 @@ class TradeTest(WebMockTestCase):
1657 value_from.rate = D("0.01") 1506 value_from.rate = D("0.01")
1658 value_from.linked_to = portfolio.Amount("FOO", "1000") 1507 value_from.linked_to = portfolio.Amount("FOO", "1000")
1659 value_to = portfolio.Amount("BTC", "1") 1508 value_to = portfolio.Amount("BTC", "1")
1660 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1509 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1661 1510
1662 trade.prepare_order(compute_value="foo") 1511 trade.prepare_order(compute_value="foo")
1663 1512
1664 filled_amount.assert_called_with(in_base_currency=True) 1513 filled_amount.assert_called_with(in_base_currency=True)
1665 compute_value.assert_called_with(get_ticker.return_value["original"], "buy", compute_value="foo") 1514 compute_value.assert_called_with(self.m.get_ticker.return_value["original"], "buy", compute_value="foo")
1666 self.assertEqual(1, len(trade.orders)) 1515 self.assertEqual(1, len(trade.orders))
1667 Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")), 1516 Order.assert_called_with("buy", portfolio.Amount("BTC", D("4.8")),
1668 D("125"), "FOO", "long", "market", 1517 D("125"), "FOO", "long", self.m,
1669 trade, close_if_possible=False) 1518 trade, close_if_possible=False)
1670 1519
1671 with self.subTest(action="acquire", inverted=True): 1520 with self.subTest(action="acquire", inverted=True):
@@ -1676,15 +1525,15 @@ class TradeTest(WebMockTestCase):
1676 value_from.rate = D("0.01") 1525 value_from.rate = D("0.01")
1677 value_from.linked_to = portfolio.Amount("FOO", "100") 1526 value_from.linked_to = portfolio.Amount("FOO", "100")
1678 value_to = portfolio.Amount("BTC", "10") 1527 value_to = portfolio.Amount("BTC", "10")
1679 trade = portfolio.Trade(value_from, value_to, "FOO", market="market") 1528 trade = portfolio.Trade(value_from, value_to, "FOO", self.m)
1680 1529
1681 trade.prepare_order(compute_value="foo") 1530 trade.prepare_order(compute_value="foo")
1682 1531
1683 filled_amount.assert_called_with(in_base_currency=False) 1532 filled_amount.assert_called_with(in_base_currency=False)
1684 compute_value.assert_called_with(get_ticker.return_value["original"], "sell", compute_value="foo") 1533 compute_value.assert_called_with(self.m.get_ticker.return_value["original"], "sell", compute_value="foo")
1685 self.assertEqual(1, len(trade.orders)) 1534 self.assertEqual(1, len(trade.orders))
1686 Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")), 1535 Order.assert_called_with("sell", portfolio.Amount("BTC", D("5")),
1687 D("125"), "FOO", "long", "market", 1536 D("125"), "FOO", "long", self.m,
1688 trade, close_if_possible=False) 1537 trade, close_if_possible=False)
1689 1538
1690 1539
@@ -1696,118 +1545,119 @@ class TradeTest(WebMockTestCase):
1696 value_from = portfolio.Amount("BTC", "0.5") 1545 value_from = portfolio.Amount("BTC", "0.5")
1697 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1546 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1698 value_to = portfolio.Amount("BTC", "1.0") 1547 value_to = portfolio.Amount("BTC", "1.0")
1699 trade = portfolio.Trade(value_from, value_to, "ETH") 1548 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1700 prepare_order.return_value = new_order_mock 1549 prepare_order.return_value = new_order_mock
1701 1550
1702 for i in [0, 1, 3, 4, 6]: 1551 for i in [0, 1, 3, 4, 6]:
1703 with self.subTest(tick=i),\ 1552 with self.subTest(tick=i):
1704 mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1705 trade.update_order(order_mock, i) 1553 trade.update_order(order_mock, i)
1706 order_mock.cancel.assert_not_called() 1554 order_mock.cancel.assert_not_called()
1707 new_order_mock.run.assert_not_called() 1555 new_order_mock.run.assert_not_called()
1708 log_order.assert_called_once_with(order_mock, i, 1556 self.m.report.log_order.assert_called_once_with(order_mock, i,
1709 update="waiting", compute_value=None, new_order=None) 1557 update="waiting", compute_value=None, new_order=None)
1710 1558
1711 order_mock.reset_mock() 1559 order_mock.reset_mock()
1712 new_order_mock.reset_mock() 1560 new_order_mock.reset_mock()
1713 trade.orders = [] 1561 trade.orders = []
1714 1562 self.m.report.log_order.reset_mock()
1715 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order: 1563
1716 trade.update_order(order_mock, 2) 1564 trade.update_order(order_mock, 2)
1717 order_mock.cancel.assert_called() 1565 order_mock.cancel.assert_called()
1718 new_order_mock.run.assert_called() 1566 new_order_mock.run.assert_called()
1719 prepare_order.assert_called() 1567 prepare_order.assert_called()
1720 log_order.assert_called() 1568 self.m.report.log_order.assert_called()
1721 self.assertEqual(2, log_order.call_count) 1569 self.assertEqual(2, self.m.report.log_order.call_count)
1722 calls = [ 1570 calls = [
1723 mock.call(order_mock, 2, update="adjusting", 1571 mock.call(order_mock, 2, update="adjusting",
1724 compute_value='lambda x, y: (x[y] + x["average"]) / 2', 1572 compute_value='lambda x, y: (x[y] + x["average"]) / 2',
1725 new_order=new_order_mock), 1573 new_order=new_order_mock),
1726 mock.call(order_mock, 2, new_order=new_order_mock), 1574 mock.call(order_mock, 2, new_order=new_order_mock),
1727 ] 1575 ]
1728 log_order.assert_has_calls(calls) 1576 self.m.report.log_order.assert_has_calls(calls)
1729 1577
1730 order_mock.reset_mock() 1578 order_mock.reset_mock()
1731 new_order_mock.reset_mock() 1579 new_order_mock.reset_mock()
1732 trade.orders = [] 1580 trade.orders = []
1733 1581 self.m.report.log_order.reset_mock()
1734 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order: 1582
1735 trade.update_order(order_mock, 5) 1583 trade.update_order(order_mock, 5)
1736 order_mock.cancel.assert_called() 1584 order_mock.cancel.assert_called()
1737 new_order_mock.run.assert_called() 1585 new_order_mock.run.assert_called()
1738 prepare_order.assert_called() 1586 prepare_order.assert_called()
1739 self.assertEqual(2, log_order.call_count) 1587 self.assertEqual(2, self.m.report.log_order.call_count)
1740 log_order.assert_called() 1588 self.m.report.log_order.assert_called()
1741 calls = [ 1589 calls = [
1742 mock.call(order_mock, 5, update="adjusting", 1590 mock.call(order_mock, 5, update="adjusting",
1743 compute_value='lambda x, y: (x[y]*2 + x["average"]) / 3', 1591 compute_value='lambda x, y: (x[y]*2 + x["average"]) / 3',
1744 new_order=new_order_mock), 1592 new_order=new_order_mock),
1745 mock.call(order_mock, 5, new_order=new_order_mock), 1593 mock.call(order_mock, 5, new_order=new_order_mock),
1746 ] 1594 ]
1747 log_order.assert_has_calls(calls) 1595 self.m.report.log_order.assert_has_calls(calls)
1748 1596
1749 order_mock.reset_mock() 1597 order_mock.reset_mock()
1750 new_order_mock.reset_mock() 1598 new_order_mock.reset_mock()
1751 trade.orders = [] 1599 trade.orders = []
1752 1600 self.m.report.log_order.reset_mock()
1753 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order: 1601
1754 trade.update_order(order_mock, 7) 1602 trade.update_order(order_mock, 7)
1755 order_mock.cancel.assert_called() 1603 order_mock.cancel.assert_called()
1756 new_order_mock.run.assert_called() 1604 new_order_mock.run.assert_called()
1757 prepare_order.assert_called_with(compute_value="default") 1605 prepare_order.assert_called_with(compute_value="default")
1758 log_order.assert_called() 1606 self.m.report.log_order.assert_called()
1759 self.assertEqual(2, log_order.call_count) 1607 self.assertEqual(2, self.m.report.log_order.call_count)
1760 calls = [ 1608 calls = [
1761 mock.call(order_mock, 7, update="market_fallback", 1609 mock.call(order_mock, 7, update="market_fallback",
1762 compute_value='default', 1610 compute_value='default',
1763 new_order=new_order_mock), 1611 new_order=new_order_mock),
1764 mock.call(order_mock, 7, new_order=new_order_mock), 1612 mock.call(order_mock, 7, new_order=new_order_mock),
1765 ] 1613 ]
1766 log_order.assert_has_calls(calls) 1614 self.m.report.log_order.assert_has_calls(calls)
1767 1615
1768 order_mock.reset_mock() 1616 order_mock.reset_mock()
1769 new_order_mock.reset_mock() 1617 new_order_mock.reset_mock()
1770 trade.orders = [] 1618 trade.orders = []
1619 self.m.report.log_order.reset_mock()
1771 1620
1772 for i in [10, 13, 16]: 1621 for i in [10, 13, 16]:
1773 with self.subTest(tick=i), mock.patch.object(portfolio.ReportStore, "log_order") as log_order: 1622 with self.subTest(tick=i):
1774 trade.update_order(order_mock, i) 1623 trade.update_order(order_mock, i)
1775 order_mock.cancel.assert_called() 1624 order_mock.cancel.assert_called()
1776 new_order_mock.run.assert_called() 1625 new_order_mock.run.assert_called()
1777 prepare_order.assert_called_with(compute_value="default") 1626 prepare_order.assert_called_with(compute_value="default")
1778 log_order.assert_called() 1627 self.m.report.log_order.assert_called()
1779 self.assertEqual(2, log_order.call_count) 1628 self.assertEqual(2, self.m.report.log_order.call_count)
1780 calls = [ 1629 calls = [
1781 mock.call(order_mock, i, update="market_adjust", 1630 mock.call(order_mock, i, update="market_adjust",
1782 compute_value='default', 1631 compute_value='default',
1783 new_order=new_order_mock), 1632 new_order=new_order_mock),
1784 mock.call(order_mock, i, new_order=new_order_mock), 1633 mock.call(order_mock, i, new_order=new_order_mock),
1785 ] 1634 ]
1786 log_order.assert_has_calls(calls) 1635 self.m.report.log_order.assert_has_calls(calls)
1787 1636
1788 order_mock.reset_mock() 1637 order_mock.reset_mock()
1789 new_order_mock.reset_mock() 1638 new_order_mock.reset_mock()
1790 trade.orders = [] 1639 trade.orders = []
1640 self.m.report.log_order.reset_mock()
1791 1641
1792 for i in [8, 9, 11, 12]: 1642 for i in [8, 9, 11, 12]:
1793 with self.subTest(tick=i), mock.patch.object(portfolio.ReportStore, "log_order") as log_order: 1643 with self.subTest(tick=i):
1794 trade.update_order(order_mock, i) 1644 trade.update_order(order_mock, i)
1795 order_mock.cancel.assert_not_called() 1645 order_mock.cancel.assert_not_called()
1796 new_order_mock.run.assert_not_called() 1646 new_order_mock.run.assert_not_called()
1797 log_order.assert_called_once_with(order_mock, i, update="waiting", 1647 self.m.report.log_order.assert_called_once_with(order_mock, i, update="waiting",
1798 compute_value=None, new_order=None) 1648 compute_value=None, new_order=None)
1799 1649
1800 order_mock.reset_mock() 1650 order_mock.reset_mock()
1801 new_order_mock.reset_mock() 1651 new_order_mock.reset_mock()
1802 trade.orders = [] 1652 trade.orders = []
1653 self.m.report.log_order.reset_mock()
1803 1654
1804 1655
1805 @mock.patch.object(portfolio.ReportStore, "print_log") 1656 def test_print_with_order(self):
1806 def test_print_with_order(self, print_log):
1807 value_from = portfolio.Amount("BTC", "0.5") 1657 value_from = portfolio.Amount("BTC", "0.5")
1808 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1658 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1809 value_to = portfolio.Amount("BTC", "1.0") 1659 value_to = portfolio.Amount("BTC", "1.0")
1810 trade = portfolio.Trade(value_from, value_to, "ETH") 1660 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1811 1661
1812 order_mock1 = mock.Mock() 1662 order_mock1 = mock.Mock()
1813 order_mock1.__repr__ = mock.Mock() 1663 order_mock1.__repr__ = mock.Mock()
@@ -1830,8 +1680,8 @@ class TradeTest(WebMockTestCase):
1830 1680
1831 trade.print_with_order() 1681 trade.print_with_order()
1832 1682
1833 print_log.assert_called() 1683 self.m.report.print_log.assert_called()
1834 calls = print_log.mock_calls 1684 calls = self.m.report.print_log.mock_calls
1835 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0])) 1685 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0]))
1836 self.assertEqual("\tMock 1", str(calls[1][1][0])) 1686 self.assertEqual("\tMock 1", str(calls[1][1][0]))
1837 self.assertEqual("\tMock 2", str(calls[2][1][0])) 1687 self.assertEqual("\tMock 2", str(calls[2][1][0]))
@@ -1842,7 +1692,7 @@ class TradeTest(WebMockTestCase):
1842 value_from = portfolio.Amount("BTC", "0.5") 1692 value_from = portfolio.Amount("BTC", "0.5")
1843 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1693 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1844 value_to = portfolio.Amount("BTC", "1.0") 1694 value_to = portfolio.Amount("BTC", "1.0")
1845 trade = portfolio.Trade(value_from, value_to, "ETH") 1695 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1846 1696
1847 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade)) 1697 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
1848 1698
@@ -1850,7 +1700,7 @@ class TradeTest(WebMockTestCase):
1850 value_from = portfolio.Amount("BTC", "0.5") 1700 value_from = portfolio.Amount("BTC", "0.5")
1851 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1701 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1852 value_to = portfolio.Amount("BTC", "1.0") 1702 value_to = portfolio.Amount("BTC", "1.0")
1853 trade = portfolio.Trade(value_from, value_to, "ETH") 1703 trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
1854 1704
1855 as_json = trade.as_json() 1705 as_json = trade.as_json()
1856 self.assertEqual("acquire", as_json["action"]) 1706 self.assertEqual("acquire", as_json["action"])
@@ -1942,34 +1792,32 @@ class OrderTest(WebMockTestCase):
1942 self.assertTrue(order.finished) 1792 self.assertTrue(order.finished)
1943 1793
1944 @mock.patch.object(portfolio.Order, "fetch") 1794 @mock.patch.object(portfolio.Order, "fetch")
1945 @mock.patch("portfolio.ReportStore") 1795 def test_cancel(self, fetch):
1946 def test_cancel(self, report_store, fetch): 1796 self.m.debug = True
1947 market = mock.Mock()
1948 portfolio.TradeStore.debug = True
1949 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1797 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1950 D("0.1"), "BTC", "long", market, "trade") 1798 D("0.1"), "BTC", "long", self.m, "trade")
1951 order.status = "open" 1799 order.status = "open"
1952 1800
1953 order.cancel() 1801 order.cancel()
1954 market.cancel_order.assert_not_called() 1802 self.m.ccxt.cancel_order.assert_not_called()
1955 report_store.log_debug_action.assert_called_once() 1803 self.m.report.log_debug_action.assert_called_once()
1956 report_store.log_debug_action.reset_mock() 1804 self.m.report.log_debug_action.reset_mock()
1957 self.assertEqual("canceled", order.status) 1805 self.assertEqual("canceled", order.status)
1958 1806
1959 portfolio.TradeStore.debug = False 1807 self.m.debug = False
1960 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1808 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1961 D("0.1"), "BTC", "long", market, "trade") 1809 D("0.1"), "BTC", "long", self.m, "trade")
1962 order.status = "open" 1810 order.status = "open"
1963 order.id = 42 1811 order.id = 42
1964 1812
1965 order.cancel() 1813 order.cancel()
1966 market.cancel_order.assert_called_with(42) 1814 self.m.ccxt.cancel_order.assert_called_with(42)
1967 fetch.assert_called_once() 1815 fetch.assert_called_once()
1968 report_store.log_debug_action.assert_not_called() 1816 self.m.report.log_debug_action.assert_not_called()
1969 1817
1970 def test_dust_amount_remaining(self): 1818 def test_dust_amount_remaining(self):
1971 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1819 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1972 D("0.1"), "BTC", "long", "market", "trade") 1820 D("0.1"), "BTC", "long", self.m, "trade")
1973 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1)) 1821 order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
1974 self.assertFalse(order.dust_amount_remaining()) 1822 self.assertFalse(order.dust_amount_remaining())
1975 1823
@@ -1980,7 +1828,7 @@ class OrderTest(WebMockTestCase):
1980 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1)) 1828 @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
1981 def test_remaining_amount(self, filled_amount, fetch): 1829 def test_remaining_amount(self, filled_amount, fetch):
1982 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1830 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1983 D("0.1"), "BTC", "long", "market", "trade") 1831 D("0.1"), "BTC", "long", self.m, "trade")
1984 1832
1985 self.assertEqual(9, order.remaining_amount().value) 1833 self.assertEqual(9, order.remaining_amount().value)
1986 order.fetch.assert_not_called() 1834 order.fetch.assert_not_called()
@@ -1992,7 +1840,7 @@ class OrderTest(WebMockTestCase):
1992 @mock.patch.object(portfolio.Order, "fetch") 1840 @mock.patch.object(portfolio.Order, "fetch")
1993 def test_filled_amount(self, fetch): 1841 def test_filled_amount(self, fetch):
1994 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1842 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1995 D("0.1"), "BTC", "long", "market", "trade") 1843 D("0.1"), "BTC", "long", self.m, "trade")
1996 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", { 1844 order.mouvements.append(portfolio.Mouvement("ETH", "BTC", {
1997 "tradeID": 42, "type": "buy", "fee": "0.0015", 1845 "tradeID": 42, "type": "buy", "fee": "0.0015",
1998 "date": "2017-12-30 12:00:12", "rate": "0.1", 1846 "date": "2017-12-30 12:00:12", "rate": "0.1",
@@ -2011,8 +1859,7 @@ class OrderTest(WebMockTestCase):
2011 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True)) 1859 self.assertEqual(portfolio.Amount("BTC", "0.7"), order.filled_amount(in_base_currency=True))
2012 1860
2013 def test_fetch_mouvements(self): 1861 def test_fetch_mouvements(self):
2014 market = mock.Mock() 1862 self.m.ccxt.privatePostReturnOrderTrades.return_value = [
2015 market.privatePostReturnOrderTrades.return_value = [
2016 { 1863 {
2017 "tradeID": 42, "type": "buy", "fee": "0.0015", 1864 "tradeID": 42, "type": "buy", "fee": "0.0015",
2018 "date": "2017-12-30 12:00:12", "rate": "0.1", 1865 "date": "2017-12-30 12:00:12", "rate": "0.1",
@@ -2025,148 +1872,143 @@ class OrderTest(WebMockTestCase):
2025 } 1872 }
2026 ] 1873 ]
2027 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1874 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2028 D("0.1"), "BTC", "long", market, "trade") 1875 D("0.1"), "BTC", "long", self.m, "trade")
2029 order.id = 12 1876 order.id = 12
2030 order.mouvements = ["Foo", "Bar", "Baz"] 1877 order.mouvements = ["Foo", "Bar", "Baz"]
2031 1878
2032 order.fetch_mouvements() 1879 order.fetch_mouvements()
2033 1880
2034 market.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12}) 1881 self.m.ccxt.privatePostReturnOrderTrades.assert_called_with({"orderNumber": 12})
2035 self.assertEqual(2, len(order.mouvements)) 1882 self.assertEqual(2, len(order.mouvements))
2036 self.assertEqual(42, order.mouvements[0].id) 1883 self.assertEqual(42, order.mouvements[0].id)
2037 self.assertEqual(43, order.mouvements[1].id) 1884 self.assertEqual(43, order.mouvements[1].id)
2038 1885
2039 market.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError 1886 self.m.ccxt.privatePostReturnOrderTrades.side_effect = portfolio.ExchangeError
2040 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1887 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2041 D("0.1"), "BTC", "long", market, "trade") 1888 D("0.1"), "BTC", "long", self.m, "trade")
2042 order.fetch_mouvements() 1889 order.fetch_mouvements()
2043 self.assertEqual(0, len(order.mouvements)) 1890 self.assertEqual(0, len(order.mouvements))
2044 1891
2045 @mock.patch("portfolio.ReportStore") 1892 def test_mark_finished_order(self):
2046 def test_mark_finished_order(self, report_store):
2047 market = mock.Mock()
2048 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1893 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2049 D("0.1"), "BTC", "short", market, "trade", 1894 D("0.1"), "BTC", "short", self.m, "trade",
2050 close_if_possible=True) 1895 close_if_possible=True)
2051 order.status = "closed" 1896 order.status = "closed"
2052 portfolio.TradeStore.debug = False 1897 self.m.debug = False
2053 1898
2054 order.mark_finished_order() 1899 order.mark_finished_order()
2055 market.close_margin_position.assert_called_with("ETH", "BTC") 1900 self.m.ccxt.close_margin_position.assert_called_with("ETH", "BTC")
2056 market.close_margin_position.reset_mock() 1901 self.m.ccxt.close_margin_position.reset_mock()
2057 1902
2058 order.status = "open" 1903 order.status = "open"
2059 order.mark_finished_order() 1904 order.mark_finished_order()
2060 market.close_margin_position.assert_not_called() 1905 self.m.ccxt.close_margin_position.assert_not_called()
2061 1906
2062 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1907 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2063 D("0.1"), "BTC", "short", market, "trade", 1908 D("0.1"), "BTC", "short", self.m, "trade",
2064 close_if_possible=False) 1909 close_if_possible=False)
2065 order.status = "closed" 1910 order.status = "closed"
2066 order.mark_finished_order() 1911 order.mark_finished_order()
2067 market.close_margin_position.assert_not_called() 1912 self.m.ccxt.close_margin_position.assert_not_called()
2068 1913
2069 order = portfolio.Order("sell", portfolio.Amount("ETH", 10), 1914 order = portfolio.Order("sell", portfolio.Amount("ETH", 10),
2070 D("0.1"), "BTC", "short", market, "trade", 1915 D("0.1"), "BTC", "short", self.m, "trade",
2071 close_if_possible=True) 1916 close_if_possible=True)
2072 order.status = "closed" 1917 order.status = "closed"
2073 order.mark_finished_order() 1918 order.mark_finished_order()
2074 market.close_margin_position.assert_not_called() 1919 self.m.ccxt.close_margin_position.assert_not_called()
2075 1920
2076 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1921 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2077 D("0.1"), "BTC", "long", market, "trade", 1922 D("0.1"), "BTC", "long", self.m, "trade",
2078 close_if_possible=True) 1923 close_if_possible=True)
2079 order.status = "closed" 1924 order.status = "closed"
2080 order.mark_finished_order() 1925 order.mark_finished_order()
2081 market.close_margin_position.assert_not_called() 1926 self.m.ccxt.close_margin_position.assert_not_called()
2082 1927
2083 portfolio.TradeStore.debug = True 1928 self.m.debug = True
2084 1929
2085 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1930 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2086 D("0.1"), "BTC", "short", market, "trade", 1931 D("0.1"), "BTC", "short", self.m, "trade",
2087 close_if_possible=True) 1932 close_if_possible=True)
2088 order.status = "closed" 1933 order.status = "closed"
2089 1934
2090 order.mark_finished_order() 1935 order.mark_finished_order()
2091 market.close_margin_position.assert_not_called() 1936 self.m.ccxt.close_margin_position.assert_not_called()
2092 report_store.log_debug_action.assert_called_once() 1937 self.m.report.log_debug_action.assert_called_once()
2093 1938
2094 @mock.patch.object(portfolio.Order, "fetch_mouvements") 1939 @mock.patch.object(portfolio.Order, "fetch_mouvements")
2095 @mock.patch("portfolio.ReportStore") 1940 def test_fetch(self, fetch_mouvements):
2096 def test_fetch(self, report_store, fetch_mouvements):
2097 time = self.time.time() 1941 time = self.time.time()
2098 with mock.patch.object(portfolio.time, "time") as time_mock: 1942 with mock.patch.object(portfolio.time, "time") as time_mock:
2099 market = mock.Mock()
2100 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1943 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2101 D("0.1"), "BTC", "long", market, "trade") 1944 D("0.1"), "BTC", "long", self.m, "trade")
2102 order.id = 45 1945 order.id = 45
2103 with self.subTest(debug=True): 1946 with self.subTest(debug=True):
2104 portfolio.TradeStore.debug = True 1947 self.m.debug = True
2105 order.fetch() 1948 order.fetch()
2106 time_mock.assert_not_called() 1949 time_mock.assert_not_called()
2107 report_store.log_debug_action.assert_called_once() 1950 self.m.report.log_debug_action.assert_called_once()
2108 report_store.log_debug_action.reset_mock() 1951 self.m.report.log_debug_action.reset_mock()
2109 order.fetch(force=True) 1952 order.fetch(force=True)
2110 time_mock.assert_not_called() 1953 time_mock.assert_not_called()
2111 market.fetch_order.assert_not_called() 1954 self.m.ccxt.fetch_order.assert_not_called()
2112 fetch_mouvements.assert_not_called() 1955 fetch_mouvements.assert_not_called()
2113 report_store.log_debug_action.assert_called_once() 1956 self.m.report.log_debug_action.assert_called_once()
2114 report_store.log_debug_action.reset_mock() 1957 self.m.report.log_debug_action.reset_mock()
2115 self.assertIsNone(order.fetch_cache_timestamp) 1958 self.assertIsNone(order.fetch_cache_timestamp)
2116 1959
2117 with self.subTest(debug=False): 1960 with self.subTest(debug=False):
2118 portfolio.TradeStore.debug = False 1961 self.m.debug = False
2119 time_mock.return_value = time 1962 time_mock.return_value = time
2120 market.fetch_order.return_value = { 1963 self.m.ccxt.fetch_order.return_value = {
2121 "status": "foo", 1964 "status": "foo",
2122 "datetime": "timestamp" 1965 "datetime": "timestamp"
2123 } 1966 }
2124 order.fetch() 1967 order.fetch()
2125 1968
2126 market.fetch_order.assert_called_once() 1969 self.m.ccxt.fetch_order.assert_called_once()
2127 fetch_mouvements.assert_called_once() 1970 fetch_mouvements.assert_called_once()
2128 self.assertEqual("foo", order.status) 1971 self.assertEqual("foo", order.status)
2129 self.assertEqual("timestamp", order.timestamp) 1972 self.assertEqual("timestamp", order.timestamp)
2130 self.assertEqual(time, order.fetch_cache_timestamp) 1973 self.assertEqual(time, order.fetch_cache_timestamp)
2131 self.assertEqual(1, len(order.results)) 1974 self.assertEqual(1, len(order.results))
2132 1975
2133 market.fetch_order.reset_mock() 1976 self.m.ccxt.fetch_order.reset_mock()
2134 fetch_mouvements.reset_mock() 1977 fetch_mouvements.reset_mock()
2135 1978
2136 time_mock.return_value = time + 8 1979 time_mock.return_value = time + 8
2137 order.fetch() 1980 order.fetch()
2138 market.fetch_order.assert_not_called() 1981 self.m.ccxt.fetch_order.assert_not_called()
2139 fetch_mouvements.assert_not_called() 1982 fetch_mouvements.assert_not_called()
2140 1983
2141 order.fetch(force=True) 1984 order.fetch(force=True)
2142 market.fetch_order.assert_called_once() 1985 self.m.ccxt.fetch_order.assert_called_once()
2143 fetch_mouvements.assert_called_once() 1986 fetch_mouvements.assert_called_once()
2144 1987
2145 market.fetch_order.reset_mock() 1988 self.m.ccxt.fetch_order.reset_mock()
2146 fetch_mouvements.reset_mock() 1989 fetch_mouvements.reset_mock()
2147 1990
2148 time_mock.return_value = time + 19 1991 time_mock.return_value = time + 19
2149 order.fetch() 1992 order.fetch()
2150 market.fetch_order.assert_called_once() 1993 self.m.ccxt.fetch_order.assert_called_once()
2151 fetch_mouvements.assert_called_once() 1994 fetch_mouvements.assert_called_once()
2152 report_store.log_debug_action.assert_not_called() 1995 self.m.report.log_debug_action.assert_not_called()
2153 1996
2154 @mock.patch.object(portfolio.Order, "fetch") 1997 @mock.patch.object(portfolio.Order, "fetch")
2155 @mock.patch.object(portfolio.Order, "mark_finished_order") 1998 @mock.patch.object(portfolio.Order, "mark_finished_order")
2156 @mock.patch("portfolio.ReportStore") 1999 def test_get_status(self, mark_finished_order, fetch):
2157 def test_get_status(self, report_store, mark_finished_order, fetch):
2158 with self.subTest(debug=True): 2000 with self.subTest(debug=True):
2159 portfolio.TradeStore.debug = True 2001 self.m.debug = True
2160 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2002 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2161 D("0.1"), "BTC", "long", "market", "trade") 2003 D("0.1"), "BTC", "long", self.m, "trade")
2162 self.assertEqual("pending", order.get_status()) 2004 self.assertEqual("pending", order.get_status())
2163 fetch.assert_not_called() 2005 fetch.assert_not_called()
2164 report_store.log_debug_action.assert_called_once() 2006 self.m.report.log_debug_action.assert_called_once()
2165 2007
2166 with self.subTest(debug=False, finished=False): 2008 with self.subTest(debug=False, finished=False):
2167 portfolio.TradeStore.debug = False 2009 self.m.debug = False
2168 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2010 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2169 D("0.1"), "BTC", "long", "market", "trade") 2011 D("0.1"), "BTC", "long", self.m, "trade")
2170 def _fetch(order): 2012 def _fetch(order):
2171 def update_status(): 2013 def update_status():
2172 order.status = "open" 2014 order.status = "open"
@@ -2179,9 +2021,9 @@ class OrderTest(WebMockTestCase):
2179 mark_finished_order.reset_mock() 2021 mark_finished_order.reset_mock()
2180 fetch.reset_mock() 2022 fetch.reset_mock()
2181 with self.subTest(debug=False, finished=True): 2023 with self.subTest(debug=False, finished=True):
2182 portfolio.TradeStore.debug = False 2024 self.m.debug = False
2183 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2025 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2184 D("0.1"), "BTC", "long", "market", "trade") 2026 D("0.1"), "BTC", "long", self.m, "trade")
2185 def _fetch(order): 2027 def _fetch(order):
2186 def update_status(): 2028 def update_status():
2187 order.status = "closed" 2029 order.status = "closed"
@@ -2192,52 +2034,48 @@ class OrderTest(WebMockTestCase):
2192 fetch.assert_called_once() 2034 fetch.assert_called_once()
2193 2035
2194 def test_run(self): 2036 def test_run(self):
2195 market = mock.Mock() 2037 self.m.ccxt.order_precision.return_value = 4
2196 2038 with self.subTest(debug=True):
2197 market.order_precision.return_value = 4 2039 self.m.debug = True
2198 with self.subTest(debug=True),\
2199 mock.patch('portfolio.ReportStore') as report_store:
2200 portfolio.TradeStore.debug = True
2201 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2040 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2202 D("0.1"), "BTC", "long", market, "trade") 2041 D("0.1"), "BTC", "long", self.m, "trade")
2203 order.run() 2042 order.run()
2204 market.create_order.assert_not_called() 2043 self.m.ccxt.create_order.assert_not_called()
2205 report_store.log_debug_action.assert_called_with("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)") 2044 self.m.report.log_debug_action.assert_called_with("market.ccxt.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
2206 self.assertEqual("open", order.status) 2045 self.assertEqual("open", order.status)
2207 self.assertEqual(1, len(order.results)) 2046 self.assertEqual(1, len(order.results))
2208 self.assertEqual(-1, order.id) 2047 self.assertEqual(-1, order.id)
2209 2048
2210 market.create_order.reset_mock() 2049 self.m.ccxt.create_order.reset_mock()
2211 with self.subTest(debug=False): 2050 with self.subTest(debug=False):
2212 portfolio.TradeStore.debug = False 2051 self.m.debug = False
2213 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2052 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2214 D("0.1"), "BTC", "long", market, "trade") 2053 D("0.1"), "BTC", "long", self.m, "trade")
2215 market.create_order.return_value = { "id": 123 } 2054 self.m.ccxt.create_order.return_value = { "id": 123 }
2216 order.run() 2055 order.run()
2217 market.create_order.assert_called_once() 2056 self.m.ccxt.create_order.assert_called_once()
2218 self.assertEqual(1, len(order.results)) 2057 self.assertEqual(1, len(order.results))
2219 self.assertEqual("open", order.status) 2058 self.assertEqual("open", order.status)
2220 2059
2221 market.create_order.reset_mock() 2060 self.m.ccxt.create_order.reset_mock()
2222 with self.subTest(exception=True),\ 2061 with self.subTest(exception=True):
2223 mock.patch('portfolio.ReportStore') as report_store:
2224 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2062 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
2225 D("0.1"), "BTC", "long", market, "trade") 2063 D("0.1"), "BTC", "long", self.m, "trade")
2226 market.create_order.side_effect = Exception("bouh") 2064 self.m.ccxt.create_order.side_effect = Exception("bouh")
2227 order.run() 2065 order.run()
2228 market.create_order.assert_called_once() 2066 self.m.ccxt.create_order.assert_called_once()
2229 self.assertEqual(0, len(order.results)) 2067 self.assertEqual(0, len(order.results))
2230 self.assertEqual("error", order.status) 2068 self.assertEqual("error", order.status)
2231 report_store.log_error.assert_called_once() 2069 self.m.report.log_error.assert_called_once()
2232 2070
2233 market.create_order.reset_mock() 2071 self.m.ccxt.create_order.reset_mock()
2234 with self.subTest(dust_amount_exception=True),\ 2072 with self.subTest(dust_amount_exception=True),\
2235 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order: 2073 mock.patch.object(portfolio.Order, "mark_finished_order") as mark_finished_order:
2236 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001), 2074 order = portfolio.Order("buy", portfolio.Amount("ETH", 0.001),
2237 D("0.1"), "BTC", "long", market, "trade") 2075 D("0.1"), "BTC", "long", self.m, "trade")
2238 market.create_order.side_effect = portfolio.ExchangeNotAvailable 2076 self.m.ccxt.create_order.side_effect = portfolio.ExchangeNotAvailable
2239 order.run() 2077 order.run()
2240 market.create_order.assert_called_once() 2078 self.m.ccxt.create_order.assert_called_once()
2241 self.assertEqual(0, len(order.results)) 2079 self.assertEqual(0, len(order.results))
2242 self.assertEqual("closed", order.status) 2080 self.assertEqual("closed", order.status)
2243 mark_finished_order.assert_called_once() 2081 mark_finished_order.assert_called_once()
@@ -2304,61 +2142,66 @@ class MouvementTest(WebMockTestCase):
2304@unittest.skipUnless("unit" in limits, "Unit skipped") 2142@unittest.skipUnless("unit" in limits, "Unit skipped")
2305class ReportStoreTest(WebMockTestCase): 2143class ReportStoreTest(WebMockTestCase):
2306 def test_add_log(self): 2144 def test_add_log(self):
2307 portfolio.ReportStore.add_log({"foo": "bar"}) 2145 report_store = market.ReportStore(self.m)
2146 report_store.add_log({"foo": "bar"})
2308 2147
2309 self.assertEqual({"foo": "bar", "date": mock.ANY}, portfolio.ReportStore.logs[0]) 2148 self.assertEqual({"foo": "bar", "date": mock.ANY}, report_store.logs[0])
2310 2149
2311 def test_set_verbose(self): 2150 def test_set_verbose(self):
2151 report_store = market.ReportStore(self.m)
2312 with self.subTest(verbose=True): 2152 with self.subTest(verbose=True):
2313 portfolio.ReportStore.set_verbose(True) 2153 report_store.set_verbose(True)
2314 self.assertTrue(portfolio.ReportStore.verbose_print) 2154 self.assertTrue(report_store.verbose_print)
2315 2155
2316 with self.subTest(verbose=False): 2156 with self.subTest(verbose=False):
2317 portfolio.ReportStore.set_verbose(False) 2157 report_store.set_verbose(False)
2318 self.assertFalse(portfolio.ReportStore.verbose_print) 2158 self.assertFalse(report_store.verbose_print)
2319 2159
2320 def test_print_log(self): 2160 def test_print_log(self):
2161 report_store = market.ReportStore(self.m)
2321 with self.subTest(verbose=True),\ 2162 with self.subTest(verbose=True),\
2322 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 2163 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2323 portfolio.ReportStore.set_verbose(True) 2164 report_store.set_verbose(True)
2324 portfolio.ReportStore.print_log("Coucou") 2165 report_store.print_log("Coucou")
2325 portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1)) 2166 report_store.print_log(portfolio.Amount("BTC", 1))
2326 self.assertEqual(stdout_mock.getvalue(), "Coucou\n1.00000000 BTC\n") 2167 self.assertEqual(stdout_mock.getvalue(), "Coucou\n1.00000000 BTC\n")
2327 2168
2328 with self.subTest(verbose=False),\ 2169 with self.subTest(verbose=False),\
2329 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 2170 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2330 portfolio.ReportStore.set_verbose(False) 2171 report_store.set_verbose(False)
2331 portfolio.ReportStore.print_log("Coucou") 2172 report_store.print_log("Coucou")
2332 portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1)) 2173 report_store.print_log(portfolio.Amount("BTC", 1))
2333 self.assertEqual(stdout_mock.getvalue(), "") 2174 self.assertEqual(stdout_mock.getvalue(), "")
2334 2175
2335 def test_to_json(self): 2176 def test_to_json(self):
2336 portfolio.ReportStore.logs.append({"foo": "bar"}) 2177 report_store = market.ReportStore(self.m)
2337 self.assertEqual('[{"foo": "bar"}]', portfolio.ReportStore.to_json()) 2178 report_store.logs.append({"foo": "bar"})
2338 portfolio.ReportStore.logs.append({"date": portfolio.datetime(2018, 2, 24)}) 2179 self.assertEqual('[{"foo": "bar"}]', report_store.to_json())
2339 self.assertEqual('[{"foo": "bar"}, {"date": "2018-02-24T00:00:00"}]', portfolio.ReportStore.to_json()) 2180 report_store.logs.append({"date": portfolio.datetime(2018, 2, 24)})
2340 portfolio.ReportStore.logs.append({"amount": portfolio.Amount("BTC", 1)}) 2181 self.assertEqual('[{"foo": "bar"}, {"date": "2018-02-24T00:00:00"}]', report_store.to_json())
2182 report_store.logs.append({"amount": portfolio.Amount("BTC", 1)})
2341 with self.assertRaises(TypeError): 2183 with self.assertRaises(TypeError):
2342 portfolio.ReportStore.to_json() 2184 report_store.to_json()
2343 2185
2344 @mock.patch.object(portfolio.ReportStore, "print_log") 2186 @mock.patch.object(market.ReportStore, "print_log")
2345 @mock.patch.object(portfolio.ReportStore, "add_log") 2187 @mock.patch.object(market.ReportStore, "add_log")
2346 def test_log_stage(self, add_log, print_log): 2188 def test_log_stage(self, add_log, print_log):
2347 portfolio.ReportStore.log_stage("foo") 2189 report_store = market.ReportStore(self.m)
2190 report_store.log_stage("foo")
2348 print_log.assert_has_calls([ 2191 print_log.assert_has_calls([
2349 mock.call("-----------"), 2192 mock.call("-----------"),
2350 mock.call("[Stage] foo"), 2193 mock.call("[Stage] foo"),
2351 ]) 2194 ])
2352 add_log.assert_called_once_with({'type': 'stage', 'stage': 'foo'}) 2195 add_log.assert_called_once_with({'type': 'stage', 'stage': 'foo'})
2353 2196
2354 @mock.patch.object(portfolio.ReportStore, "print_log") 2197 @mock.patch.object(market.ReportStore, "print_log")
2355 @mock.patch.object(portfolio.ReportStore, "add_log") 2198 @mock.patch.object(market.ReportStore, "add_log")
2356 @mock.patch("store.BalanceStore") 2199 def test_log_balances(self, add_log, print_log):
2357 def test_log_balances(self, balance_store, add_log, print_log): 2200 report_store = market.ReportStore(self.m)
2358 balance_store.as_json.return_value = "json" 2201 self.m.balances.as_json.return_value = "json"
2359 balance_store.all = { "FOO": "bar", "BAR": "baz" } 2202 self.m.balances.all = { "FOO": "bar", "BAR": "baz" }
2360 2203
2361 portfolio.ReportStore.log_balances("market", tag="tag") 2204 report_store.log_balances(tag="tag")
2362 print_log.assert_has_calls([ 2205 print_log.assert_has_calls([
2363 mock.call("[Balance]"), 2206 mock.call("[Balance]"),
2364 mock.call("\tbar"), 2207 mock.call("\tbar"),
@@ -2370,17 +2213,17 @@ class ReportStoreTest(WebMockTestCase):
2370 'tag': 'tag' 2213 'tag': 'tag'
2371 }) 2214 })
2372 2215
2373 @mock.patch.object(portfolio.ReportStore, "print_log") 2216 @mock.patch.object(market.ReportStore, "print_log")
2374 @mock.patch.object(portfolio.ReportStore, "add_log") 2217 @mock.patch.object(market.ReportStore, "add_log")
2375 def test_log_tickers(self, add_log, print_log): 2218 def test_log_tickers(self, add_log, print_log):
2376 market = mock.Mock() 2219 report_store = market.ReportStore(self.m)
2377 amounts = { 2220 amounts = {
2378 "BTC": portfolio.Amount("BTC", 10), 2221 "BTC": portfolio.Amount("BTC", 10),
2379 "ETH": portfolio.Amount("BTC", D("0.3")) 2222 "ETH": portfolio.Amount("BTC", D("0.3"))
2380 } 2223 }
2381 amounts["ETH"].rate = D("0.1") 2224 amounts["ETH"].rate = D("0.1")
2382 2225
2383 portfolio.ReportStore.log_tickers(market, amounts, "BTC", "default", "total") 2226 report_store.log_tickers(amounts, "BTC", "default", "total")
2384 print_log.assert_not_called() 2227 print_log.assert_not_called()
2385 add_log.assert_called_once_with({ 2228 add_log.assert_called_once_with({
2386 'type': 'tickers', 2229 'type': 'tickers',
@@ -2398,15 +2241,16 @@ class ReportStoreTest(WebMockTestCase):
2398 'total': D('10.3') 2241 'total': D('10.3')
2399 }) 2242 })
2400 2243
2401 @mock.patch.object(portfolio.ReportStore, "print_log") 2244 @mock.patch.object(market.ReportStore, "print_log")
2402 @mock.patch.object(portfolio.ReportStore, "add_log") 2245 @mock.patch.object(market.ReportStore, "add_log")
2403 def test_log_dispatch(self, add_log, print_log): 2246 def test_log_dispatch(self, add_log, print_log):
2247 report_store = market.ReportStore(self.m)
2404 amount = portfolio.Amount("BTC", "10.3") 2248 amount = portfolio.Amount("BTC", "10.3")
2405 amounts = { 2249 amounts = {
2406 "BTC": portfolio.Amount("BTC", 10), 2250 "BTC": portfolio.Amount("BTC", 10),
2407 "ETH": portfolio.Amount("BTC", D("0.3")) 2251 "ETH": portfolio.Amount("BTC", D("0.3"))
2408 } 2252 }
2409 portfolio.ReportStore.log_dispatch(amount, amounts, "medium", "repartition") 2253 report_store.log_dispatch(amount, amounts, "medium", "repartition")
2410 print_log.assert_not_called() 2254 print_log.assert_not_called()
2411 add_log.assert_called_once_with({ 2255 add_log.assert_called_once_with({
2412 'type': 'dispatch', 2256 'type': 'dispatch',
@@ -2422,9 +2266,10 @@ class ReportStoreTest(WebMockTestCase):
2422 } 2266 }
2423 }) 2267 })
2424 2268
2425 @mock.patch.object(portfolio.ReportStore, "print_log") 2269 @mock.patch.object(market.ReportStore, "print_log")
2426 @mock.patch.object(portfolio.ReportStore, "add_log") 2270 @mock.patch.object(market.ReportStore, "add_log")
2427 def test_log_trades(self, add_log, print_log): 2271 def test_log_trades(self, add_log, print_log):
2272 report_store = market.ReportStore(self.m)
2428 trade_mock1 = mock.Mock() 2273 trade_mock1 = mock.Mock()
2429 trade_mock2 = mock.Mock() 2274 trade_mock2 = mock.Mock()
2430 trade_mock1.as_json.return_value = { "trade": "1" } 2275 trade_mock1.as_json.return_value = { "trade": "1" }
@@ -2434,23 +2279,24 @@ class ReportStoreTest(WebMockTestCase):
2434 (True, trade_mock1), 2279 (True, trade_mock1),
2435 (False, trade_mock2), 2280 (False, trade_mock2),
2436 ] 2281 ]
2437 portfolio.ReportStore.log_trades(matching_and_trades, "only", "debug") 2282 report_store.log_trades(matching_and_trades, "only")
2438 2283
2439 print_log.assert_not_called() 2284 print_log.assert_not_called()
2440 add_log.assert_called_with({ 2285 add_log.assert_called_with({
2441 'type': 'trades', 2286 'type': 'trades',
2442 'only': 'only', 2287 'only': 'only',
2443 'debug': 'debug', 2288 'debug': False,
2444 'trades': [ 2289 'trades': [
2445 {'trade': '1', 'skipped': False}, 2290 {'trade': '1', 'skipped': False},
2446 {'trade': '2', 'skipped': True} 2291 {'trade': '2', 'skipped': True}
2447 ] 2292 ]
2448 }) 2293 })
2449 2294
2450 @mock.patch.object(portfolio.ReportStore, "print_log") 2295 @mock.patch.object(market.ReportStore, "print_log")
2451 @mock.patch.object(portfolio.ReportStore, "add_log") 2296 @mock.patch.object(market.ReportStore, "add_log")
2452 @mock.patch.object(portfolio.TradeStore, "print_all_with_order") 2297 def test_log_orders(self, add_log, print_log):
2453 def test_log_orders(self, print_all_with_order, add_log, print_log): 2298 report_store = market.ReportStore(self.m)
2299
2454 order_mock1 = mock.Mock() 2300 order_mock1 = mock.Mock()
2455 order_mock2 = mock.Mock() 2301 order_mock2 = mock.Mock()
2456 2302
@@ -2459,11 +2305,11 @@ class ReportStoreTest(WebMockTestCase):
2459 2305
2460 orders = [order_mock1, order_mock2] 2306 orders = [order_mock1, order_mock2]
2461 2307
2462 portfolio.ReportStore.log_orders(orders, tick="tick", 2308 report_store.log_orders(orders, tick="tick",
2463 only="only", compute_value="compute_value") 2309 only="only", compute_value="compute_value")
2464 2310
2465 print_log.assert_called_once_with("[Orders]") 2311 print_log.assert_called_once_with("[Orders]")
2466 print_all_with_order.assert_called_once_with(ind="\t") 2312 self.m.trades.print_all_with_order.assert_called_once_with(ind="\t")
2467 2313
2468 add_log.assert_called_with({ 2314 add_log.assert_called_with({
2469 'type': 'orders', 2315 'type': 'orders',
@@ -2473,9 +2319,10 @@ class ReportStoreTest(WebMockTestCase):
2473 'orders': ['order1', 'order2'] 2319 'orders': ['order1', 'order2']
2474 }) 2320 })
2475 2321
2476 @mock.patch.object(portfolio.ReportStore, "print_log") 2322 @mock.patch.object(market.ReportStore, "print_log")
2477 @mock.patch.object(portfolio.ReportStore, "add_log") 2323 @mock.patch.object(market.ReportStore, "add_log")
2478 def test_log_order(self, add_log, print_log): 2324 def test_log_order(self, add_log, print_log):
2325 report_store = market.ReportStore(self.m)
2479 order_mock = mock.Mock() 2326 order_mock = mock.Mock()
2480 order_mock.as_json.return_value = "order" 2327 order_mock.as_json.return_value = "order"
2481 new_order_mock = mock.Mock() 2328 new_order_mock = mock.Mock()
@@ -2486,7 +2333,7 @@ class ReportStoreTest(WebMockTestCase):
2486 new_order_mock.__repr__.return_value = "New order Mock" 2333 new_order_mock.__repr__.return_value = "New order Mock"
2487 2334
2488 with self.subTest(finished=True): 2335 with self.subTest(finished=True):
2489 portfolio.ReportStore.log_order(order_mock, 1, finished=True) 2336 report_store.log_order(order_mock, 1, finished=True)
2490 print_log.assert_called_once_with("[Order] Finished Order Mock") 2337 print_log.assert_called_once_with("[Order] Finished Order Mock")
2491 add_log.assert_called_once_with({ 2338 add_log.assert_called_once_with({
2492 'type': 'order', 2339 'type': 'order',
@@ -2501,7 +2348,7 @@ class ReportStoreTest(WebMockTestCase):
2501 print_log.reset_mock() 2348 print_log.reset_mock()
2502 2349
2503 with self.subTest(update="waiting"): 2350 with self.subTest(update="waiting"):
2504 portfolio.ReportStore.log_order(order_mock, 1, update="waiting") 2351 report_store.log_order(order_mock, 1, update="waiting")
2505 print_log.assert_called_once_with("[Order] Order Mock, tick 1, waiting") 2352 print_log.assert_called_once_with("[Order] Order Mock, tick 1, waiting")
2506 add_log.assert_called_once_with({ 2353 add_log.assert_called_once_with({
2507 'type': 'order', 2354 'type': 'order',
@@ -2515,7 +2362,7 @@ class ReportStoreTest(WebMockTestCase):
2515 add_log.reset_mock() 2362 add_log.reset_mock()
2516 print_log.reset_mock() 2363 print_log.reset_mock()
2517 with self.subTest(update="adjusting"): 2364 with self.subTest(update="adjusting"):
2518 portfolio.ReportStore.log_order(order_mock, 3, 2365 report_store.log_order(order_mock, 3,
2519 update="adjusting", new_order=new_order_mock, 2366 update="adjusting", new_order=new_order_mock,
2520 compute_value="default") 2367 compute_value="default")
2521 print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock") 2368 print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
@@ -2531,7 +2378,7 @@ class ReportStoreTest(WebMockTestCase):
2531 add_log.reset_mock() 2378 add_log.reset_mock()
2532 print_log.reset_mock() 2379 print_log.reset_mock()
2533 with self.subTest(update="market_fallback"): 2380 with self.subTest(update="market_fallback"):
2534 portfolio.ReportStore.log_order(order_mock, 7, 2381 report_store.log_order(order_mock, 7,
2535 update="market_fallback", new_order=new_order_mock) 2382 update="market_fallback", new_order=new_order_mock)
2536 print_log.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value") 2383 print_log.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value")
2537 add_log.assert_called_once_with({ 2384 add_log.assert_called_once_with({
@@ -2546,7 +2393,7 @@ class ReportStoreTest(WebMockTestCase):
2546 add_log.reset_mock() 2393 add_log.reset_mock()
2547 print_log.reset_mock() 2394 print_log.reset_mock()
2548 with self.subTest(update="market_adjusting"): 2395 with self.subTest(update="market_adjusting"):
2549 portfolio.ReportStore.log_order(order_mock, 17, 2396 report_store.log_order(order_mock, 17,
2550 update="market_adjust", new_order=new_order_mock) 2397 update="market_adjust", new_order=new_order_mock)
2551 print_log.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock") 2398 print_log.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock")
2552 add_log.assert_called_once_with({ 2399 add_log.assert_called_once_with({
@@ -2558,9 +2405,10 @@ class ReportStoreTest(WebMockTestCase):
2558 'new_order': 'new_order' 2405 'new_order': 'new_order'
2559 }) 2406 })
2560 2407
2561 @mock.patch.object(portfolio.ReportStore, "print_log") 2408 @mock.patch.object(market.ReportStore, "print_log")
2562 @mock.patch.object(portfolio.ReportStore, "add_log") 2409 @mock.patch.object(market.ReportStore, "add_log")
2563 def test_log_move_balances(self, add_log, print_log): 2410 def test_log_move_balances(self, add_log, print_log):
2411 report_store = market.ReportStore(self.m)
2564 needed = { 2412 needed = {
2565 "BTC": portfolio.Amount("BTC", 10), 2413 "BTC": portfolio.Amount("BTC", 10),
2566 "USDT": 1 2414 "USDT": 1
@@ -2569,11 +2417,11 @@ class ReportStoreTest(WebMockTestCase):
2569 "BTC": portfolio.Amount("BTC", 3), 2417 "BTC": portfolio.Amount("BTC", 3),
2570 "USDT": -2 2418 "USDT": -2
2571 } 2419 }
2572 portfolio.ReportStore.log_move_balances(needed, moving, True) 2420 report_store.log_move_balances(needed, moving)
2573 print_log.assert_not_called() 2421 print_log.assert_not_called()
2574 add_log.assert_called_once_with({ 2422 add_log.assert_called_once_with({
2575 'type': 'move_balances', 2423 'type': 'move_balances',
2576 'debug': True, 2424 'debug': False,
2577 'needed': { 2425 'needed': {
2578 'BTC': D('10'), 2426 'BTC': D('10'),
2579 'USDT': 1 2427 'USDT': 1
@@ -2584,14 +2432,15 @@ class ReportStoreTest(WebMockTestCase):
2584 } 2432 }
2585 }) 2433 })
2586 2434
2587 @mock.patch.object(portfolio.ReportStore, "print_log") 2435 @mock.patch.object(market.ReportStore, "print_log")
2588 @mock.patch.object(portfolio.ReportStore, "add_log") 2436 @mock.patch.object(market.ReportStore, "add_log")
2589 def test_log_http_request(self, add_log, print_log): 2437 def test_log_http_request(self, add_log, print_log):
2438 report_store = market.ReportStore(self.m)
2590 response = mock.Mock() 2439 response = mock.Mock()
2591 response.status_code = 200 2440 response.status_code = 200
2592 response.text = "Hey" 2441 response.text = "Hey"
2593 2442
2594 portfolio.ReportStore.log_http_request("method", "url", "body", 2443 report_store.log_http_request("method", "url", "body",
2595 "headers", response) 2444 "headers", response)
2596 print_log.assert_not_called() 2445 print_log.assert_not_called()
2597 add_log.assert_called_once_with({ 2446 add_log.assert_called_once_with({
@@ -2604,11 +2453,12 @@ class ReportStoreTest(WebMockTestCase):
2604 'response': 'Hey' 2453 'response': 'Hey'
2605 }) 2454 })
2606 2455
2607 @mock.patch.object(portfolio.ReportStore, "print_log") 2456 @mock.patch.object(market.ReportStore, "print_log")
2608 @mock.patch.object(portfolio.ReportStore, "add_log") 2457 @mock.patch.object(market.ReportStore, "add_log")
2609 def test_log_error(self, add_log, print_log): 2458 def test_log_error(self, add_log, print_log):
2459 report_store = market.ReportStore(self.m)
2610 with self.subTest(message=None, exception=None): 2460 with self.subTest(message=None, exception=None):
2611 portfolio.ReportStore.log_error("action") 2461 report_store.log_error("action")
2612 print_log.assert_called_once_with("[Error] action") 2462 print_log.assert_called_once_with("[Error] action")
2613 add_log.assert_called_once_with({ 2463 add_log.assert_called_once_with({
2614 'type': 'error', 2464 'type': 'error',
@@ -2621,7 +2471,7 @@ class ReportStoreTest(WebMockTestCase):
2621 print_log.reset_mock() 2471 print_log.reset_mock()
2622 add_log.reset_mock() 2472 add_log.reset_mock()
2623 with self.subTest(message="Hey", exception=None): 2473 with self.subTest(message="Hey", exception=None):
2624 portfolio.ReportStore.log_error("action", message="Hey") 2474 report_store.log_error("action", message="Hey")
2625 print_log.assert_has_calls([ 2475 print_log.assert_has_calls([
2626 mock.call("[Error] action"), 2476 mock.call("[Error] action"),
2627 mock.call("\tHey") 2477 mock.call("\tHey")
@@ -2637,7 +2487,7 @@ class ReportStoreTest(WebMockTestCase):
2637 print_log.reset_mock() 2487 print_log.reset_mock()
2638 add_log.reset_mock() 2488 add_log.reset_mock()
2639 with self.subTest(message=None, exception=Exception("bouh")): 2489 with self.subTest(message=None, exception=Exception("bouh")):
2640 portfolio.ReportStore.log_error("action", exception=Exception("bouh")) 2490 report_store.log_error("action", exception=Exception("bouh"))
2641 print_log.assert_has_calls([ 2491 print_log.assert_has_calls([
2642 mock.call("[Error] action"), 2492 mock.call("[Error] action"),
2643 mock.call("\tException: bouh") 2493 mock.call("\tException: bouh")
@@ -2653,7 +2503,7 @@ class ReportStoreTest(WebMockTestCase):
2653 print_log.reset_mock() 2503 print_log.reset_mock()
2654 add_log.reset_mock() 2504 add_log.reset_mock()
2655 with self.subTest(message="Hey", exception=Exception("bouh")): 2505 with self.subTest(message="Hey", exception=Exception("bouh")):
2656 portfolio.ReportStore.log_error("action", message="Hey", exception=Exception("bouh")) 2506 report_store.log_error("action", message="Hey", exception=Exception("bouh"))
2657 print_log.assert_has_calls([ 2507 print_log.assert_has_calls([
2658 mock.call("[Error] action"), 2508 mock.call("[Error] action"),
2659 mock.call("\tException: bouh"), 2509 mock.call("\tException: bouh"),
@@ -2667,10 +2517,11 @@ class ReportStoreTest(WebMockTestCase):
2667 'message': "Hey" 2517 'message': "Hey"
2668 }) 2518 })
2669 2519
2670 @mock.patch.object(portfolio.ReportStore, "print_log") 2520 @mock.patch.object(market.ReportStore, "print_log")
2671 @mock.patch.object(portfolio.ReportStore, "add_log") 2521 @mock.patch.object(market.ReportStore, "add_log")
2672 def test_log_debug_action(self, add_log, print_log): 2522 def test_log_debug_action(self, add_log, print_log):
2673 portfolio.ReportStore.log_debug_action("Hey") 2523 report_store = market.ReportStore(self.m)
2524 report_store.log_debug_action("Hey")
2674 2525
2675 print_log.assert_called_once_with("[Debug] Hey") 2526 print_log.assert_called_once_with("[Debug] Hey")
2676 add_log.assert_called_once_with({ 2527 add_log.assert_called_once_with({
@@ -2678,12 +2529,263 @@ class ReportStoreTest(WebMockTestCase):
2678 'action': 'Hey' 2529 'action': 'Hey'
2679 }) 2530 })
2680 2531
2532@unittest.skipUnless("unit" in limits, "Unit skipped")
2533class HelperTest(WebMockTestCase):
2534 def test_main_store_report(self):
2535 file_open = mock.mock_open()
2536 with self.subTest(file=None), mock.patch("__main__.open", file_open):
2537 helper.main_store_report(None, 1, self.m)
2538 file_open.assert_not_called()
2539
2540 file_open = mock.mock_open()
2541 with self.subTest(file="present"), mock.patch("helper.open", file_open),\
2542 mock.patch.object(helper, "datetime") as time_mock:
2543 time_mock.now.return_value = datetime.datetime(2018, 2, 25)
2544 self.m.report.to_json.return_value = "json_content"
2545
2546 helper.main_store_report("present", 1, self.m)
2547
2548 file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
2549 file_open().write.assert_called_once_with("json_content")
2550 self.m.report.to_json.assert_called_once_with()
2551
2552 with self.subTest(file="error"),\
2553 mock.patch("helper.open") as file_open,\
2554 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2555 file_open.side_effect = FileNotFoundError
2556
2557 helper.main_store_report("error", 1, self.m)
2558
2559 self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
2560
2561 @mock.patch("helper.process_sell_all__1_all_sell")
2562 @mock.patch("helper.process_sell_all__2_all_buy")
2563 @mock.patch("portfolio.Portfolio.wait_for_recent")
2564 def test_main_process_market(self, wait, buy, sell):
2565 with self.subTest(before=False, after=False):
2566 helper.main_process_market("user")
2567
2568 wait.assert_not_called()
2569 buy.assert_not_called()
2570 sell.assert_not_called()
2571
2572 buy.reset_mock()
2573 wait.reset_mock()
2574 sell.reset_mock()
2575 with self.subTest(before=True, after=False):
2576 helper.main_process_market("user", before=True)
2577
2578 wait.assert_not_called()
2579 buy.assert_not_called()
2580 sell.assert_called_once_with("user")
2581
2582 buy.reset_mock()
2583 wait.reset_mock()
2584 sell.reset_mock()
2585 with self.subTest(before=False, after=True):
2586 helper.main_process_market("user", after=True)
2587
2588 wait.assert_called_once_with("user")
2589 buy.assert_called_once_with("user")
2590 sell.assert_not_called()
2591
2592 buy.reset_mock()
2593 wait.reset_mock()
2594 sell.reset_mock()
2595 with self.subTest(before=True, after=True):
2596 helper.main_process_market("user", before=True, after=True)
2597
2598 wait.assert_called_once_with("user")
2599 buy.assert_called_once_with("user")
2600 sell.assert_called_once_with("user")
2601
2602 @mock.patch.object(helper, "psycopg2")
2603 def test_fetch_markets(self, psycopg2):
2604 connect_mock = mock.Mock()
2605 cursor_mock = mock.MagicMock()
2606 cursor_mock.__iter__.return_value = ["row_1", "row_2"]
2607
2608 connect_mock.cursor.return_value = cursor_mock
2609 psycopg2.connect.return_value = connect_mock
2610
2611 rows = list(helper.main_fetch_markets({"foo": "bar"}))
2612
2613 psycopg2.connect.assert_called_once_with(foo="bar")
2614 cursor_mock.execute.assert_called_once_with("SELECT config,user_id FROM market_configs")
2615
2616 self.assertEqual(["row_1", "row_2"], rows)
2617
2618 @mock.patch.object(helper.sys, "exit")
2619 def test_main_parse_args(self, exit):
2620 with self.subTest(config="config.ini"):
2621 args = helper.main_parse_args([])
2622 self.assertEqual("config.ini", args.config)
2623 self.assertFalse(args.before)
2624 self.assertFalse(args.after)
2625 self.assertFalse(args.debug)
2626
2627 args = helper.main_parse_args(["--before", "--after", "--debug"])
2628 self.assertTrue(args.before)
2629 self.assertTrue(args.after)
2630 self.assertTrue(args.debug)
2631
2632 exit.assert_not_called()
2633
2634 with self.subTest(config="inexistant"),\
2635 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2636 args = helper.main_parse_args(["--config", "foo.bar"])
2637 exit.assert_called_once_with(1)
2638 self.assertEqual("no config file found, exiting\n", stdout_mock.getvalue())
2639
2640 @mock.patch.object(helper.sys, "exit")
2641 @mock.patch("helper.configparser")
2642 @mock.patch("helper.os")
2643 def test_main_parse_config(self, os, configparser, exit):
2644 with self.subTest(pg_config=True, report_path=None):
2645 config_mock = mock.MagicMock()
2646 configparser.ConfigParser.return_value = config_mock
2647 def config(element):
2648 return element == "postgresql"
2649
2650 config_mock.__contains__.side_effect = config
2651 config_mock.__getitem__.return_value = "pg_config"
2652
2653 result = helper.main_parse_config("configfile")
2654
2655 config_mock.read.assert_called_with("configfile")
2656
2657 self.assertEqual(["pg_config", None], result)
2658
2659 with self.subTest(pg_config=True, report_path="present"):
2660 config_mock = mock.MagicMock()
2661 configparser.ConfigParser.return_value = config_mock
2662
2663 config_mock.__contains__.return_value = True
2664 config_mock.__getitem__.side_effect = [
2665 {"report_path": "report_path"},
2666 {"report_path": "report_path"},
2667 "pg_config",
2668 ]
2669
2670 os.path.exists.return_value = False
2671 result = helper.main_parse_config("configfile")
2672
2673 config_mock.read.assert_called_with("configfile")
2674 self.assertEqual(["pg_config", "report_path"], result)
2675 os.path.exists.assert_called_once_with("report_path")
2676 os.makedirs.assert_called_once_with("report_path")
2677
2678 with self.subTest(pg_config=False),\
2679 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2680 config_mock = mock.MagicMock()
2681 configparser.ConfigParser.return_value = config_mock
2682 result = helper.main_parse_config("configfile")
2683
2684 config_mock.read.assert_called_with("configfile")
2685 exit.assert_called_once_with(1)
2686 self.assertEqual("no configuration for postgresql in config file\n", stdout_mock.getvalue())
2687
2688
2689 def test_print_orders(self):
2690 helper.print_orders(self.m)
2691
2692 self.m.report.log_stage.assert_called_with("print_orders")
2693 self.m.balances.fetch_balances.assert_called_with(tag="print_orders")
2694 self.m.prepare_trades.assert_called_with(base_currency="BTC",
2695 compute_value="average")
2696 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
2697
2698 def test_print_balances(self):
2699 self.m.balances.in_currency.return_value = {
2700 "BTC": portfolio.Amount("BTC", "0.65"),
2701 "ETH": portfolio.Amount("BTC", "0.3"),
2702 }
2703
2704 helper.print_balances(self.m)
2705
2706 self.m.balances.fetch_balances.assert_called_with()
2707 self.m.report.print_log.assert_has_calls([
2708 mock.call("total:"),
2709 mock.call(portfolio.Amount("BTC", "0.95")),
2710 ])
2711
2712 def test_process_sell_needed__1_sell(self):
2713 helper.process_sell_needed__1_sell(self.m)
2714
2715 self.m.balances.fetch_balances.assert_has_calls([
2716 mock.call(tag="process_sell_needed__1_sell_begin"),
2717 mock.call(tag="process_sell_needed__1_sell_end"),
2718 ])
2719 self.m.prepare_trades.assert_called_with(base_currency="BTC",
2720 liquidity="medium")
2721 self.m.trades.prepare_orders.assert_called_with(compute_value="average",
2722 only="dispose")
2723 self.m.trades.run_orders.assert_called()
2724 self.m.follow_orders.assert_called()
2725 self.m.report.log_stage.assert_has_calls([
2726 mock.call("process_sell_needed__1_sell_begin"),
2727 mock.call("process_sell_needed__1_sell_end")
2728 ])
2729
2730 def test_process_sell_needed__2_buy(self):
2731 helper.process_sell_needed__2_buy(self.m)
2732
2733 self.m.balances.fetch_balances.assert_has_calls([
2734 mock.call(tag="process_sell_needed__2_buy_begin"),
2735 mock.call(tag="process_sell_needed__2_buy_end"),
2736 ])
2737 self.m.update_trades.assert_called_with(base_currency="BTC",
2738 liquidity="medium", only="acquire")
2739 self.m.trades.prepare_orders.assert_called_with(compute_value="average",
2740 only="acquire")
2741 self.m.move_balances.assert_called_with()
2742 self.m.trades.run_orders.assert_called()
2743 self.m.follow_orders.assert_called()
2744 self.m.report.log_stage.assert_has_calls([
2745 mock.call("process_sell_needed__2_buy_begin"),
2746 mock.call("process_sell_needed__2_buy_end")
2747 ])
2748
2749 def test_process_sell_all__1_sell(self):
2750 helper.process_sell_all__1_all_sell(self.m)
2751
2752 self.m.balances.fetch_balances.assert_has_calls([
2753 mock.call(tag="process_sell_all__1_all_sell_begin"),
2754 mock.call(tag="process_sell_all__1_all_sell_end"),
2755 ])
2756 self.m.prepare_trades_to_sell_all.assert_called_with(base_currency="BTC")
2757 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
2758 self.m.trades.run_orders.assert_called()
2759 self.m.follow_orders.assert_called()
2760 self.m.report.log_stage.assert_has_calls([
2761 mock.call("process_sell_all__1_all_sell_begin"),
2762 mock.call("process_sell_all__1_all_sell_end")
2763 ])
2764
2765 def test_process_sell_all__2_all_buy(self):
2766 helper.process_sell_all__2_all_buy(self.m)
2767
2768 self.m.balances.fetch_balances.assert_has_calls([
2769 mock.call(tag="process_sell_all__2_all_buy_begin"),
2770 mock.call(tag="process_sell_all__2_all_buy_end"),
2771 ])
2772 self.m.prepare_trades.assert_called_with(base_currency="BTC",
2773 liquidity="medium")
2774 self.m.trades.prepare_orders.assert_called_with(compute_value="average")
2775 self.m.move_balances.assert_called_with()
2776 self.m.trades.run_orders.assert_called()
2777 self.m.follow_orders.assert_called()
2778 self.m.report.log_stage.assert_has_calls([
2779 mock.call("process_sell_all__2_all_buy_begin"),
2780 mock.call("process_sell_all__2_all_buy_end")
2781 ])
2782
2681@unittest.skipUnless("acceptance" in limits, "Acceptance skipped") 2783@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
2682class AcceptanceTest(WebMockTestCase): 2784class AcceptanceTest(WebMockTestCase):
2683 @unittest.expectedFailure 2785 @unittest.expectedFailure
2684 def test_success_sell_only_necessary(self): 2786 def test_success_sell_only_necessary(self):
2685 # FIXME: catch stdout 2787 # FIXME: catch stdout
2686 portfolio.ReportStore.verbose_print = False 2788 self.m.report.verbose_print = False
2687 fetch_balance = { 2789 fetch_balance = {
2688 "ETH": { 2790 "ETH": {
2689 "exchange_free": D("1.0"), 2791 "exchange_free": D("1.0"),