aboutsummaryrefslogblamecommitdiff
path: root/main.py
blob: 13c22409188a34bd10138b308d766287ada0671b (plain) (tree)
1
2
3
4
5
6
7
8
9
                     

               
          
 

                
 
                                           
 


















































                                                                                       




                                    
                                                                                       

                                                         
                            





                                              
                                                                      
         
                                                                                               



                      












                                         
 











                                                                   
                                  
 


                                        
 
                                  

                     

                                             


                                         
                                                






                                                                   


                                                            







                                                                                   

                                                                                         



                                                                                           



                                                                                                  













                                                                                                 





                                                                                     
 



                                    
 
                                                                              

                      
                                                                      

                                                                       



                                                                           


                           
                                                
 

                                                       



                                       
                    
                            


                                                                
         


                                                                                 
                                                                                  
 




                                      

                                             
import configargparse
import psycopg2
import os
import sys

import market
import portfolio

__all__ = ["make_order", "get_user_market"]

def make_order(market, value, currency, action="acquire",
        close_if_possible=False, base_currency="BTC", follow=True,
        compute_value="average"):
    """
    Make an order on market
    "market": The market on which to place the order
    "value": The value in *base_currency* to acquire,
             or in *currency* to dispose.
             use negative for margin trade.
    "action": "acquire" or "dispose".
                "acquire" will buy long or sell short,
                "dispose" will sell long or buy short.
    "currency": The currency to acquire or dispose
    "base_currency": The base currency. The value is expressed in that
                     currency (default: BTC)
    "follow": Whether to follow the order once run (default: True)
    "close_if_possible": Whether to try to close the position at the end
                         of the trade, i.e. reach exactly 0 at the end
                         (only meaningful in "dispose"). May have
                         unwanted effects if the end value of the
                         currency is not 0.
    "compute_value": Compute value to place the order
    """
    market.report.log_stage("make_order_begin")
    market.balances.fetch_balances(tag="make_order_begin")
    if action == "acquire":
        trade = portfolio.Trade(
                portfolio.Amount(base_currency, 0),
                portfolio.Amount(base_currency, value),
                currency, market)
    else:
        amount = portfolio.Amount(currency, value)
        trade = portfolio.Trade(
                amount.in_currency(base_currency, market, compute_value=compute_value),
                portfolio.Amount(base_currency, 0),
                currency, market)
    market.trades.all.append(trade)
    order = trade.prepare_order(
            close_if_possible=close_if_possible,
            compute_value=compute_value)
    market.report.log_orders([order], None, compute_value)
    market.trades.run_orders()
    if follow:
        market.follow_orders()
        market.balances.fetch_balances(tag="make_order_end")
    else:
        market.report.log_stage("make_order_end_not_followed")
        return order
    market.report.log_stage("make_order_end")

def get_user_market(config_path, user_id, debug=False):
    args = ["--config", config_path]
    if debug:
        args.append("--debug")
    args = parse_args(args)
    pg_config = parse_config(args)
    market_id, market_config, user_id = list(fetch_markets(pg_config, str(user_id)))[0]
    return market.Market.from_config(market_config, args,
            pg_config=pg_config, market_id=market_id,
            user_id=user_id)

def fetch_markets(pg_config, user):
    connection = psycopg2.connect(**pg_config)
    cursor = connection.cursor()

    if user is None:
        cursor.execute("SELECT id,config,user_id FROM market_configs")
    else:
        cursor.execute("SELECT id,config,user_id FROM market_configs WHERE user_id = %s", user)

    for row in cursor:
        yield row

def parse_config(args):
    pg_config = {
            "host": args.db_host,
            "port": args.db_port,
            "user": args.db_user,
            "password": args.db_password,
            "database": args.db_database,
            }
    del(args.db_host)
    del(args.db_port)
    del(args.db_user)
    del(args.db_password)
    del(args.db_database)

    redis_config = {
            "host": args.redis_host,
            "port": args.redis_port,
            "db": args.redis_database,
            }
    if redis_config["host"].startswith("/"):
        redis_config["unix_socket_path"] = redis_config.pop("host")
        del(redis_config["port"])
    del(args.redis_host)
    del(args.redis_port)
    del(args.redis_database)

    report_path = args.report_path

    if report_path is not None and not \
            os.path.exists(report_path):
        os.makedirs(report_path)

    return pg_config, redis_config

def parse_args(argv):
    parser = configargparse.ArgumentParser(
            description="Run the trade bot.")

    parser.add_argument("-c", "--config",
            default="config.ini",
            required=False, is_config_file=True,
            help="Config file to load (default: config.ini)")
    parser.add_argument("--before",
            default=False, action='store_const', const=True,
            help="Run the steps before the cryptoportfolio update")
    parser.add_argument("--after",
            default=False, action='store_const', const=True,
            help="Run the steps after the cryptoportfolio update")
    parser.add_argument("--quiet",
            default=False, action='store_const', const=True,
            help="Don't print messages")
    parser.add_argument("--debug",
            default=False, action='store_const', const=True,
            help="Run in debug mode")
    parser.add_argument("--user",
            default=None, required=False, help="Only run for that user")
    parser.add_argument("--action",
            action='append',
            help="Do a different action than trading (add several times to chain)")
    parser.add_argument("--parallel", action='store_true', default=True, dest="parallel")
    parser.add_argument("--no-parallel", action='store_false', dest="parallel")
    parser.add_argument("--report-db", action='store_true', default=True, dest="report_db",
            help="Store report to database (default)")
    parser.add_argument("--no-report-db", action='store_false', dest="report_db",
            help="Don't store report to database")
    parser.add_argument("--report-redis", action='store_true', default=False, dest="report_redis",
            help="Store report to redis")
    parser.add_argument("--no-report-redis", action='store_false', dest="report_redis",
            help="Don't store report to redis (default)")
    parser.add_argument("--report-path", required=False,
            help="Where to store the reports (default: absent, don't store)")
    parser.add_argument("--no-report-path", action='store_const', dest='report_path', const=None,
            help="Don't store the report to file (default)")
    parser.add_argument("--db-host", default="localhost",
            help="Host access to database (default: localhost)")
    parser.add_argument("--db-port", default=5432,
            help="Port access to database (default: 5432)")
    parser.add_argument("--db-user", default="cryptoportfolio",
            help="User access to database (default: cryptoportfolio)")
    parser.add_argument("--db-password", default="cryptoportfolio",
            help="Password access to database (default: cryptoportfolio)")
    parser.add_argument("--db-database", default="cryptoportfolio",
            help="Database access to database (default: cryptoportfolio)")
    parser.add_argument("--redis-host", default="localhost",
            help="Host access to database (default: localhost). Use path for socket")
    parser.add_argument("--redis-port", default=6379,
            help="Port access to redis (default: 6379)")
    parser.add_argument("--redis-database", default=0,
            help="Redis database to use (default: 0)")

    parsed = parser.parse_args(argv)
    if parsed.action is None:
        parsed.action = ["sell_all"]
    return parsed

def process(market_config, market_id, user_id, args, pg_config, redis_config):
    try:
        market.Market\
                .from_config(market_config, args, market_id=market_id,
                        pg_config=pg_config, redis_config=redis_config,
                        user_id=user_id)\
                .process(args.action, before=args.before, after=args.after)
    except Exception as e:
        print("{}: {}".format(e.__class__.__name__, e))

def main(argv):
    args = parse_args(argv)

    pg_config, redis_config = parse_config(args)

    market.Portfolio.report.set_verbose(not args.quiet)

    if args.parallel:
        import threading
        market.Portfolio.start_worker()

        threads = []
        def process_(*args):
            thread = threading.Thread(target=process, args=args)
            thread.start()
            threads.append(thread)
    else:
        process_ = process

    for market_id, market_config, user_id in fetch_markets(pg_config, args.user):
        process_(market_config, market_id, user_id, args, pg_config, redis_config)

    if args.parallel:
        for thread in threads:
            thread.join()
        market.Portfolio.stop_worker()

if __name__ == '__main__': # pragma: no cover
    main(sys.argv[1:])