Beispiel #1
0
def application(argv):
    fix_thread_set_name()

    # init terminal displayer
    Terminal()

    options = {
        'working-path': os.getcwd(),
        'identity': 'real',
        'config-path': './user/config',
        'log-path': './user/log',
        'reports-path': './user/reports',
        'markets-path': './user/markets',
        'log-name': 'siis.log',
    }

    # create initial siis data structure if necessary
    install(options)

    siis_log = SiisLog(options, Terminal().inst().style())
    siis_logger = logging.getLogger('siis')

    # parse process command line
    if len(argv) > 1:
        options['livemode'] = True

        # utc or local datetime ?
        for arg in argv:
            if arg.startswith('--'):
                if arg == '--paper-mode':
                    # livemode but in paper-mode
                    options['paper-mode'] = True
                elif arg == '--fetch':
                    # use the fetcher
                    options['fetch'] = True
                elif arg == '--binarize':
                    # use the binarizer
                    options['binarize'] = True
                elif arg == '--optimize':
                    # use the optimizer
                    options['optimize'] = True
                elif arg == '--sync':
                    # use the syncer
                    options['sync'] = True
                elif arg == '--rebuild':
                    # use the rebuilder
                    options['rebuild'] = True

                elif arg == '--install-market':
                    options['install-market'] = True
                elif arg == '--initial-fetch':
                    # do the initial OHLC fetch for watchers
                    options['initial-fetch'] = True
                elif arg == '--backtest':
                    # backtest mean always paper-mode
                    options['paper-mode'] = True
                    options['backtesting'] = True
                elif arg.startswith('--timestep='):
                    # backesting timestep, default is 60 second
                    options['timestep'] = float(arg.split('=')[1])
                elif arg.startswith('--time-factor='):
                    # backtesting time-factor
                    options['time-factor'] = float(arg.split('=')[1])

                elif arg.startswith('--from='):
                    # if backtest from date (if ommited use whoole data) date format is "yyyy-mm-dd-hh:mm:ss", fetch, binarize, optimize to date
                    options['from'] = datetime.strptime(
                        arg.split('=')[1],
                        '%Y-%m-%dT%H:%M:%S').replace(tzinfo=UTC())
                elif arg.startswith('--to='):
                    # if backtest to date (can be ommited), fetch, binarize, optimize to date
                    options['to'] = datetime.strptime(
                        arg.split('=')[1],
                        '%Y-%m-%dT%H:%M:%S').replace(tzinfo=UTC())
                elif arg.startswith('--last='):
                    # fetch the last n data history
                    options['last'] = int(arg.split('=')[1])

                elif arg.startswith('--market='):
                    # fetch, binarize, optimize the data history for this market
                    options['market'] = arg.split('=')[1]
                elif arg.startswith('--spec='):
                    # fetcher data history option
                    options['option'] = arg.split('=')[1]
                elif arg.startswith('--broker='):
                    # broker name for fetcher, watcher, optimize, binarize
                    options['broker'] = arg.split('=')[1]
                elif arg.startswith('--timeframe='):
                    # fetch, binarize, optimize base timeframe
                    options['timeframe'] = arg.split('=')[1]
                elif arg.startswith('--cascaded='):
                    # fetch cascaded ohlc generation
                    options['cascaded'] = arg.split('=')[1]

                elif arg == '--watcher-only':
                    # feed only with live data (not compatible with --read-only)
                    options['watcher-only'] = True

                elif arg == '--read-only':
                    # does not write to the database (not compatible with --watcher-only)
                    options['read-only'] = True
                elif arg == '--check-data':
                    # check DB ohlc data (@todo)
                    options['check-data'] = True

                elif arg.startswith('--profile='):
                    # appliances profile name
                    options['profile'] = arg.split('=')[1]

                elif arg == '--version':
                    Terminal.inst().info('%s %s' % (APP_SHORT_NAME, '.'.join(
                        [str(x) for x in APP_VERSION])))
                    sys.exit(0)

                elif arg == '--help' or '-h':
                    display_cli_help()
                    sys.exit(0)
            else:
                options['identity'] = argv[1]

        # watcher-only read-only mutual exclusion
        if options.get('watcher-only') and options.get('read-only'):
            Terminal.inst().error(
                "Options --watcher-only and --read-only are mutually exclusive !"
            )
            sys.exit(-1)

        # backtesting
        if options.get('backtesting', False):
            if options.get('from') is None or options.get('to') is None:
                del options['backtesting']
                Terminal.inst().error(
                    "Backtesting need from= and to= date time")
                sys.exit(-1)

    if options['identity'].startswith('-'):
        Terminal.inst().error("First option must be the identity name")

    #
    # binarizer mode
    #

    if options.get('binarize'):
        if options.get('market') and options.get('from') and options.get(
                'to') and options.get('broker'):
            from tools.binarizer import do_binarizer
            do_binarizer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # fetcher mode
    #

    if options.get('fetch'):
        if options.get('market') and options.get('broker'):
            from tools.fetcher import do_fetcher
            do_fetcher(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # optimizer mode
    #

    if options.get('optimize'):
        if options.get('market') and options.get('from') and options.get(
                'to') and options.get('broker'):
            from tools.optimizer import do_optimizer
            do_optimizer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # sync mode
    #

    if options.get('sync'):
        if options.get('market') and options.get('broker'):
            from tools.syncer import do_syncer
            do_syncer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # rebuilder mode
    #

    if options.get('rebuild'):
        if options.get('market') and options.get('from') and options.get(
                'to') and options.get('broker') and options.get('timeframe'):
            from tools.rebuilder import do_rebuilder
            do_rebuilder(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # normal mode
    #

    Terminal.inst().info("Starting SIIS using %s identity..." %
                         options['identity'])
    Terminal.inst().action("- (Press 'q' twice to terminate)")
    Terminal.inst().action("- (Press 'h' for help)")
    Terminal.inst().flush()

    if options.get('backtesting'):
        Terminal.inst().notice("Process a backtesting.")

    if options.get('paper-mode'):
        Terminal.inst().notice("- Using paper-mode trader.")
    else:
        Terminal.inst().notice("- Using live-mode trader.")

    signal.signal(signal.SIGINT, signal_handler)

    # monitoring service
    Terminal.inst().info("Starting monitor service...")
    monitor_service = MonitorService(options)

    # desktop notifier
    desktop_service = DesktopNotifier()
    # discord_service = DiscordNotifier()
    view_service = ViewService()

    # database manager
    Database.create(options)
    Database.inst().setup(options)

    # watcher service
    Terminal.inst().info("Starting watcher's service...")
    watcher_service = WatcherService(options)
    watcher_service.start(options)

    # trader service
    Terminal.inst().info("Starting trader's service...")
    trader_service = TraderService(watcher_service, monitor_service, options)
    trader_service.start(options)

    # want to display desktop notification and update views
    watcher_service.add_listener(desktop_service)
    watcher_service.add_listener(view_service)

    # want to display desktop notification and update views
    trader_service.add_listener(desktop_service)
    trader_service.add_listener(view_service)

    # trader service listen to watcher service and update views
    watcher_service.add_listener(trader_service)

    # strategy service
    Terminal.inst().info("Starting strategy's service...")
    strategy_service = StrategyService(watcher_service, trader_service,
                                       monitor_service, options)
    strategy_service.start(options)

    # strategy service listen to watcher service
    watcher_service.add_listener(strategy_service)

    # strategy service listen to trader service
    trader_service.add_listener(strategy_service)

    # want to display desktop notification, update view and notify on discord
    # strategy_service.add_listener(notifier_service)
    # @todo add notifier service and replace desktop service as desktop notifier into this service same for discord...
    strategy_service.add_listener(desktop_service)
    strategy_service.add_listener(view_service)

    # for display stats (@todo move to views)
    desktop_service.strategy_service = strategy_service
    desktop_service.trader_service = trader_service

    # register terminal commands
    commands_handler = CommandsHandler()
    commands_handler.init(options)

    # cli commands registration
    register_general_commands(commands_handler)
    register_trading_commands(commands_handler, trader_service,
                              strategy_service, monitor_service)
    register_region_commands(commands_handler, strategy_service)

    setup_views(siis_logger, view_service, watcher_service, trader_service,
                strategy_service)

    # setup and start the monitor service
    monitor_service.setup(watcher_service, trader_service, strategy_service)
    monitor_service.start()

    Terminal.inst().message("Running main loop...")

    Terminal.inst().upgrade()
    Terminal.inst().message("Steady...", view='notice')

    display_welcome()

    LOOP_SLEEP = 0.016  # in second
    MAX_CMD_ALIVE = 15  # in second

    running = True

    value = None
    value_changed = False
    command_timeout = 0
    prev_timestamp = 0

    try:
        while running:
            # keyboard input commands
            try:
                c = Terminal.inst().read()
                key = Terminal.inst().key()

                if c:
                    # split the commande line
                    args = [
                        arg
                        for arg in (value[1:].split(' ')
                                    if value and value.startswith(':') else [])
                        if arg
                    ]
                    if value and value[-1] == ' ':
                        args.append('')

                    # update the current type command
                    commands_handler.process_char(c, args)

                if key:
                    if key == 'KEY_ESCAPE':
                        # cancel command
                        value = None
                        value_changed = True
                        command_timeout = 0

                        # use command mode
                        Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                    # split the commande line
                    args = [
                        arg
                        for arg in (value[1:].split(' ')
                                    if value and value.startswith(':') else [])
                        if arg
                    ]
                    if value and value[-1] == ' ':
                        args.append('')

                    # process on the arguments
                    args = commands_handler.process_key(
                        key, args,
                        Terminal.inst().mode == Terminal.MODE_COMMAND)

                    if args:
                        # regen the updated commande ligne
                        value = ":" + ' '.join(args)
                        value_changed = True
                        command_timeout = 0

                    desktop_service.on_key_pressed(key)

                # @todo move the rest to command_handler
                if c:
                    if value and value[0] == ':':
                        if c == '\b':
                            # backspace, erase last command char
                            value = value[:-1] if value else None
                            value_changed = True
                            command_timeout = time.time()

                        elif c != '\n':
                            # append to the advanced command value
                            value += c
                            value_changed = True
                            command_timeout = time.time()

                        elif c == '\n':
                            result = commands_handler.process_cli(value)
                            command_timeout = 0

                            if not result:
                                # maybe an application level command
                                if value == ':q' or value == ':quit':
                                    running = False

                                elif value.startswith(':x '):
                                    # manually exit position at market @todo move as command
                                    target = value[2:]

                                    if target == "all" or target == "ALL":
                                        Terminal.inst().action(
                                            "Send close to market command for any positions",
                                            view='status')
                                        trader_service.command(
                                            Trader.COMMAND_CLOSE_ALL_MARKET,
                                            {})
                                    else:
                                        Terminal.inst().action(
                                            "Send close to market command for position %s"
                                            % (target, ),
                                            view='status')
                                        trader_service.command(
                                            Trader.COMMAND_CLOSE_MARKET,
                                            {'key': target})

                                elif value.startswith(':d '):
                                    # @deprecated manually duplicate a position entry or exit must be associated to social strategy
                                    # @todo move as command
                                    target = value[2:]

                                    Terminal.inst().action(
                                        "Send replicate to market command for position %s"
                                        % (target, ),
                                        view='status')
                                    trader_service.command(
                                        Trader.COMMAND_TRIGGER,
                                        {'key': target})

                            # clear command value
                            value_changed = True
                            value = None

                            # use default mode
                            Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                    elif c != '\n':
                        # initial command value
                        value = "" + c
                        value_changed = True
                        command_timeout = time.time()

                        if value and value[0] == ':':
                            # use command mode
                            Terminal.inst().set_mode(Terminal.MODE_COMMAND)

                    if value and value[0] != ':':
                        # direct key

                        # use default mode
                        Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                        try:
                            result = commands_handler.process_accelerator(key)

                            # @todo convert to Command object accelerator
                            if not result:
                                result = True

                                # @todo might be replaced by views
                                if value == 'p':
                                    trader_service.command(
                                        Trader.COMMAND_LIST_POSITIONS, {})
                                elif value == 'o':
                                    trader_service.command(
                                        Trader.COMMAND_LIST_ORDERS, {})
                                elif value == 'g':
                                    trader_service.command(
                                        Trader.COMMAND_SHOW_PERFORMANCE, {})

                                # display views

                                elif value == 'C':
                                    Terminal.inst().clear_content()
                                elif value == 'D':
                                    Terminal.inst().switch_view('debug')
                                elif value == 'I':
                                    Terminal.inst().switch_view('content')
                                elif value == 'F':
                                    Terminal.inst().switch_view('strategy')
                                elif value == 'S':
                                    Terminal.inst().switch_view('stats')
                                elif value == 'P':
                                    Terminal.inst().switch_view('perf')
                                elif value == 'T':
                                    Terminal.inst().switch_view('ticker')
                                elif value == 'A':
                                    Terminal.inst().switch_view('account')
                                elif value == 'M':
                                    Terminal.inst().switch_view('market')
                                elif value == 'Q':
                                    Terminal.inst().switch_view('asset')
                                elif value == 'N':
                                    Terminal.inst().switch_view('signal')

                                elif value == '?':
                                    # ping services and workers
                                    watcher_service.ping()
                                    trader_service.ping()
                                    strategy_service.ping()
                                    monitor_service.ping()

                                elif value == ' ':
                                    # a simple mark on the terminal
                                    Terminal.inst().notice(
                                        "Trading time %s" %
                                        (datetime.fromtimestamp(
                                            strategy_service.timestamp).
                                         strftime('%Y-%m-%d %H:%M:%S')),
                                        view='status')

                                elif value == 'a':
                                    desktop_service.audible = not desktop_service.audible
                                    Terminal.inst().action(
                                        "Audible notification are now %s" %
                                        ("actives" if desktop_service.audible
                                         else "disabled", ),
                                        view='status')
                                elif value == 'n':
                                    desktop_service.popups = not desktop_service.popups
                                    Terminal.inst().action(
                                        "Desktop notification are now %s" %
                                        ("actives" if desktop_service.popups
                                         else "disabled", ),
                                        view='status')
                                elif value == 'e':
                                    desktop_service.discord = not desktop_service.discord
                                    Terminal.inst().action(
                                        "Discord notification are now %s" %
                                        ("actives" if desktop_service.discord
                                         else "disabled", ),
                                        view='status')

                                else:
                                    result = False

                            if result:
                                value = None
                                value_changed = True
                                command_timeout = 0

                        except Exception as e:
                            has_exception(siis_logger, e)

            except IOError:
                pass
            except Exception as e:
                has_exception(siis_logger, e)

            # display advanced command only
            if value_changed:
                if value and value.startswith(':'):
                    Terminal.inst().action("Command: %s" % value[1:],
                                           view='command')
                else:
                    Terminal.inst().message("", view='command')

            # clear input if no char hit during the last MAX_CMD_ALIVE
            if value and not value.startswith(':'):
                if (command_timeout > 0) and (time.time() - command_timeout >=
                                              MAX_CMD_ALIVE):
                    value = None
                    value_changed = True
                    Terminal.inst().info("Current typing canceled",
                                         view='status')

            try:
                # display strategy tarding time (update max once per second)
                if strategy_service.timestamp - prev_timestamp >= 1.0:
                    mode = "live"
                    if trader_service.backtesting:
                        mode = "backtesting"
                    elif trader_service.paper_mode:
                        mode = "paper-mode"

                    Terminal.inst().message(
                        "%s - %s" %
                        (mode,
                         datetime.fromtimestamp(strategy_service.timestamp).
                         strftime('%Y-%m-%d %H:%M:%S')),
                        view='notice')
                    prev_timestamp = strategy_service.timestamp

                # synchronous operations here
                watcher_service.sync()
                trader_service.sync()
                strategy_service.sync()
                monitor_service.sync()
                desktop_service.sync()
                view_service.sync()

                Terminal.inst().update()

            except BaseException as e:
                siis_logger.error(traceback.format_exc())
                Terminal.inst().error(repr(e))

            # don't waste CPU time on main thread
            time.sleep(LOOP_SLEEP)

    finally:
        Terminal.inst().restore_term()

    Terminal.inst().info("Terminate...")
    Terminal.inst().flush()

    commands_handler.terminate(options)
    commands_handler = None

    # service terminate
    monitor_service.terminate()
    strategy_service.terminate()
    trader_service.terminate()
    watcher_service.terminate()
    desktop_service.terminate()
    # discord_service.terminate()
    view_service.terminate()

    Terminal.inst().info("Saving database...")
    Terminal.inst().flush()

    Database.terminate()

    Terminal.inst().info("Bye!")
    Terminal.inst().flush()

    Terminal.terminate()
Beispiel #2
0
def application(argv):
    fix_thread_set_name()

    # init terminal displayer
    Terminal()

    options = {'log-path': './user/log', 'log-name': 'client.log'}

    # create initial siis data structure if necessary
    install(options)

    siis_log = SiisLog(options)
    logger = logging.getLogger('siis.client')
    stream = ""
    rpc = ""
    fifo = -1
    fifo_rpc = -1

    if len(sys.argv) > 1:
        stream = sys.argv[1]

    if len(sys.argv) > 2:
        rpc = sys.argv[2]

    if not stream:
        Terminal.inst().error("- Missing stream url !")

    if not rpc:
        Terminal.inst().error("- Missing RPC url !")

    try:
        fifo = os.open(stream, os.O_NONBLOCK | posix.O_RDONLY)
    except Exception as e:
        Terminal.inst().error(repr(e))

    if not fifo:
        Terminal.inst().error("- Cannot open the stream !")

    try:
        fifo_rpc = os.open(rpc, os.O_NONBLOCK | posix.O_WRONLY)
    except Exception as e:
        Terminal.inst().error(repr(e))

    if not fifo_rpc:
        Terminal.inst().error("- Cannot open the RPC fifo !")

    Terminal.inst().info("Starting SIIS simple chart client...")
    Terminal.inst().flush()

    try:
        Charting.inst().start()
    except Exception as e:
        has_exception(e)

    dispatcher = Dispatcher()

    running = True

    Terminal.inst().message("Running main loop...")

    size = 32768
    buf = []
    content = ""
    cur = bytearray()

    if not Charting.inst().visible:
        if not Charting.inst().running:
            # charting service
            try:
                Charting.inst().start()
            except Exception as e:
                has_exception(e)

        if Charting.inst().running:
            Charting.inst().show()
            Terminal.inst().action("Charting is now shown")

    while running:
        # read from fifo
        try:
            buf = os.read(fifo, size)

            if buf:
                for n in buf:
                    if n == 10:  # new line as message termination
                        try:
                            msg = json.loads(cur.decode('utf8'))
                            dispatcher.on_message(msg)
                        except Exception as e:
                            logger.error(repr(e))

                        cur = bytearray()
                    else:
                        cur.append(n)

        except (BrokenPipeError, IOError):
            pass

        if not Charting.inst().has_charts():
            running = False

        time.sleep(0.01)

    # close message
    messages = dispatcher.close()

    if fifo:
        os.close(fifo)
        fifo = -1

    if fifo_rpc:
        for msg in messages:
            try:
                # write to fifo
                posix.write(fifo_rpc, (json.dumps(msg) + '\n').encode('utf8'))
            except (BrokenPipeError, IOError) as e:
                logger.error(repr(e))
            except (TypeError, ValueError) as e:
                logger.error("Error sending message : %s" % repr(c))

        fifo_rpc.flush()

        # os.flush(fifo_rpc)
        os.close(fifo_rpc)
        fifo_rpc = -1

    Terminal.inst().info("Terminate...")
    Terminal.inst().flush()

    # terminate charting singleton
    Charting.terminate()

    Terminal.inst().info("Bye!")
    Terminal.inst().flush()

    Terminal.terminate()
Beispiel #3
0
def application(argv):
    fix_thread_set_name()

    # init terminal displayer
    Terminal()

    options = {
        'working-path': os.getcwd(),
        'identity': 'real',
        'config-path': './user/config',
        'log-path': './user/log',
        'reports-path': './user/reports',
        'markets-path': './user/markets',
        'log-name': 'siis.log',
    }

    # create initial siis data structure if necessary
    install(options)

    siis_log = SiisLog(options, Terminal().inst().style())
    siis_logger = logging.getLogger('siis')
    traceback_logger = logging.getLogger('siis.traceback')

    # parse process command line
    if len(argv) > 1:
        options['livemode'] = True

        # utc or local datetime ?
        for arg in argv:
            if arg.startswith('--'):
                if arg == '--paper-mode':
                    # livemode but in paper-mode
                    options['paper-mode'] = True

                elif arg == '--fetch':
                    # use the fetcher
                    options['tool'] = "fetcher"
                elif arg == '--binarize':
                    # use the binarizer
                    options['tool'] = "binarize"
                elif arg == '--optimizer':
                    # use the optimizer
                    options['tool'] = "optimizer"
                elif arg == '--sync':
                    # use the syncer
                    options['tool'] = "syncer"
                elif arg == '--rebuild':
                    # use the rebuilder
                    options['tool'] = "rebuilder"
                elif arg == '--export':
                    # use the exporter
                    options['tool'] = "exporter"
                elif arg == '--import':
                    # use the importer
                    options['tool'] = "importer"
                elif arg == '--clean':
                    # use the cleaner
                    options['tool'] = "cleaner"
                elif arg.startswith("--tool="):
                    # use a named tool
                    options['tool'] = arg.split('=')[1]

                elif arg == '--no-conf':
                    options['no-conf'] = True
                elif arg == '--zip':
                    options['zip'] = True

                elif arg == '--install-market':
                    options['install-market'] = True
                elif arg == '--initial-fetch':
                    # do the initial OHLC fetch for watchers
                    options['initial-fetch'] = True

                elif arg == '--backtest':
                    # backtest mean always paper-mode
                    options['paper-mode'] = True
                    options['backtesting'] = True
                elif arg.startswith('--timestep='):
                    # backesting timestep, default is 60 second
                    options['timestep'] = float(arg.split('=')[1])
                elif arg.startswith('--time-factor='):
                    # backtesting time-factor
                    options['time-factor'] = float(arg.split('=')[1])

                elif arg.startswith('--filename='):
                    # used with import or export
                    options['filename'] = arg.split('=')[1]

                elif arg.startswith('--from='):
                    # if backtest from date and tools
                    options['from'] = parse_datetime(arg.split('=')[1])
                    if not options['from']:
                        Terminal.inst().error("Invalid 'from' datetime format")
                        sys.exit(-1)
                elif arg.startswith('--to='):
                    # if backtest to date and tools
                    options['to'] = parse_datetime(arg.split('=')[1])
                    if not options['to']:
                        Terminal.inst().error("Invalid 'to' datetime format")
                        sys.exit(-1)
                elif arg.startswith('--last='):
                    # fetch the last n data history
                    options['last'] = int(arg.split('=')[1])
                    if options['last'] <= 0:
                        Terminal.inst().error(
                            "Invalid 'last' value. Must be at least 1")
                        sys.exit(-1)

                elif arg.startswith('--market='):
                    # fetch, binarize, optimize the data history for this market
                    options['market'] = arg.split('=')[1]
                elif arg.startswith('--spec='):
                    # fetcher data history option
                    options['option'] = arg.split('=')[1]
                elif arg.startswith('--broker='):
                    # broker name for fetcher, watcher, optimize, binarize
                    options['broker'] = arg.split('=')[1]
                elif arg.startswith('--timeframe='):
                    # fetch, binarize, optimize base timeframe
                    options['timeframe'] = arg.split('=')[1]
                elif arg.startswith('--cascaded='):
                    # fetch cascaded ohlc generation
                    options['cascaded'] = arg.split('=')[1]
                elif arg.startswith('--target='):
                    # target ohlc generation
                    options['target'] = arg.split('=')[1]

                elif arg == '--watcher-only':
                    # feed only with live data (not compatible with --read-only)
                    options['watcher-only'] = True
                elif arg == '--read-only':
                    # does not write to the database (not compatible with --watcher-only)
                    options['read-only'] = True

                elif arg.startswith('--profile='):
                    # appliances profile name
                    options['profile'] = arg.split('=')[1]

                elif arg == '--version':
                    Terminal.inst().info('%s %s' % (APP_SHORT_NAME, '.'.join(
                        [str(x) for x in APP_VERSION])))
                    sys.exit(0)

                elif arg == '--help' or '-h':
                    display_cli_help()
                    sys.exit(0)
            else:
                options['identity'] = argv[1]

        # watcher-only read-only mutual exclusion
        if options.get('watcher-only') and options.get('read-only'):
            Terminal.inst().error(
                "Options --watcher-only and --read-only are mutually exclusive !"
            )
            sys.exit(-1)

        # backtesting
        if options.get('backtesting', False):
            if options.get('from') is None or options.get('to') is None:
                del options['backtesting']
                Terminal.inst().error(
                    "Backtesting need from= and to= date time")
                sys.exit(-1)

    #
    # binarizer mode
    #

    if options.get('tool') == "binarizer":
        if options.get('market') and options.get('from') and options.get(
                'to') and options.get('broker'):
            from tools.binarizer import do_binarizer
            do_binarizer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # fetcher mode
    #

    if options.get('tool') == "fetcher":
        if options.get('market') and options.get('broker'):
            from tools.fetcher import do_fetcher
            do_fetcher(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # optimizer mode
    #

    if options.get('tool') == "optimizer":
        if options.get('market') and options.get('from') and options.get(
                'broker'):
            from tools.optimizer import do_optimizer
            do_optimizer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # rebuilder mode
    #

    if options.get('tool') == "rebuilder":
        if options.get('market') and options.get('from') and options.get(
                'broker') and options.get('timeframe'):
            from tools.rebuilder import do_rebuilder
            do_rebuilder(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # exporter mode
    #

    if options.get('tool') == "exporter":
        if options.get('market') and options.get('from') and options.get(
                'broker') and options.get('filename'):
            from tools.exporter import do_exporter
            do_exporter(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # importer mode
    #

    if options.get('tool') == "importer":
        if options.get('filename'):
            from tools.importer import do_importer
            do_importer(options)
        else:
            display_cli_help()

        sys.exit(0)

    #
    # tool mode
    #

    if options.get('tool'):
        ToolClazz = Tool.load_tool(options.get('tool'))
        if ToolClazz:
            if ToolClazz.need_identity():
                if options['identity'].startswith('-'):
                    Terminal.inst().error(
                        "First option must be the identity name")
                    Terminal.inst().flush()

                    sys.exit(-1)

            tool = ToolClazz(options)

            if not tool.check_options(options):
                display_cli_help()
                sys.exit(-1)

            if ToolClazz.need_identity():
                Terminal.inst().info(
                    "Starting SIIS %s using %s identity..." %
                    (options.get('tool'), options['identity']))
            else:
                Terminal.inst().info("Starting SIIS %s..." %
                                     options.get('tool'))

            Terminal.inst().flush()

            tool.execute(options)

            Terminal.inst().info(
                "%s done!" %
                (ToolClazz.alias() or options.get('tool')).capitalize())
            Terminal.inst().flush()

            Terminal.terminate()

            sys.exit(0)
        else:
            sys.exit(-1)

    if options['identity'].startswith('-'):
        Terminal.inst().error("First option must be the identity name")

    #
    # normal mode
    #

    Terminal.inst().info("Starting SIIS using %s identity..." %
                         options['identity'])
    Terminal.inst().action("- type ':q<Enter> or :quit<Enter>' to terminate")
    Terminal.inst().action(
        "- type ':h<Enter> or :help<Enter>' to display help")
    Terminal.inst().flush()

    if options.get('backtesting'):
        Terminal.inst().notice("Process a backtesting.")

    if options.get('paper-mode'):
        Terminal.inst().notice("- Using paper-mode trader.")
    else:
        Terminal.inst().notice("- Using live-mode trader.")

    signal.signal(signal.SIGINT, signal_handler)

    #
    # application
    #

    watchdog_service = WatchdogService(options)
    watchdog_service.start(options)

    # application services
    view_service = None
    notifier_service = None
    watcher_service = None
    trader_service = None
    strategy_service = None

    # monitoring service
    Terminal.inst().info("Starting monitor service...")
    monitor_service = MonitorService(options)

    # notifier service
    try:
        notifier_service = NotifierService(options)
        notifier_service.start(options)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # view service
    try:
        view_service = ViewService(options)
        watchdog_service.add_service(view_service)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # database manager
    try:
        Database.create(options)
        Database.inst().setup(options)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # watcher service
    Terminal.inst().info("Starting watcher's service...")
    try:
        watcher_service = WatcherService(options)
        watcher_service.start(options)
        watchdog_service.add_service(watcher_service)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # trader service
    Terminal.inst().info("Starting trader's service...")
    try:
        trader_service = TraderService(watcher_service, monitor_service,
                                       options)
        trader_service.start(options)
        watchdog_service.add_service(trader_service)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # want to display desktop notification and update views
    watcher_service.add_listener(view_service)

    # want to display desktop notification and update views
    trader_service.add_listener(view_service)

    # trader service listen to watcher service and update views
    watcher_service.add_listener(trader_service)

    # strategy service
    Terminal.inst().info("Starting strategy's service...")
    try:
        strategy_service = StrategyService(watcher_service, trader_service,
                                           monitor_service, options)
        strategy_service.start(options)
        watchdog_service.add_service(strategy_service)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    # wan't to be notifier of system errors
    watchdog_service.add_listener(notifier_service)

    # strategy service listen to watcher service
    watcher_service.add_listener(strategy_service)

    # strategy service listen to trader service
    trader_service.add_listener(strategy_service)

    # want to display desktop notification, update view and notify on discord
    strategy_service.add_listener(notifier_service)
    strategy_service.add_listener(view_service)

    # want signal and important notifications
    notifier_service.set_strategy_service(strategy_service)
    notifier_service.set_trader_service(trader_service)

    # register terminal commands
    commands_handler = CommandsHandler()
    commands_handler.init(options)

    # cli commands registration
    register_general_commands(commands_handler)
    register_trading_commands(commands_handler, trader_service,
                              strategy_service, monitor_service,
                              notifier_service)
    register_region_commands(commands_handler, strategy_service)

    # setup and start the monitor service
    monitor_service.setup(watcher_service, trader_service, strategy_service)
    try:
        monitor_service.start()
        watchdog_service.add_service(monitor_service)
    except Exception as e:
        Terminal.inst().error(str(e))
        terminate(watchdog_service, watcher_service, trader_service,
                  strategy_service, monitor_service, view_service,
                  notifier_service)
        sys.exit(-1)

    Terminal.inst().message("Running main loop...")

    Terminal.inst().upgrade()
    Terminal.inst().message("Steady...", view='notice')

    if view_service:
        # setup the default views
        try:
            setup_default_views(view_service, watcher_service, trader_service,
                                strategy_service)
        except Exception as e:
            Terminal.inst().error(str(e))
            terminate(watchdog_service, watcher_service, trader_service,
                      strategy_service, monitor_service, view_service,
                      notifier_service)
            sys.exit(-1)

    display_welcome()

    LOOP_SLEEP = 0.016  # in second
    MAX_CMD_ALIVE = 15  # in second

    running = True

    value = None
    value_changed = False
    command_timeout = 0
    prev_timestamp = 0

    try:
        while running:
            # keyboard input commands
            try:
                c = Terminal.inst().read()
                key = Terminal.inst().key()

                if c:
                    # split the commande line
                    args = [
                        arg
                        for arg in (value[1:].split(' ')
                                    if value and value.startswith(':') else [])
                        if arg
                    ]
                    if value and value[-1] == ' ':
                        args.append('')

                    # update the current type command
                    commands_handler.process_char(c, args)

                if key:
                    if key == 'KEY_ESCAPE':
                        # cancel command
                        value = None
                        value_changed = True
                        command_timeout = 0

                    # split the commande line
                    args = [
                        arg
                        for arg in (value[1:].split(' ')
                                    if value and value.startswith(':') else [])
                        if arg
                    ]
                    if value and value[-1] == ' ':
                        args.append('')

                    # process on the arguments
                    args = commands_handler.process_key(
                        key, args,
                        Terminal.inst().mode == Terminal.MODE_COMMAND)

                    if args:
                        # regen the updated commande ligne
                        value = ":" + ' '.join(args)
                        value_changed = True
                        command_timeout = 0

                    view_service.on_key_pressed(key)

                    if key == 'KEY_ESCAPE':
                        # was in command me, now in default mode
                        Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                # @todo move the rest to command_handler
                if c:
                    if value and value[0] == ':':
                        if c == '\b':
                            # backspace, erase last command char
                            value = value[:-1] if value else None
                            value_changed = True
                            command_timeout = time.time()

                        elif c != '\n':
                            # append to the advanced command value
                            value += c
                            value_changed = True
                            command_timeout = time.time()

                        elif c == '\n':
                            result = commands_handler.process_cli(value)
                            command_timeout = 0

                            if not result:
                                # maybe an application level command
                                if value == ':q' or value == ':quit':
                                    running = False

                                elif value.startswith(':x '):
                                    # manually exit position at market
                                    # @todo move as command
                                    target = value[3:]

                                    if target == "all" or target == "ALL":
                                        Terminal.inst().action(
                                            "Send close to market command for any positions",
                                            view='status')
                                        trader_service.command(
                                            Trader.COMMAND_CLOSE_ALL_MARKET,
                                            {})
                                    else:
                                        Terminal.inst().action(
                                            "Send close to market command for position %s"
                                            % (target, ),
                                            view='status')
                                        trader_service.command(
                                            Trader.COMMAND_CLOSE_MARKET,
                                            {'key': target})

                            # clear command value
                            value_changed = True
                            value = None

                            # use default mode
                            Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                    elif c != '\n':
                        # initial command value
                        value = "" + c
                        value_changed = True
                        command_timeout = time.time()

                        if value and value[0] == ':':
                            # use command mode
                            Terminal.inst().set_mode(Terminal.MODE_COMMAND)

                    if value and value[0] != ':':
                        # direct key

                        # use default mode
                        Terminal.inst().set_mode(Terminal.MODE_DEFAULT)

                        try:
                            result = commands_handler.process_accelerator(key)

                            # @todo convert to Command object accelerator
                            if not result:
                                result = True

                                # display views @todo must be managed by view_service
                                if value == 'C':
                                    Terminal.inst().clear_content()
                                elif value == 'D':
                                    Terminal.inst().switch_view('debug')
                                elif value == 'I':
                                    Terminal.inst().switch_view('content')
                                elif value == 'F':
                                    Terminal.inst().switch_view('strategy')
                                elif value == 'S':
                                    Terminal.inst().switch_view('stats')
                                elif value == 'P':
                                    Terminal.inst().switch_view('perf')
                                elif value == 'X':
                                    Terminal.inst().switch_view('position')
                                elif value == 'O':
                                    Terminal.inst().switch_view('order')
                                elif value == 'T':
                                    Terminal.inst().switch_view('ticker')
                                elif value == 'A':
                                    Terminal.inst().switch_view('account')
                                elif value == 'M':
                                    Terminal.inst().switch_view('market')
                                elif value == 'Q':
                                    Terminal.inst().switch_view('asset')
                                elif value == 'N':
                                    Terminal.inst().switch_view('signal')

                                elif value == '?':
                                    # ping services and workers
                                    watchdog_service.ping(1.0)

                                elif value == ' ':
                                    # a simple mark on the terminal
                                    Terminal.inst().notice(
                                        "Trading time %s" %
                                        (datetime.fromtimestamp(
                                            strategy_service.timestamp).
                                         strftime('%Y-%m-%d %H:%M:%S')),
                                        view='status')

                                elif value == 'a':
                                    if notifier_service:
                                        notifier_service.command(
                                            Notifier.COMMAND_TOGGLE, {
                                                'notifier': "desktop",
                                                'value': "audible"
                                            })
                                elif value == 'n':
                                    if notifier_service:
                                        notifier_service.command(
                                            Notifier.COMMAND_TOGGLE, {
                                                'notifier': "desktop",
                                                'value': "popup"
                                            })
                                elif value == '%':
                                    if view_service:
                                        view_service.toggle_percent()
                                elif value == ',':
                                    if view_service:
                                        view_service.toggle_group()
                                elif value == '!':
                                    if view_service:
                                        view_service.toggle_datetime_format()
                                else:
                                    result = False

                            if result:
                                value = None
                                value_changed = True
                                command_timeout = 0

                        except Exception as e:
                            siis_logger.error(repr(e))
                            traceback_logger.error(traceback.format_exc())

            except IOError:
                pass
            except Exception as e:
                siis_logger.error(repr(e))
                traceback_logger.error(traceback.format_exc())

            # display advanced command only
            if value_changed:
                if value and value.startswith(':'):
                    Terminal.inst().message("Command: %s" % value[1:],
                                            view='command')
                else:
                    Terminal.inst().message("", view='command')

            # clear input if no char hit during the last MAX_CMD_ALIVE
            if value and not value.startswith(':'):
                if (command_timeout > 0) and (time.time() - command_timeout >=
                                              MAX_CMD_ALIVE):
                    value = None
                    value_changed = True
                    Terminal.inst().info("Current typing canceled",
                                         view='status')

            try:
                # display strategy tarding time (update max once per second)
                if strategy_service.timestamp - prev_timestamp >= 1.0:
                    mode = "live"
                    if trader_service.backtesting:
                        mode = "backtesting"
                    elif trader_service.paper_mode:
                        mode = "paper-mode"

                    Terminal.inst().message(
                        "%s - %s" %
                        (mode,
                         datetime.fromtimestamp(strategy_service.timestamp).
                         strftime('%Y-%m-%d %H:%M:%S')),
                        view='notice')
                    prev_timestamp = strategy_service.timestamp

                # synchronous operations here
                watcher_service.sync()
                trader_service.sync()
                strategy_service.sync()

                if monitor_service:
                    monitor_service.sync()

                if view_service:
                    view_service.sync()

                if notifier_service:
                    notifier_service.sync()

                Terminal.inst().update()

            except BaseException as e:
                siis_logger.error(repr(e))
                traceback_logger.error(traceback.format_exc())

            # don't waste CPU time on main thread
            time.sleep(LOOP_SLEEP)

    finally:
        Terminal.inst().restore_term()

    Terminal.inst().info("Terminate...")
    Terminal.inst().flush()

    commands_handler.terminate(options) if commands_handler else None
    commands_handler = None

    # service terminate
    monitor_service.terminate() if monitor_service else None
    strategy_service.terminate() if strategy_service else None
    trader_service.terminate() if trader_service else None
    watcher_service.terminate() if watcher_service else None
    view_service.terminate() if view_service else None
    notifier_service.terminate() if notifier_service else None

    Terminal.inst().info("Saving database...")
    Terminal.inst().flush()

    Database.terminate()

    watchdog_service.terminate() if watchdog_service else None

    Terminal.inst().info("Bye!")
    Terminal.inst().flush()

    Terminal.terminate()
Beispiel #4
0
def application(argv):
    fix_thread_set_name()

    options = {
        'identity': 'real',
        'config-path': './user/config',
        'connectors-path': './user/config/connectors',
        'log-path': './user/log',
        'reports-path': './user/reports',
        'markets-path': './user/markets',
        'log-name': 'connector.log',
        'default': "connector.json",
        'connector-config': "",
        'database': {
            'name': "siis",
            'type': "pgsql",
            'host': "127.0.0.1",
            'port': 5432,
            'user': "******",
            'password': "******"
        },
        'cache': {
            'name': "siis",
            'type': "redis",
            'host': "127.0.0.1",
            'port': 6379,
            'user': "******",
            'password': "******"
        },
        'strategy': {
            'protocol': "tcp",
            'host': "127.0.0.1",
            'port': 5600
        },
        'connector': {
            'name': "",
            'host': ""
        },
        'markets': {},
        'connectors': {
            'binance.com': {
                'classpath': 'connectors.binance.connector.BinanceConnector',
            },
            'bitmex.com': {
                'classpath': 'connectors.bitmex.connector.BitMexConnector',
            },
            'ig.com': {
                'classpath': 'connectors.ig.connector.IGConnector',
            },
        }
    }

    # create initial siis data structure if necessary
    install(options)

    siis_log = SiisLog(options, "uterm")
    siis_logger = logging.getLogger('siis.connector')

    if len(argv) > 1:
        # utc or local datetime ?
        for n, arg in enumerate(argv):
            if arg.startswith('-'):
                if (arg == '--fetch' or arg == '-F'):
                    # use the fetcher
                    options['fetch'] = True
                elif (arg == '--spec' or arg == '-S') and n + 1 < len(argv):
                    # fetcher data history option
                    options['option'] = argv[n + 1]

                elif arg == '--binarize':
                    # use the binarizer
                    options['binarize'] = True

                elif arg == '--sync':
                    # use the syncer
                    options['sync'] = True

                elif (arg == '--connector'
                      or arg == '-c') and n + 1 < len(argv):
                    # connector conf filename
                    options['connector-config'] = argv[n + 1]

                elif (arg == '--from' or arg == '-f') and n + 1 < len(argv):
                    # if backtest from date (if ommited use whoole data) date format is "yyyy-mm-dd-hh:mm:ss", fetch, binarize to date
                    options['from'] = datetime.datetime.strptime(
                        argv[n + 1], '%Y-%m-%dT%H:%M:%S').replace(tzinfo=UTC())
                elif (arg == '--to' or arg == '-t') and n + 1 < len(argv):
                    # if backtest to date (can be ommited), fetch, binarize to date
                    options['to'] = datetime.datetime.strptime(
                        argv[n + 1], '%Y-%m-%dT%H:%M:%S').replace(tzinfo=UTC())
                elif (arg == '--last' or arg == '-l') and n + 1 < len(argv):
                    # fetch the last n data history
                    options['last'] = int(argv[n + 1])

                elif (arg == '--market' or arg == '-m') and n + 1 < len(argv):
                    # fetch, binarize the data history for this market
                    options['market'] = argv[n + 1]

                elif (arg == '--timeframe'
                      or arg == '-s') and n + 1 < len(argv):
                    # fetch, binarize base timeframe
                    options['timeframe'] = argv[n + 1]
                elif (arg == '--cascaded='
                      or arg == '-C') and n + 1 < len(argv):
                    # fetch cascaded ohlc generation
                    options['cascaded'] = argv[n + 1]

                elif arg == '--read-only':
                    options['read-only'] = True
                elif arg == '--check-data':
                    options['check-data'] = True

                elif arg == '--version' or arg == '-v':
                    print('%s %s' % (APP_SHORT_NAME, '.'.join(
                        [str(x) for x in APP_VERSION])))
                    sys.exit(0)

                elif arg == '--help' or arg == '-h':
                    display_cmd_line_help()
                    sys.exit(0)

        # replay
        if options.get('replay', False):
            if options.get('from') is None or options.get('to') is None:
                del options['replay']
                print("Replay need from= and to= date time")
                sys.exit(-1)

    if not options['connector-config']:
        print("Connector configuration filename must be specified")
        sys.exit(-1)

    #
    # config
    #

    parse_config(siis_logger, options)
    parse_connector_spec(siis_logger, options)

    siis_log.upgrade(options)

    #
    # binarizer
    #

    if options.get('binarize'):
        if options.get('market') and options.get('from') and options.get(
                'to') and options.get('connector'):
            do_binarizer(options, siis_logger)
        else:
            display_cmd_line_help()

        sys.exit(0)

    #
    # fetcher mode
    #

    if options.get('fetch'):
        if options.get('market') and options.get('connector') and options.get(
                'timeframe'):
            do_fetcher(options, siis_logger)
        else:
            display_cmd_line_help()

        sys.exit(0)

    #
    # running mode
    #

    print("Starting SIIS simple runner using %s..." %
          options['connector-config'])
    print("Hit CTRL-C twice to terminate")

    if options.get('replay'):
        print("Process a replay.")

    # monitoring service
    # print("Starting monitor service...")
    # monitor_service = MonitorService(options)
    # monitor_service.start()

    # database manager
    Database.create(options)
    Database.inst().setup(options)

    print("Starting connector handler...")
    handler = DefaultHandler(options)

    handler.init(options)
    handler.start()

    run(siis_logger, handler)

    print("Terminate...")
    handler.stop()
    handler.terminate()

    handler = None

    print("Saving database...")
    Database.terminate()

    print("Bye!")