1 from datetime
import datetime
10 def make_order(market
, value
, currency
, action
="acquire",
11 close_if_possible
=False, base_currency
="BTC", follow
=True,
12 compute_value
="average"):
14 Make an order on market
15 "market": The market on which to place the order
16 "value": The value in *base_currency* to acquire,
17 or in *currency* to dispose.
18 use negative for margin trade.
19 "action": "acquire" or "dispose".
20 "acquire" will buy long or sell short,
21 "dispose" will sell long or buy short.
22 "currency": The currency to acquire or dispose
23 "base_currency": The base currency. The value is expressed in that
24 currency (default: BTC)
25 "follow": Whether to follow the order once run (default: True)
26 "close_if_possible": Whether to try to close the position at the end
27 of the trade, i.e. reach exactly 0 at the end
28 (only meaningful in "dispose"). May have
29 unwanted effects if the end value of the
31 "compute_value": Compute value to place the order
33 market
.report
.log_stage("make_order_begin")
34 market
.balances
.fetch_balances(tag
="make_order_begin")
35 if action
== "acquire":
36 trade
= portfolio
.Trade(
37 portfolio
.Amount(base_currency
, 0),
38 portfolio
.Amount(base_currency
, value
),
41 amount
= portfolio
.Amount(currency
, value
)
42 trade
= portfolio
.Trade(
43 amount
.in_currency(base_currency
, market
, compute_value
=compute_value
),
44 portfolio
.Amount(base_currency
, 0),
46 market
.trades
.all
.append(trade
)
47 order
= trade
.prepare_order(
48 close_if_possible
=close_if_possible
,
49 compute_value
=compute_value
)
50 market
.report
.log_orders([order
], None, compute_value
)
51 market
.trades
.run_orders()
53 market
.follow_orders()
54 market
.balances
.fetch_balances(tag
="make_order_end")
56 market
.report
.log_stage("make_order_end_not_followed")
58 market
.report
.log_stage("make_order_end")
60 def get_user_market(config_path
, user_id
, debug
=False):
62 pg_config
, report_path
= main_parse_config(config_path
)
63 market_config
= list(main_fetch_markets(pg_config
, str(user_id
)))[0][0]
64 return market
.Market
.from_config(market_config
, debug
=debug
)
66 def main_parse_args(argv
):
67 parser
= argparse
.ArgumentParser(
68 description
="Run the trade bot")
70 parser
.add_argument("-c", "--config",
73 help="Config file to load (default: config.ini)")
74 parser
.add_argument("--before",
75 default
=False, action
='store_const', const
=True,
76 help="Run the steps before the cryptoportfolio update")
77 parser
.add_argument("--after",
78 default
=False, action
='store_const', const
=True,
79 help="Run the steps after the cryptoportfolio update")
80 parser
.add_argument("--debug",
81 default
=False, action
='store_const', const
=True,
82 help="Run in debug mode")
83 parser
.add_argument("--user",
84 default
=None, required
=False, help="Only run for that user")
85 parser
.add_argument("--action",
87 help="Do a different action than trading (add several times to chain)")
89 args
= parser
.parse_args(argv
)
91 if not os
.path
.exists(args
.config
):
92 print("no config file found, exiting")
97 def main_parse_config(config_file
):
98 config
= configparser
.ConfigParser()
99 config
.read(config_file
)
101 if "postgresql" not in config
:
102 print("no configuration for postgresql in config file")
105 if "app" in config
and "report_path" in config
["app"]:
106 report_path
= config
["app"]["report_path"]
108 if not os
.path
.exists(report_path
):
109 os
.makedirs(report_path
)
113 return [config
["postgresql"], report_path
]
115 def main_fetch_markets(pg_config
, user
):
116 connection
= psycopg2
.connect(**pg_config
)
117 cursor
= connection
.cursor()
120 cursor
.execute("SELECT config,user_id FROM market_configs")
122 cursor
.execute("SELECT config,user_id FROM market_configs WHERE user_id = %s", user
)
127 def main_process_market(user_market
, actions
, before
=False, after
=False):
128 if len(actions
or []) == 0:
130 Processor(user_market
).process("sell_all", steps
="before")
132 Processor(user_market
).process("sell_all", steps
="after")
134 for action
in actions
:
135 if action
in globals():
136 (globals()[action
])(user_market
)
138 raise NotImplementedError("Unknown action {}".format(action
))
140 def main_store_report(report_path
, user_id
, user_market
):
142 if report_path
is not None:
143 report_file
= "{}/{}_{}.json".format(report_path
, datetime
.now().isoformat(), user_id
)
144 with open(report_file
, "w") as f
:
145 f
.write(user_market
.report
.to_json())
146 except Exception as e
:
147 print("impossible to store report file: {}; {}".format(e
.__class
__.__name
__, e
))
149 def print_orders(market
, base_currency
="BTC"):
150 market
.report
.log_stage("print_orders")
151 market
.balances
.fetch_balances(tag
="print_orders")
152 market
.prepare_trades(base_currency
=base_currency
, compute_value
="average")
153 market
.trades
.prepare_orders(compute_value
="average")
155 def print_balances(market
, base_currency
="BTC"):
156 market
.report
.log_stage("print_balances")
157 market
.balances
.fetch_balances()
158 if base_currency
is not None:
159 market
.report
.print_log("total:")
160 market
.report
.print_log(sum(market
.balances
.in_currency(base_currency
).values()))
170 "wait_for_recent": {},
177 "fetch_balances": ["begin", "end"],
178 "prepare_trades": {},
179 "prepare_orders": { "only": "dispose", "compute_value": "average" }
,
189 "fetch_balances": ["begin", "end"],
190 "prepare_trades": { "only": "acquire" }
,
191 "prepare_orders": { "only": "acquire", "compute_value": "average" }
,
204 "fetch_balances": ["begin", "end"],
205 "prepare_trades": { "repartition": { "base_currency": (1, "long") }
},
206 "prepare_orders": { "compute_value": "average" }
,
216 "wait_for_recent": {},
223 "fetch_balances": ["begin", "end"],
224 "prepare_trades": {},
225 "prepare_orders": { "compute_value": "average" }
,
235 "wait_for_recent", "prepare_trades", "prepare_orders",
236 "move_balances", "run_orders", "follow_orders",
239 def __init__(self
, market
):
242 def select_steps(self
, scenario
, step
):
245 elif step
== "before" or step
== "after":
246 return list(filter(lambda x
: step
in x
and x
[step
], scenario
))
247 elif type(step
) == int:
248 return [scenario
[step
-1]]
249 elif type(step
) == str:
250 return list(filter(lambda x
: x
["name"] == step
, scenario
))
252 raise TypeError("Unknown step {}".format(step
))
254 def process(self
, scenario_name
, steps
="all", **kwargs
):
255 scenario
= self
.scenarios
[scenario_name
]
258 if type(steps
) == str or type(steps
) == int:
259 selected_steps
+= self
.select_steps(scenario
, steps
)
262 selected_steps
+= self
.select_steps(scenario
, step
)
263 for step
in selected_steps
:
264 self
.process_step(scenario_name
, step
, kwargs
)
266 def process_step(self
, scenario_name
, step
, kwargs
):
267 process_name
= "process_{}__{}_{}".format(scenario_name
, step
["number"], step
["name"])
268 self
.market
.report
.log_stage("{}_begin".format(process_name
))
269 if "begin" in step
.get("fetch_balances", []):
270 self
.market
.balances
.fetch_balances(tag
="{}_begin".format(process_name
))
272 for action
in self
.ordered_actions
:
274 self
.run_action(action
, step
[action
], kwargs
)
276 if "end" in step
.get("fetch_balances", []):
277 self
.market
.balances
.fetch_balances(tag
="{}_end".format(process_name
))
278 self
.market
.report
.log_stage("{}_end".format(process_name
))
280 def method_arguments(self
, action
):
283 if action
== "wait_for_recent":
284 method
= portfolio
.Portfolio
.wait_for_recent
285 elif action
== "prepare_trades":
286 method
= self
.market
.prepare_trades
287 elif action
== "prepare_orders":
288 method
= self
.market
.trades
.prepare_orders
289 elif action
== "move_balances":
290 method
= self
.market
.move_balances
291 elif action
== "run_orders":
292 method
= self
.market
.trades
.run_orders
293 elif action
== "follow_orders":
294 method
= self
.market
.follow_orders
295 elif action
== "close_trades":
296 method
= self
.market
.trades
.close_trades
298 signature
= inspect
.getfullargspec(method
)
299 defaults
= signature
.defaults
or []
300 kwargs
= signature
.args
[-len(defaults
):]
302 return [method
, kwargs
]
304 def parse_args(self
, action
, default_args
, kwargs
):
305 method
, allowed_arguments
= self
.method_arguments(action
)
306 args
= {k: v for k, v in {**default_args, **kwargs}
.items() if k
in allowed_arguments
}
308 if "repartition" in args
and "base_currency" in args
["repartition"]:
309 r
= args
["repartition"]
310 r
[args
.get("base_currency", "BTC")] = r
.pop("base_currency")
314 def run_action(self
, action
, default_args
, kwargs
):
315 method
, args
= self
.parse_args(action
, default_args
, kwargs
)
317 if action
== "wait_for_recent":
318 method(self
.market
, **args
)