Saw a post about currency arbitrage in Prolog on Hacker News, and wrote a more general solution:
:- use_module(library(lists)). :- use_module(library('http/http_client')). :- use_module(library('http/json')). %! find_chains(+MaxLength) is semidet % % Prints possible profitable conversion chains. find_chains(MaxLength) :- http_get('http://fx.priceonomics.com/v1/rates/', Json, []), atom_json_term(Json, json(Prices), []), % convert the json atom to a prolog term and extract the list of prices abolish(price/3), % clear any old price facts from the database assert_prices(Prices), % update the database with current prices % now get the set of all solutions for the predicate % build_chain/3, and build a list of the results setof(chain(Symbols, Profit), build_chain(MaxLength, Symbols, Profit), Chains), % print the results write(Chains). %! assert_prices(+List) is det % % Adds the list of prices to the dynamic database. % assert_prices([]). assert_prices([SymbolPair = Price | Rest]) :- atomic_list_concat([From, To], '_', SymbolPair), atom_number(Price, Num), assertz(price(From, To, Num)), assert_prices(Rest). %! build_chain(+MaxLength, -Symbols, -Profit) is nondet % % Finds a profitable chain build_chain(MaxLength, Symbols, Profit) :- price(Dest, Next, _), Dest \= Next, % pick a starting symbol (ignore repeats), with backtracking build_chain(MaxLength, Dest, [Dest], 1.0, Symbols, Profit). %! build_chain(+Count, Dest, SymbolsIn, ProfitIn, SymbolsOut, ProfitOut) % % Finds a possible next link in the chain, checks to see if it's a loop, otherwise recurses. build_chain(0, _, _, _, _, _) :- !, fail. % stop backtracking when we hit our maximum length build_chain(Count, Dest, SymbolsIn, ProfitIn, SymbolsOut, ProfitOut) :- price(A, B, P), % find a price record (backtracking over all of them) append(_, [A], SymbolsIn), % make sure the chain connects Profit is ProfitIn * P, % calculate our profit (B = Dest % do we have a loop? -> Profit > 1.0, % is it profitable? append(SymbolsIn, [B], SymbolsOut), % if so, then we're done ProfitOut = Profit ; \+ member(B, SymbolsIn), % if not a loop, make sure we don't repeat interior symbols append(SymbolsIn, [B], SymbolsNext), % add B to a temp list NextCount is Count - 1, % and recurse, decrementing our counter build_chain(NextCount, Dest, SymbolsNext, Profit, SymbolsOut, ProfitOut)).