Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package name.abuchen.portfolio.snapshot.trades;

import static name.abuchen.portfolio.junit.PortfolioBuilder.amountOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import org.junit.Test;
Expand All @@ -18,6 +21,9 @@
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.snapshot.filter.PortfolioClientFilter;
import name.abuchen.portfolio.snapshot.security.LazySecurityPerformanceSnapshot;
import name.abuchen.portfolio.util.Interval;

@SuppressWarnings("nls")
public class TradeCollector6Test
Expand Down Expand Up @@ -58,7 +64,7 @@ public void testTradesSimpleOpenPosition() throws TradeCollectorException
}

@Test
public void testTradesProtitAndGrossProfitFIFOAndMovingAverage() throws TradeCollectorException
public void testTradesProfitAndGrossProfitFIFOAndMovingAverage() throws TradeCollectorException
{
Client client = new Client();

Expand Down Expand Up @@ -123,4 +129,110 @@ public void testTradesProtitAndGrossProfitFIFOAndMovingAverage() throws TradeCol
assertThat(secondTrade.getReturnMovingAverage(), closeTo(0.9737, 0.0001));
}

@Test
public void testMovingAverageTradesSeveralPortfolio() throws TradeCollectorException
{
Client client = new Client();

Security security = new SecurityBuilder().addTo(client);
new PortfolioBuilder(new Account("one")).buyPrice(security, "2022-01-01", 5, 100).addTo(client);
new PortfolioBuilder(new Account("two")).buyPrice(security, "2022-02-02", 5, 200).addTo(client);

TradeCollector collector = new TradeCollector(client, new TestCurrencyConverter());
List<Trade> trades = collector.collect(security);
Collections.sort(trades, (r, l) -> r.getStart().compareTo(l.getStart()));

assertThat(trades.size(), is(2));

Trade firstTrade = trades.get(0);

assertThat(firstTrade.getStart(), is(LocalDateTime.parse("2022-01-01T00:00")));
assertThat(firstTrade.getEntryValueMovingAverage(), is(Money.of(CurrencyUnit.EUR, amountOf(100 * 5))));

Trade secondTrade = trades.get(1);

assertThat(secondTrade.getStart(), is(LocalDateTime.parse("2022-02-02T00:00")));
assertThat(secondTrade.getEntryValueMovingAverage(), is(Money.of(CurrencyUnit.EUR, amountOf(200 * 5))));
}

@Test
public void testMovingAverageTradesSeveralPortfolio2() throws TradeCollectorException
{
Client client = new Client();

Security security = new SecurityBuilder().addPrice("2023-03-01", Values.Quote.factorize(200)) //
.addTo(client);
var p1 = new PortfolioBuilder(new Account("one")).buyPrice(security, "2022-01-01", 5, 100, 10, 10)
.buyPrice(security, "2022-02-01", 10, 100)
.sellPrice(security, "2022-03-01", 10, 150, 10, 10)
.addTo(client);

var p2 = new PortfolioBuilder(new Account("two")).buyPrice(security, "2022-01-02", 5, 200, 10, 10)
.buyPrice(security, "2022-02-02", 10, 200)
.sellPrice(security, "2022-03-02", 10, 150, 10, 10)
.addTo(client);

TradeCollector collector = new TradeCollector(client, new TestCurrencyConverter());
List<Trade> trades = collector.collect(security);
Collections.sort(trades, (r, l) -> r.getStart().compareTo(l.getStart()));

assertThat(trades.size(), is(4));

Trade firstClosedTrade = trades.get(0);

assertThat(firstClosedTrade.getStart(), is(LocalDateTime.parse("2022-01-01T00:00")));
assertThat(firstClosedTrade.getEnd().isPresent(), is(true));
// 1500 - (5*100 + 10*100) * 10/15 = 500
assertThat(firstClosedTrade.getProfitLossMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(500))));
// 1500+10+10 - (500-10-10 + 1000) * 10/15 = 533.33
assertThat(firstClosedTrade.getProfitLossMovingAverageWithoutTaxesAndFees(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(533.33))));

Trade secondClosedTrade = trades.get(1);

assertThat(secondClosedTrade.getStart(), is(LocalDateTime.parse("2022-01-02T00:00")));
assertThat(secondClosedTrade.getEnd().isPresent(), is(true));
// 1500 - (5*200 + 10*200) * 10/15 = -500
assertThat(secondClosedTrade.getProfitLossMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(-500))));
// 1500+10+10 - (1000-10-10 + 2000) * 10/15 = -466.666
assertThat(secondClosedTrade.getProfitLossMovingAverageWithoutTaxesAndFees(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(-466.67))));

Trade firstOpenTrade = trades.get(2);

assertThat(firstOpenTrade.getEnd().isPresent(), is(false));
assertThat(firstOpenTrade.getStart(), is(LocalDateTime.parse("2022-02-01T00:00")));
// 200 * 5 - (500 + 1000) * 5/15 = 500
assertThat(firstOpenTrade.getProfitLossMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(500))));
// 200 * 5 - (500-10-10 + 1000) * 5/15 = 506.666
assertThat(firstOpenTrade.getProfitLossMovingAverageWithoutTaxesAndFees(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(506.67))));

var snapshot = LazySecurityPerformanceSnapshot.create(new PortfolioClientFilter(p1).filter(client),
new TestCurrencyConverter(), Interval.of(LocalDate.MIN, LocalDate.now()));
var securityRecord = snapshot.getRecord(security).get();

assertThat(firstOpenTrade.getEntryValueMovingAverage(), is(securityRecord.getMovingAverageCost().get()));

Trade secondOpenTrade = trades.get(3);

assertThat(secondOpenTrade.getEnd().isPresent(), is(false));
assertThat(secondOpenTrade.getStart(), is(LocalDateTime.parse("2022-02-02T00:00")));
// 200 * 5 - (1000 + 2000) * 5/15 = 0
assertThat(secondOpenTrade.getProfitLossMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00))));
// 200 * 5 - (1000-10-10 + 2000) * 5/15 = 6.666
assertThat(secondOpenTrade.getProfitLossMovingAverageWithoutTaxesAndFees(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(6.67))));

snapshot = LazySecurityPerformanceSnapshot.create(new PortfolioClientFilter(p2).filter(client),
new TestCurrencyConverter(), Interval.of(LocalDate.MIN, LocalDate.now()));
securityRecord = snapshot.getRecord(security).get();

assertThat(secondOpenTrade.getEntryValueMovingAverage(), is(securityRecord.getMovingAverageCost().get()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static name.abuchen.portfolio.junit.PortfolioBuilder.sharesOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
import static org.junit.Assert.assertEquals;

import java.time.LocalDateTime;
Expand All @@ -19,6 +20,7 @@
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;

/**
* This is intended to be unit test for Trade (and by extension, TradeCollector)
Expand Down Expand Up @@ -55,6 +57,10 @@ public void testLong() throws TradeCollectorException
assertThat(trade1.getProfitLoss(), is(Money.of(CurrencyUnit.EUR, amountOf(180 - 100) * 5)));
assertThat(trade1.getReturn(), is(0.8));
assertEquals(trade1.getIRR(), 0.8, 0.0001);

assertThat(trade1.getEntryValueMovingAverage(), is(trade1.getEntryValue()));
assertThat(trade1.getProfitLossMovingAverage(), is(trade1.getProfitLoss()));
assertThat(trade1.getReturnMovingAverage(), is(trade1.getReturn()));
}

@Test
Expand Down Expand Up @@ -124,26 +130,10 @@ public void testShort() throws TradeCollectorException
assertThat(trade1.getProfitLoss(), is(Money.of(CurrencyUnit.EUR, amountOf(20 - 5) * 3)));
assertThat(trade1.getReturn(), is(0.75));
assertEquals(trade1.getIRR(), 0.75, 0.0001);
}

@Test
public void testShortMovingAverageCostDoesNotDivideByZero() throws TradeCollectorException
{
Client client = new Client();
TradeCollector collector = new TradeCollector(client, new TestCurrencyConverter());

var portfolio = new PortfolioBuilder();
portfolio.addTo(client);

Security securityShort = new SecurityBuilder().addTo(client);
portfolio.sellPrice(securityShort, "2024-01-01", 3.0, 20.0).buyPrice(securityShort, "2024-12-31", 3.0, 5.0);

List<Trade> trades = collector.collect(securityShort);
Trade trade = trades.get(0);

Money movingAverageEntryValue = trade.getEntryValueMovingAverage();

assertThat(movingAverageEntryValue, is(Money.of(CurrencyUnit.EUR, 0L)));
assertThat(trade1.getEntryValueMovingAverage(), is(trade1.getEntryValue()));
assertThat(trade1.getProfitLossMovingAverage(), is(trade1.getProfitLoss()));
assertThat(trade1.getReturnMovingAverage(), is(trade1.getReturn()));
}

@Test
Expand Down Expand Up @@ -219,4 +209,48 @@ public void testShortMultipleSells() throws TradeCollectorException
assertThat(trade2.getProfitLoss(), is(Money.of(CurrencyUnit.EUR, amountOf(1 * 120 + 2 * 50 - 0))));
assertThat(trade2.getReturn(), is(1.0));
}

@Test
public void testShortMultipleSellsMovingAverage() throws TradeCollectorException
{
Client client = new Client();
TradeCollector collector = new TradeCollector(client, new TestCurrencyConverter());
List<Trade> trades;

var port = new PortfolioBuilder();
port.addTo(client);

Security securityShort = new SecurityBuilder().addPrice("2025-03-01", Values.Quote.factorize(30)).addTo(client);
port.sellPrice(securityShort, "2024-01-01", 2.0, 100.0).sellPrice(securityShort, "2024-02-01", 3.0, 120.0)
.sellPrice(securityShort, "2024-03-01", 2.0, 50.0)
.buyPrice(securityShort, "2024-12-31", 4.0, 20.0);

trades = collector.collect(securityShort);
assertThat(trades.size(), is(2));

Trade trade1 = trades.get(0);
assertThat(trade1.isClosed(), is(true));
assertThat(trade1.isLong(), is(false));
assertThat(trade1.getShares(), is(sharesOf(4)));
// entryAmount = (2 * 100 + 3 * 120 + 2 * 50) * 4 / 7 = 377.142857
var exitAmount = 4 * 20;
assertThat(trade1.getEntryValueMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(377.14))));
assertThat(trade1.getExitValue(), is(Money.of(CurrencyUnit.EUR, amountOf(exitAmount))));
assertThat(trade1.getProfitLossMovingAverage(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(377.14 - exitAmount))));
assertThat(trade1.getReturnMovingAverage(), closeTo(1.0 - (double) exitAmount / 377.14, 0.0001));
assertEquals(0.8710, trade1.getIRR(), 0.0001);

Trade trade2 = trades.get(1);
// entryAmount2 = (2 * 100 + 3 * 120 + 2 * 50) * 3 / 7 = 282.85714
var exitAmount2 = 3 * 30;

assertThat(trade2.isClosed(), is(false));
assertThat(trade2.isLong(), is(false));
assertThat(trade2.getShares(), is(sharesOf(3)));
assertThat(trade2.getEnd().isPresent(), is(false));
assertThat(trade2.getProfitLossMovingAverage(), is(Money.of(CurrencyUnit.EUR, amountOf(282.86 - exitAmount2))));
assertThat(trade2.getReturnMovingAverage(), is(1 - exitAmount2 / 282.86));
}
}

This file was deleted.

Loading