Fun with Prolog

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)).