If you have the accounts already listed in E2 down, you could enter this array formula in F2:
=ArrayFormula(IF(LEN(E2:E);SUMIF(C2:C;E2:E;D2:D)-SUMIF(B2:B;E2:E;D2:D);IFERROR(1/0)))
This would probably have the best performance of any alternative in this answer. However it relies on the account names already being populated.
This formula will return the entire table:
=ArrayFormula(QUERY(IF({1,0};TRANSPOSE(SPLIT(CONCATENATE(FILTER(B2:C;LEN(B2:B);LEN(C2:C))&CHAR(9));CHAR(9)));TRANSPOSE(SPLIT(CONCATENATE((FILTER(D2:D;LEN(B2:B);LEN(C2:C))*{-1,1})&CHAR(9));CHAR(9))));"select Col1, sum(Col2) group by Col1 label Col1 'Account', sum(Col2) 'Balance'";0))
But aside from being horribly unreadable, these type of "concatenate then split" formulae can have really poor performance for large data sets. So I would usually prefer to use a custom function in this situation:
function accountBalance(fromAccount, toAccount, amount) {
var result = [], output = [['Account', 'Balance']], from, to, value;
for (var i = 0; i < amount.length; i ++) {
from = fromAccount[i][0];
to = toAccount[i][0];
value = amount[i][0];
if (from && to) {
if (!(from in result)) result[from] = 0;
if (!(to in result)) result[to] = 0;
result[from] -= value;
result[to] += value;
}
}
for (var j in result) {
output.push([j, result[j]]);
}
return output;
}
And then in the spreadsheet cell, you would invoke:
=accountBalance(B2:B;C2:C;D2:D)