]>
Commit | Line | Data |
---|---|---|
1 | from datetime import datetime | |
2 | import argparse | |
3 | import configparser | |
4 | import psycopg2 | |
5 | import os | |
6 | import sys | |
7 | ||
8 | import market | |
9 | import portfolio | |
10 | ||
11 | __all__ = ["make_order", "get_user_market"] | |
12 | ||
13 | def make_order(market, value, currency, action="acquire", | |
14 | close_if_possible=False, base_currency="BTC", follow=True, | |
15 | compute_value="average"): | |
16 | """ | |
17 | Make an order on market | |
18 | "market": The market on which to place the order | |
19 | "value": The value in *base_currency* to acquire, | |
20 | or in *currency* to dispose. | |
21 | use negative for margin trade. | |
22 | "action": "acquire" or "dispose". | |
23 | "acquire" will buy long or sell short, | |
24 | "dispose" will sell long or buy short. | |
25 | "currency": The currency to acquire or dispose | |
26 | "base_currency": The base currency. The value is expressed in that | |
27 | currency (default: BTC) | |
28 | "follow": Whether to follow the order once run (default: True) | |
29 | "close_if_possible": Whether to try to close the position at the end | |
30 | of the trade, i.e. reach exactly 0 at the end | |
31 | (only meaningful in "dispose"). May have | |
32 | unwanted effects if the end value of the | |
33 | currency is not 0. | |
34 | "compute_value": Compute value to place the order | |
35 | """ | |
36 | market.report.log_stage("make_order_begin") | |
37 | market.balances.fetch_balances(tag="make_order_begin") | |
38 | if action == "acquire": | |
39 | trade = portfolio.Trade( | |
40 | portfolio.Amount(base_currency, 0), | |
41 | portfolio.Amount(base_currency, value), | |
42 | currency, market) | |
43 | else: | |
44 | amount = portfolio.Amount(currency, value) | |
45 | trade = portfolio.Trade( | |
46 | amount.in_currency(base_currency, market, compute_value=compute_value), | |
47 | portfolio.Amount(base_currency, 0), | |
48 | currency, market) | |
49 | market.trades.all.append(trade) | |
50 | order = trade.prepare_order( | |
51 | close_if_possible=close_if_possible, | |
52 | compute_value=compute_value) | |
53 | market.report.log_orders([order], None, compute_value) | |
54 | market.trades.run_orders() | |
55 | if follow: | |
56 | market.follow_orders() | |
57 | market.balances.fetch_balances(tag="make_order_end") | |
58 | else: | |
59 | market.report.log_stage("make_order_end_not_followed") | |
60 | return order | |
61 | market.report.log_stage("make_order_end") | |
62 | ||
63 | def get_user_market(config_path, user_id, debug=False): | |
64 | pg_config, report_path = parse_config(config_path) | |
65 | market_config = list(fetch_markets(pg_config, str(user_id)))[0][0] | |
66 | return market.Market.from_config(market_config, debug=debug) | |
67 | ||
68 | def fetch_markets(pg_config, user): | |
69 | connection = psycopg2.connect(**pg_config) | |
70 | cursor = connection.cursor() | |
71 | ||
72 | if user is None: | |
73 | cursor.execute("SELECT config,user_id FROM market_configs") | |
74 | else: | |
75 | cursor.execute("SELECT config,user_id FROM market_configs WHERE user_id = %s", user) | |
76 | ||
77 | for row in cursor: | |
78 | yield row | |
79 | ||
80 | def parse_config(config_file): | |
81 | config = configparser.ConfigParser() | |
82 | config.read(config_file) | |
83 | ||
84 | if "postgresql" not in config: | |
85 | print("no configuration for postgresql in config file") | |
86 | sys.exit(1) | |
87 | ||
88 | if "app" in config and "report_path" in config["app"]: | |
89 | report_path = config["app"]["report_path"] | |
90 | ||
91 | if not os.path.exists(report_path): | |
92 | os.makedirs(report_path) | |
93 | else: | |
94 | report_path = None | |
95 | ||
96 | return [config["postgresql"], report_path] | |
97 | ||
98 | def parse_args(argv): | |
99 | parser = argparse.ArgumentParser( | |
100 | description="Run the trade bot") | |
101 | ||
102 | parser.add_argument("-c", "--config", | |
103 | default="config.ini", | |
104 | required=False, | |
105 | help="Config file to load (default: config.ini)") | |
106 | parser.add_argument("--before", | |
107 | default=False, action='store_const', const=True, | |
108 | help="Run the steps before the cryptoportfolio update") | |
109 | parser.add_argument("--after", | |
110 | default=False, action='store_const', const=True, | |
111 | help="Run the steps after the cryptoportfolio update") | |
112 | parser.add_argument("--debug", | |
113 | default=False, action='store_const', const=True, | |
114 | help="Run in debug mode") | |
115 | parser.add_argument("--user", | |
116 | default=None, required=False, help="Only run for that user") | |
117 | parser.add_argument("--action", | |
118 | action='append', | |
119 | help="Do a different action than trading (add several times to chain)") | |
120 | ||
121 | args = parser.parse_args(argv) | |
122 | ||
123 | if not os.path.exists(args.config): | |
124 | print("no config file found, exiting") | |
125 | sys.exit(1) | |
126 | ||
127 | return args | |
128 | ||
129 | def process(market_config, user_id, report_path, args): | |
130 | try: | |
131 | market.Market\ | |
132 | .from_config(market_config, debug=args.debug, user_id=user_id, report_path=report_path)\ | |
133 | .process(args.action, before=args.before, after=args.after) | |
134 | except Exception as e: | |
135 | print("{}: {}".format(e.__class__.__name__, e)) | |
136 | ||
137 | def main(argv): | |
138 | args = parse_args(argv) | |
139 | ||
140 | pg_config, report_path = parse_config(args.config) | |
141 | ||
142 | for market_config, user_id in fetch_markets(pg_config, args.user): | |
143 | process(market_config, user_id, report_path, args) | |
144 | ||
145 | if __name__ == '__main__': # pragma: no cover | |
146 | main(sys.argv[1:]) |