]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - market.py
Store tickers in balance log
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / market.py
index e16641c476865bc3977ceaa8f30ccce5296925ab..fc6f9f667fb320e469f7fa103d398d2c40d184b2 100644 (file)
--- a/market.py
+++ b/market.py
@@ -2,9 +2,11 @@ from ccxt import ExchangeError, NotSupported, RequestTimeout, InvalidNonce
 import ccxt_wrapper as ccxt
 import time
 import psycopg2
+import redis
 from store import *
 from cachetools.func import ttl_cache
 from datetime import datetime
+import datetime
 from retry import retry
 import portfolio
 
@@ -25,10 +27,10 @@ class Market:
         self.balances = BalanceStore(self)
         self.processor = Processor(self)
 
-        for key in ["user_id", "market_id", "pg_config"]:
+        for key in ["user_id", "market_id", "pg_config", "redis_config"]:
             setattr(self, key, kwargs.get(key, None))
 
-        self.report.log_market(self.args, self.user_id, self.market_id)
+        self.report.log_market(self.args)
 
     @classmethod
     def from_config(cls, config, args, **kwargs):
@@ -40,11 +42,13 @@ class Market:
 
     def store_report(self):
         self.report.merge(Portfolio.report)
-        date = datetime.now()
+        date = datetime.datetime.now()
         if self.args.report_path is not None:
             self.store_file_report(date)
         if self.pg_config is not None and self.args.report_db:
             self.store_database_report(date)
+        if self.redis_config is not None and self.args.report_redis:
+            self.store_redis_report(date)
 
     def store_file_report(self, date):
         try:
@@ -73,19 +77,28 @@ class Market:
         except Exception as e:
             print("impossible to store report to database: {}; {}".format(e.__class__.__name__, e))
 
+    def store_redis_report(self, date):
+        try:
+            conn = redis.Redis(**self.redis_config)
+            for type_, log in self.report.to_json_redis():
+                key = "/cryptoportfolio/{}/{}/{}".format(self.market_id, date.isoformat(), type_)
+                conn.set(key, log, ex=31*24*60*60)
+                key = "/cryptoportfolio/{}/latest/{}".format(self.market_id, type_)
+                conn.set(key, log)
+            key = "/cryptoportfolio/{}/latest/date".format(self.market_id)
+            conn.set(key, date.isoformat())
+        except Exception as e:
+            print("impossible to store report to redis: {}; {}".format(e.__class__.__name__, e))
+
     def process(self, actions, before=False, after=False):
         try:
-            if len(actions or []) == 0:
-                if before:
-                    self.processor.process("sell_all", steps="before")
-                if after:
-                    self.processor.process("sell_all", steps="after")
-            else:
-                for action in actions:
-                    if hasattr(self, action):
-                        getattr(self, action)()
-                    else:
-                        self.report.log_error("market_process", message="Unknown action {}".format(action))
+            for action in actions:
+                if bool(before) is bool(after):
+                    self.processor.process(action, steps="all")
+                elif before:
+                    self.processor.process(action, steps="before")
+                elif after:
+                    self.processor.process(action, steps="after")
         except Exception as e:
             self.report.log_error("market_process", exception=e)
         finally:
@@ -197,30 +210,35 @@ class Market:
         self.report.log_stage("follow_orders_end")
 
     def prepare_trades(self, base_currency="BTC", liquidity="medium",
-            compute_value="average", repartition=None, only=None):
+            compute_value="average", repartition=None, only=None,
+            available_balance_only=False):
 
         self.report.log_stage("prepare_trades",
                 base_currency=base_currency, liquidity=liquidity,
                 compute_value=compute_value, only=only,
-                repartition=repartition)
+                repartition=repartition, available_balance_only=available_balance_only)
 
         values_in_base = self.balances.in_currency(base_currency,
                 compute_value=compute_value)
-        total_base_value = sum(values_in_base.values())
+        if available_balance_only:
+            balance = self.balances.all.get(base_currency)
+            if balance is None:
+                total_base_value = portfolio.Amount(base_currency, 0)
+            else:
+                total_base_value = balance.exchange_free + balance.margin_available
+        else:
+            total_base_value = sum(values_in_base.values())
         new_repartition = self.balances.dispatch_assets(total_base_value,
                 liquidity=liquidity, repartition=repartition)
-        self.trades.compute_trades(values_in_base, new_repartition, only=only)
+        if available_balance_only:
+            for currency, amount in values_in_base.items():
+                if currency != base_currency:
+                    new_repartition.setdefault(currency, portfolio.Amount(base_currency, 0))
+                    new_repartition[currency] += amount
 
-    # Helpers
-    def print_orders(self, base_currency="BTC"):
-        self.report.log_stage("print_orders")
-        self.balances.fetch_balances(tag="print_orders")
-        self.prepare_trades(base_currency=base_currency, compute_value="average")
-        self.trades.prepare_orders(compute_value="average")
+        self.trades.compute_trades(values_in_base, new_repartition, only=only)
 
-    def print_balances(self, base_currency="BTC"):
-        self.report.log_stage("print_balances")
-        self.balances.fetch_balances()
+    def print_tickers(self, base_currency="BTC"):
         if base_currency is not None:
             self.report.print_log("total:")
             self.report.print_log(sum(self.balances.in_currency(base_currency).values()))
@@ -236,12 +254,20 @@ class Processor:
                     "wait_for_recent": {},
                     },
                 ],
+            "print_balances": [
+                {
+                    "name": "print_balances",
+                    "number": 1,
+                    "fetch_balances": ["begin"],
+                    "print_tickers": { "base_currency": "BTC" },
+                    }
+                ],
             "print_orders": [
                 {
                     "name": "wait",
                     "number": 1,
-                    "before": False,
-                    "after": True,
+                    "before": True,
+                    "after": False,
                     "wait_for_recent": {},
                     },
                 {
@@ -280,7 +306,7 @@ class Processor:
                     "before": False,
                     "after": True,
                     "fetch_balances": ["begin", "end"],
-                    "prepare_trades": { "only": "acquire" },
+                    "prepare_trades": { "only": "acquire", "available_balance_only": True },
                     "prepare_orders": { "only": "acquire", "compute_value": "average" },
                     "move_balances": {},
                     "run_orders": {},
@@ -314,7 +340,7 @@ class Processor:
                     "before": False,
                     "after": True,
                     "fetch_balances": ["begin", "end"],
-                    "prepare_trades": {},
+                    "prepare_trades": { "available_balance_only": True },
                     "prepare_orders": { "compute_value": "average" },
                     "move_balances": {},
                     "run_orders": {},
@@ -327,7 +353,7 @@ class Processor:
     ordered_actions = [
             "wait_for_recent", "prepare_trades", "prepare_orders",
             "move_balances", "run_orders", "follow_orders",
-            "close_trades"]
+            "close_trades", "print_tickers"]
 
     def __init__(self, market):
         self.market = market
@@ -336,7 +362,7 @@ class Processor:
         if step == "all":
             return scenario
         elif step == "before" or step == "after":
-            return list(filter(lambda x: step in x and x[step], scenario))
+            return list(filter(lambda x: x.get(step, False), scenario))
         elif type(step) == int:
             return [scenario[step-1]]
         elif type(step) == str:
@@ -344,7 +370,12 @@ class Processor:
         else:
             raise TypeError("Unknown step {}".format(step))
 
+    def can_process(self, scenario_name):
+        return scenario_name in self.scenarios
+
     def process(self, scenario_name, steps="all", **kwargs):
+        if not self.can_process(scenario_name):
+            raise TypeError("Unknown scenario {}".format(scenario_name))
         scenario = self.scenarios[scenario_name]
         selected_steps = []
 
@@ -360,14 +391,14 @@ class Processor:
         process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"])
         self.market.report.log_stage("{}_begin".format(process_name))
         if "begin" in step.get("fetch_balances", []):
-            self.market.balances.fetch_balances(tag="{}_begin".format(process_name))
+            self.market.balances.fetch_balances(tag="{}_begin".format(process_name), log_tickers=True)
 
         for action in self.ordered_actions:
             if action in step:
                 self.run_action(action, step[action], kwargs)
 
         if "end" in step.get("fetch_balances", []):
-            self.market.balances.fetch_balances(tag="{}_end".format(process_name))
+            self.market.balances.fetch_balances(tag="{}_end".format(process_name), log_tickers=True)
         self.market.report.log_stage("{}_end".format(process_name))
 
     def method_arguments(self, action):
@@ -387,6 +418,8 @@ class Processor:
             method = self.market.follow_orders
         elif action == "close_trades":
             method = self.market.trades.close_trades
+        elif action == "print_tickers":
+            method = self.market.print_tickers
 
         signature = inspect.getfullargspec(method)
         defaults = signature.defaults or []