Пример #1
0
def recreate_trading_api_periodically():
    global trading_api
    while True:
        # every 10 minutes, recreate session.
        time.sleep(600)
        trading_api_mutex.acquire()
        try:
            trading_api = TradingAPI(credentials=credentials)
            trading_api.connect()
            trading_api.get_account_info()
        except:
            logger.error('failed to recreate TradingAPI.')
        finally:
            trading_api_mutex.release()
Пример #2
0
def cli(username: str,
        password: str,
        int_account: int,
        log_level: str,
        log_directory: str = None):
    """ Retrieves a session_id from Degiro Quotecast API."""

    from trading.api import API
    from trading.pb.trading_pb2 import Credentials

    credentials = Credentials(int_account=int_account,
                              username=username,
                              password=password)
    api = API(credentials=credentials)

    api.connect()

    click.secho('Connection done !', bg='blue')

    click.secho(f'Session id : {api.connection_storage.session_id}', bg='blue')
Пример #3
0
# SETUP CREDENTIALS
int_account = config_dict['int_account']
username = config_dict['username']
password = config_dict['password']
credentials = Credentials(
    int_account=int_account,
    username=username,
    password=password,
)

# SETUP TRADING API
trading_api = TradingAPI(credentials=credentials)

# CONNECT
trading_api.connect()

# FETCH DATA - MESSAGE
products_config = trading_api.get_products_config(raw=False)

# DISPLAY - MESSAGE
for item in products_config.values:
    print(item)

# FETCH DATA - DICT
products_config_dict = trading_api.get_products_config(raw=True)
products_config_pretty = json.dumps(
    products_config_dict,
    sort_keys=True,
    indent=4,
)
Пример #4
0
def trading_api(credentials):
    trading_api = TradingAPI(credentials=credentials)
    trading_api.connect()

    return trading_api
class DegiroController:
    CHOICES = [
        "companynews",
        "hold",
        "lastnews",
        "login",
        "logout",
        "lookup",
        "pending",
        "q",
        "quit",
        "topnews",
    ]

    @staticmethod
    def filter_current_positions(
        portfolio: Update.Portfolio, ) -> pd.DataFrame:
        """Filter the positions in order to keep only held ones.

        Parameters
        ----------
        portfolio : Update.Portfolio
            Portfolio returned from the API.

        Returns
        -------
        pd.DataFrame
            Filtered portfolio.
        """

        # CONVERT TO DATAFRAME
        portfolio_dict = pb_handler.message_to_dict(message=portfolio)
        positions = pd.DataFrame(portfolio_dict["values"])

        # SETUP MASK
        mask_product = positions["positionType"] == "PRODUCT"
        mask_not_empty = positions["size"] > 0
        mask_current_position = mask_product & mask_not_empty

        # FILTER
        positions = positions[mask_current_position]

        return positions

    def __init__(self, credentials: Credentials):
        self.__default_credentials = credentials
        self.__trading_api = TradingAPI(credentials=credentials)

        self.__degiro_parser = argparse.ArgumentParser(
            add_help=False,
            prog="degiro",
        )
        self.__degiro_parser.add_argument("cmd", choices=self.CHOICES)

    def q(self, _):
        """Process Q command - quit the menu"""

        return False

    def quit(self, _):
        """Process Quit command - quit the program"""

        return True

    def fetch_additional_information(
        self,
        positions: pd.DataFrame,
    ) -> pd.DataFrame:
        """Fetch extra information about the positions like :
            - name
            - isin
            - symbol
            - ...

        Parameters
        ----------
        positions : pd.DataFrame
            Positions from which we want extra fields.

        Returns
        -------
        pd.DataFrame
            Positions with additional data.
        """

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # EXTRACT POSITIONS IDS
        positions_ids = positions["id"].astype("int32").tolist()

        # FETCH EXTRA DATA
        request = ProductsInfo.Request()
        request.products.extend(positions_ids)
        products_info_pb = trading_api.get_products_info(
            request=request,
            raw=False,
        )

        # CONVERT TO DICT
        products_info_dict = pb_handler.message_to_dict(
            message=products_info_pb, )

        # CONVERT TO DATAFRAME
        products_info = pd.DataFrame(products_info_dict["values"].values())

        # MERGE DATA WITH POSITIONS
        positions_full = pd.merge(positions, products_info, on="id")

        return positions_full

    def fetch_current_positions(self) -> pd.DataFrame:
        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # FETCH HELD PRODUCTS
        request_list = Update.RequestList()
        request_list.values.extend([
            Update.Request(
                option=Update.Option.PORTFOLIO,
                last_updated=0,
            ),
        ])

        update_pb = trading_api.get_update(
            request_list=request_list,
            raw=False,
        )
        positions_partial = self.filter_current_positions(
            portfolio=update_pb.portfolio, )

        # FETCH ADDITIONAL DATA ON PRODUCTS
        positions = self.fetch_additional_information(
            positions=positions_partial, )

        return positions

    def help(self):
        message = ("Degiro:\n"
                   "   companynews view news about a company with it's isin\n"
                   "   hold        view holdings\n"
                   "   lastnews    view latest news\n"
                   "   login       connect to degiro's api\n"
                   "   logout      disconnect from degiro's api\n"
                   "   lookup      view search for a product by name\n"
                   "   pending     view pending orders\n"
                   "   topnews     view top news preview\n")

        print(message)

    def login(self, l_args):
        """Connect to Degiro's API."""

        # PARSING ARGS
        default_credentials = self.__default_credentials
        parser = argparse.ArgumentParser(
            add_help=False,
            prog="login",
        )
        parser.add_argument(
            "-u",
            "--username",
            dest="username",
            type=str,
            default=default_credentials.username,
            help="Username in Degiro's account.",
        )
        parser.add_argument(
            "-p",
            "--password",
            dest="password",
            type=str,
            default=default_credentials.password,
            help="Password in Degiro's account.",
        )
        parser.add_argument(
            "-o",
            "--otp",
            dest="otp",
            type=int,
            default=None,
            help="One time password (2FA).",
        )
        parser.add_argument(
            "-s",
            "--topt-secret",
            dest="topt_secret",
            type=str,
            default=None,
            help="TOTP SECRET (2FA).",
        )
        ns_parser = parse_known_args_and_warn(parser, l_args)

        if ns_parser:
            credentials = Credentials()
            credentials.CopyFrom(default_credentials)
            credentials.username = ns_parser.username
            credentials.password = ns_parser.password

            if ns_parser.otp is not None:
                credentials.one_time_password = ns_parser.otp
            if ns_parser.topt_secret is not None:
                credentials.totp_secret_key = ns_parser.topt_secret

            self.__trading_api = TradingAPI(credentials=credentials)

            self.__trading_api.connect()
            self.setup_extra_credentials()

            print("You are now logged in !")

    def logout(self, _):
        """Log out from Degiro's API."""

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        trading_api.logout()

        print("You are now logged out !")

    def setup_extra_credentials(self):
        trading_api = self.__trading_api
        client_details_table = trading_api.get_client_details()
        int_account = client_details_table["data"]["intAccount"]
        trading_api.credentials.int_account = int_account

    def hold(self, _):
        """Display held products."""

        # FETCH HELD PRODUCTS
        positions = self.fetch_current_positions()

        # FORMAT DATAFRAME
        selected_columns = [
            "symbol",
            "size",
            "price",
            "closePrice",
            "breakEvenPrice",
        ]
        formatted_columns = [
            "Stonk",
            "Size",
            "Last Price",
            "Close Price",
            "Break Even Price",
        ]
        fmt_positions = positions[selected_columns].copy(deep=True)
        fmt_positions.columns = formatted_columns

        fmt_positions["% Change"] = positions["price"]
        fmt_positions["% Change"] -= fmt_positions["Close Price"]
        fmt_positions["% Change"] /= fmt_positions["Close Price"]
        fmt_positions["% Change"] = fmt_positions["% Change"].round(3)

        # DISPLAY DATAFRAME
        print(fmt_positions)

    def topnews(self, _):
        """Display pending orders."""

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # FETCH DATA
        news = trading_api.get_top_news_preview(raw=True)

        # DISPLAY DATA
        for article in news["data"]["items"]:
            print("date", article["date"])
            print("lastUpdated", article["lastUpdated"])
            print("category", article["category"])
            print("title", article["title"])
            print("brief", article["brief"])
            print("---")

    def lookup(self, l_args):
        """Search for products by their name."""

        # PARSING ARGS
        parser = argparse.ArgumentParser(
            add_help=False,
            prog="lookup",
        )
        parser.add_argument(
            "search_text",
            type=str,
            help="Name of the company or a text.",
        )
        parser.add_argument(
            "-l",
            "--limit",
            type=int,
            default=10,
            help="Number of result expected (0 for unlimited).",
        )
        parser.add_argument(
            "-o",
            "--offset",
            type=int,
            default=0,
            help="To use an offset.",
        )
        ns_parser = parse_known_args_and_warn(parser, l_args)

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # SETUP REQUEST
        limit = ns_parser.limit
        offset = ns_parser.offset
        search_text = ns_parser.search_text
        request_lookup = ProductSearch.RequestLookup(
            search_text=search_text,
            limit=limit,
            offset=offset,
        )

        # FETCH DATA
        products = trading_api.product_search(
            request=request_lookup,
            raw=True,
        )

        # DISPLAY DATA
        if "products" in products:
            products_df = pd.DataFrame(products["products"])
            products_selected = products_df[[
                "name",
                "isin",
                "symbol",
                "productType",
                "currency",
                "closePrice",
                "closePriceDate",
            ]]
            print(products_selected)
        else:
            print("0 results found.")

    def lastnews(self, _):
        """Display latest news."""

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # SETUP REQUEST
        request = LatestNews.Request(
            offset=0,
            languages="en,fr",
            limit=20,
        )

        # FETCH DATA
        news = trading_api.get_latest_news(request=request, raw=True)

        # DISPLAY DATA
        for article in news["data"]["items"]:
            print("date", article["date"])
            print("title", article["title"])
            print("content", article["content"])
            print("---")

    def companynews(self, l_args):
        """Display news related to a company using its ISIN."""

        # PARSING ARGS
        parser = argparse.ArgumentParser(
            add_help=False,
            prog="companynews",
        )
        parser.add_argument(
            "isin",
            type=str,
            help="ISIN code of the company.",
        )
        ns_parser = parse_known_args_and_warn(parser, l_args)

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # SETUP REQUEST
        isin = ns_parser.isin
        request = NewsByCompany.Request(
            isin=isin,
            limit=10,
            offset=0,
            languages="en,fr",
        )

        # FETCH DATA
        news = trading_api.get_news_by_company(
            request=request,
            raw=True,
        )

        # DISPLAY DATA
        for article in news["data"]["items"]:
            print("date", article["date"])
            print("title", article["title"])
            print("content", article["content"])
            print("isins", article["isins"])
            print("---")

    def pending(self, _):
        """Display pending orders."""

        # GET ATTRIBUTES
        trading_api = self.__trading_api

        # SETUP REQUEST
        request_list = Update.RequestList()
        request_list.values.extend([
            Update.Request(option=Update.Option.ORDERS, last_updated=0),
        ])

        # FETCH DATA
        update = trading_api.get_update(request_list=request_list)

        # FORMAT DATA
        update_dict = pb_handler.message_to_dict(message=update)
        orders_df = pd.DataFrame(update_dict["orders"]["values"])

        # DISPLAY DATA
        if orders_df.shape[0] == 0:
            print("No pending orders.")
        else:
            print(orders_df)

    def switch(self, an_input: str):
        """Process and dispatch input

        Returns
        -------
        True, False or None
            False - quit the menu
            True - quit the program
            None - continue in the menu
        """

        try:
            degiro_parser = self.__degiro_parser

            (known_args,
             other_args) = degiro_parser.parse_known_args(an_input.split())

            return getattr(
                self,
                known_args.cmd,
                lambda: "Command not recognized!",
            )(other_args)
        except Exception as e:
            print(e)
            print("")

            return None