Fx interest rate arbitrage 001 --- monitoring opportunities

 

Aim

This is a strategy about interest rate arbitrage. The first stage of this project is building a system to scan for an opportunity.

  • Found interest rate arbitrage opportunities
  • Understand the interest rate update period

System overview 



Action details

001

  • Collectors will gather interest rate information from different brokers and accounts
  • They will send those information to analysis
broker account symbol swap_long swap_short spread create_time
xxx xxx USDCAD -4 -4 10 xxx
xxx xxx USDHKD 6 -6 15 xxx

 



002

  • Searcher will search for the opportunities
  • It will search after interest rate is updated
  • It will records the updated item in a time series manner for update pattern observation

 

003

  • Detail information of the opportunities 

broker account symbol spread swap_long swap_short swap_side side swap_total action_group
xxx xxx USDCAD 10 -4 -4 -4 long 2 1
xxx xxx USDCAD 10 -6 6 6 short 2 1
xxx xxx USDAUD 15 -3 -3 -3 long 1 2
xxx xxx USDAUD 15 -4 4 4 short 1 2

  • We use email notification ✉️ with attachments to keep clients informed the latest updates

Analysis table

Here shows one arbitrage action created by the searcher. With the given symbol USDCNH, the arbitrage should be able to cover the transaction cost after 12 days. To be able to make a profitable strategy, we have follow up questions:

  • How long does the interest rate stay the same?
  • How much money we need to put in the broker account to keep the position given that the price of the product could be fluctuate.
  • What leverage level we can use to generate enough profit?

The search algorithm

We would like to find results as following:

  • EURUSD.long, USDEUR.short
  • EURUSD.long, USDCAD.long, EURCAD.short
  • ...

Clearly, the data is linked by the base and quote currency. To search for all combination, we can choose BFS/DFS.

Please notice that we have to prevent duplicated searching by pruning the search tree, for example, here is an reverse searching:

  • EURUSD.long, USDCAD.long, EURCAD.short
  • EURUSD.long, EURCAD.short, USDCAD.long

the above 2 results are the same. 

Here is part of the source code to implement the searching:

    def __Arb_Actions_Dfs(self
                          , ir_info: List[IR_Element]
                          , result: List[List[IR_Element]]
                          , path: List[IR_Element]
                          , seen: Set[str]
                          , max_level: int
                          , current_level: int) -> None:
        for info_ele in ir_info:
            for side in ["long", "short"]:
                if side == "short" and current_level == 0:
                    continue

                ele_key = self.__Get_Seen_Key(info_ele=info_ele, side=side)
                if ele_key in seen:
                    continue

                if len(path) == 0 or self.__Is_Expandable(ir_ele=path[-1]
                                                          , next_ir_ele=info_ele
                                                          , next_side=side):
                    seen.add(ele_key)
                    path.append(self.__Get_Expand_IR_Element(path=path, next_ir_ele=info_ele, next_side=side))

                    result_num = len(result)
                    if self.__Is_Market_Neutral(path=path):
                        # found answer, no need to search further
                        result.append(copy.deepcopy(path))
                    elif (current_level + 1) < max_level:
                        # control search level, only search when smaller than max_level
                        self.__Arb_Actions_Dfs(ir_info=ir_info
                                               , result=result
                                               , path=path
                                               , seen=seen
                                               , max_level=max_level
                                               , current_level=current_level+1)
                    path.pop()
                    if current_level == 0:
                        # only remove seen at the root to prevent reverse searching
                        seen.clear()
                    elif result_num == len(result): # no new soultion found when the values are the same
                        # remove seen when no new solution found, so that we can retry the symbol
                        seen.remove(ele_key)
                        

To prevent the reverse searching, the "seen" set will be clear only when the root currency finished its searching.

Comments