]>
Commit | Line | Data |
---|---|---|
1 | import configargparse | |
2 | import dbs | |
3 | import os | |
4 | import sys | |
5 | ||
6 | import market | |
7 | import portfolio | |
8 | ||
9 | __all__ = ["make_order", "get_user_market"] | |
10 | ||
11 | def make_order(market, value, currency, action="acquire", | |
12 | close_if_possible=False, base_currency="BTC", follow=True, | |
13 | compute_value="average"): | |
14 | """ | |
15 | Make an order on market | |
16 | "market": The market on which to place the order | |
17 | "value": The value in *base_currency* to acquire, | |
18 | or in *currency* to dispose. | |
19 | use negative for margin trade. | |
20 | "action": "acquire" or "dispose". | |
21 | "acquire" will buy long or sell short, | |
22 | "dispose" will sell long or buy short. | |
23 | "currency": The currency to acquire or dispose | |
24 | "base_currency": The base currency. The value is expressed in that | |
25 | currency (default: BTC) | |
26 | "follow": Whether to follow the order once run (default: True) | |
27 | "close_if_possible": Whether to try to close the position at the end | |
28 | of the trade, i.e. reach exactly 0 at the end | |
29 | (only meaningful in "dispose"). May have | |
30 | unwanted effects if the end value of the | |
31 | currency is not 0. | |
32 | "compute_value": Compute value to place the order | |
33 | """ | |
34 | market.report.log_stage("make_order_begin") | |
35 | market.balances.fetch_balances(tag="make_order_begin") | |
36 | if action == "acquire": | |
37 | trade = portfolio.Trade( | |
38 | portfolio.Amount(base_currency, 0), | |
39 | portfolio.Amount(base_currency, value), | |
40 | currency, market) | |
41 | else: | |
42 | amount = portfolio.Amount(currency, value) | |
43 | trade = portfolio.Trade( | |
44 | amount.in_currency(base_currency, market, compute_value=compute_value), | |
45 | portfolio.Amount(base_currency, 0), | |
46 | currency, market) | |
47 | market.trades.all.append(trade) | |
48 | order = trade.prepare_order( | |
49 | close_if_possible=close_if_possible, | |
50 | compute_value=compute_value) | |
51 | market.report.log_orders([order], None, compute_value) | |
52 | market.trades.run_orders() | |
53 | if follow: | |
54 | market.follow_orders() | |
55 | market.balances.fetch_balances(tag="make_order_end") | |
56 | else: | |
57 | market.report.log_stage("make_order_end_not_followed") | |
58 | return order | |
59 | market.report.log_stage("make_order_end") | |
60 | ||
61 | def get_user_market(config_path, user_id, debug=False): | |
62 | args = ["--config", config_path] | |
63 | if debug: | |
64 | args.append("--debug") | |
65 | args = parse_args(args) | |
66 | parse_config(args) | |
67 | market_id, market_config, user_id, options = list(fetch_markets(str(user_id)))[0] | |
68 | return market.Market.from_config(market_config, args, user_id=user_id, options=options) | |
69 | ||
70 | def fetch_markets(user): | |
71 | cursor = dbs.psql.cursor() | |
72 | ||
73 | if user is None: | |
74 | cursor.execute("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled'") | |
75 | else: | |
76 | cursor.execute("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled' AND user_id = %s", [user]) | |
77 | ||
78 | for row in cursor: | |
79 | options = { | |
80 | "liquidity": parse_liquidity(row[3]) | |
81 | } | |
82 | yield row[0:3] + (options,) | |
83 | ||
84 | def parse_liquidity(value): | |
85 | if value == "high-liquidity": | |
86 | return "high" | |
87 | elif value == "medium-liquidity": | |
88 | return "medium" | |
89 | else: | |
90 | return None | |
91 | ||
92 | def parse_config(args): | |
93 | if args.db_host is not None: | |
94 | dbs.connect_psql(args) | |
95 | ||
96 | if args.redis_host is not None: | |
97 | dbs.connect_redis(args) | |
98 | ||
99 | report_path = args.report_path | |
100 | ||
101 | if report_path is not None and not \ | |
102 | os.path.exists(report_path): | |
103 | os.makedirs(report_path) | |
104 | ||
105 | def parse_args(argv): | |
106 | parser = configargparse.ArgumentParser( | |
107 | description="Run the trade bot.") | |
108 | ||
109 | parser.add_argument("-c", "--config", | |
110 | default="config.ini", | |
111 | required=False, is_config_file=True, | |
112 | help="Config file to load (default: config.ini)") | |
113 | parser.add_argument("--before", | |
114 | default=False, action='store_const', const=True, | |
115 | help="Run the steps before the cryptoportfolio update") | |
116 | parser.add_argument("--after", | |
117 | default=False, action='store_const', const=True, | |
118 | help="Run the steps after the cryptoportfolio update") | |
119 | parser.add_argument("--quiet", | |
120 | default=False, action='store_const', const=True, | |
121 | help="Don't print messages") | |
122 | parser.add_argument("--debug", | |
123 | default=False, action='store_const', const=True, | |
124 | help="Run in debug mode") | |
125 | parser.add_argument("--user", | |
126 | default=None, required=False, help="Only run for that user") | |
127 | parser.add_argument("--action", | |
128 | action='append', | |
129 | help="Do a different action than trading (add several times to chain)") | |
130 | parser.add_argument("--parallel", action='store_true', default=True, dest="parallel") | |
131 | parser.add_argument("--no-parallel", action='store_false', dest="parallel") | |
132 | parser.add_argument("--report-db", action='store_true', default=True, dest="report_db", | |
133 | help="Store report to database (default)") | |
134 | parser.add_argument("--no-report-db", action='store_false', dest="report_db", | |
135 | help="Don't store report to database") | |
136 | parser.add_argument("--report-redis", action='store_true', default=False, dest="report_redis", | |
137 | help="Store report to redis") | |
138 | parser.add_argument("--no-report-redis", action='store_false', dest="report_redis", | |
139 | help="Don't store report to redis (default)") | |
140 | parser.add_argument("--report-path", required=False, | |
141 | help="Where to store the reports (default: absent, don't store)") | |
142 | parser.add_argument("--no-report-path", action='store_const', dest='report_path', const=None, | |
143 | help="Don't store the report to file (default)") | |
144 | parser.add_argument("--db-host", default="localhost", | |
145 | help="Host access to database (default: localhost)") | |
146 | parser.add_argument("--db-port", default=5432, | |
147 | help="Port access to database (default: 5432)") | |
148 | parser.add_argument("--db-user", default="cryptoportfolio", | |
149 | help="User access to database (default: cryptoportfolio)") | |
150 | parser.add_argument("--db-password", default="cryptoportfolio", | |
151 | help="Password access to database (default: cryptoportfolio)") | |
152 | parser.add_argument("--db-database", default="cryptoportfolio", | |
153 | help="Database access to database (default: cryptoportfolio)") | |
154 | parser.add_argument("--redis-host", default="localhost", | |
155 | help="Host access to database (default: localhost). Use path for socket") | |
156 | parser.add_argument("--redis-port", default=6379, | |
157 | help="Port access to redis (default: 6379)") | |
158 | parser.add_argument("--redis-database", default=0, | |
159 | help="Redis database to use (default: 0)") | |
160 | ||
161 | parsed = parser.parse_args(argv) | |
162 | if parsed.action is None: | |
163 | parsed.action = ["sell_all"] | |
164 | return parsed | |
165 | ||
166 | def process(market_config, market_id, user_id, args, options): | |
167 | try: | |
168 | market.Market\ | |
169 | .from_config(market_config, args, market_id=market_id, | |
170 | user_id=user_id, options=options)\ | |
171 | .process(args.action, before=args.before, after=args.after) | |
172 | except Exception as e: | |
173 | print("{}: {}".format(e.__class__.__name__, e)) | |
174 | ||
175 | def main(argv): | |
176 | args = parse_args(argv) | |
177 | ||
178 | parse_config(args) | |
179 | ||
180 | market.Portfolio.report.set_verbose(not args.quiet) | |
181 | ||
182 | if args.parallel: | |
183 | import threading | |
184 | market.Portfolio.start_worker() | |
185 | ||
186 | threads = [] | |
187 | def process_(*args): | |
188 | thread = threading.Thread(target=process, args=args) | |
189 | thread.start() | |
190 | threads.append(thread) | |
191 | else: | |
192 | process_ = process | |
193 | ||
194 | for market_id, market_config, user_id, options in fetch_markets(args.user): | |
195 | process_(market_config, market_id, user_id, args, options) | |
196 | ||
197 | if args.parallel: | |
198 | for thread in threads: | |
199 | thread.join() | |
200 | market.Portfolio.stop_worker() | |
201 | ||
202 | if __name__ == '__main__': # pragma: no cover | |
203 | main(sys.argv[1:]) |