maartenn / crypto-fiat-tracker

0 stars 0 forks source link

Add Year over Year Overview data #1

Open maartenn opened 11 months ago

maartenn commented 11 months ago

What In the java side we want to return the year over year data , I've already create class for this ( YearOverYearData ) . The data should be filled only on the first of january of every year. But also for the next year ( so I see the profit in this year SO FAR ). All the fields in the class should be filled with appropiate values. If these are filled correct then they need to be added to the Response class. Also consider writing some junittests.

Why to provide users with more info about their current holdings

@autopilot

codeautopilot[bot] commented 11 months ago

Potential solution

To implement the requested feature, we need to ensure that the YearOverYearData class is properly structured to hold the necessary data. We will then implement the logic to calculate the year over year data, ensuring it is only done on the first of January each year. The calculated data will be added to the Response class, and we will write unit tests to validate the functionality.

Code

For YearOverYearDataService.java:

import java.time.LocalDate;
import java.util.Optional;

public class YearOverYearDataService {

    public Optional<YearOverYearData> calculateYearOverYearData() {
        if (isFirstOfJanuary()) {
            YearOverYearData data = new YearOverYearData();
            // Calculate previous year's profit
            data.setPreviousYearProfit(calculatePreviousYearProfit());
            // Calculate current year's profit so far
            data.setCurrentYearProfitSoFar(calculateCurrentYearProfitSoFar());
            // ... set other fields as necessary
            return Optional.of(data);
        }
        return Optional.empty();
    }

    private boolean isFirstOfJanuary() {
        LocalDate today = LocalDate.now();
        return today.getDayOfMonth() == 1 && today.getMonthValue() == 1;
    }

    private double calculatePreviousYearProfit() {
        // Logic to calculate last year's profit
        return 0.0; // Placeholder for actual calculation
    }

    private double calculateCurrentYearProfitSoFar() {
        // Logic to calculate the profit for the current year up to today
        return 0.0; // Placeholder for actual calculation
    }

    // Additional methods as needed for calculations
}

For Response.java:

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL) // Include only non-null fields in the JSON
public class Response {

    // Other fields that are already part of the Response class
    // ...

    // The new YearOverYearData field
    private YearOverYearData yearOverYearData;

    // Constructor, getters, setters, and other methods for existing fields
    // ...

    // Getter and setter for the YearOverYearData field
    public YearOverYearData getYearOverYearData() {
        return yearOverYearData;
    }

    public void setYearOverYearData(YearOverYearData yearOverYearData) {
        this.yearOverYearData = yearOverYearData;
    }

    // Override the toString method if necessary to include the YearOverYearData field
    @Override
    public String toString() {
        // Implement the logic to return a string representation of the Response object,
        // including the YearOverYearData field.
        // ...
    }

    // If using a custom serializer, ensure that the YearOverYearData field is included
    // in the serialization process.
    // ...
}

For ScheduledTasks.java using Spring's @Scheduled:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.service.YearOverYearDataService;

@Component
public class ScheduledTasks {

    private final YearOverYearDataService yearOverYearDataService;

    @Autowired
    public ScheduledTasks(YearOverYearDataService yearOverYearDataService) {
        this.yearOverYearDataService = yearOverYearDataService;
    }

    // Runs at midnight on the first of January every year
    @Scheduled(cron = "0 0 0 1 1 *")
    public void calculateYearOverYearData() {
        yearOverYearDataService.calculateAndStoreYearOverYearData();
    }
}

For ResponseTests.java:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class ResponseTests {

    @Test
    void testYearOverYearDataAddedToResponse() {
        // Arrange
        YearOverYearData yearOverYearData = createTestYearOverYearData();
        Response response = new Response();

        // Act
        response.setYearOverYearData(yearOverYearData);

        // Assert
        assertNotNull(response.getYearOverYearData(), "YearOverYearData should not be null");
        assertEquals(yearOverYearData, response.getYearOverYearData(), "YearOverYearData should match the test data");
    }

    @Test
    void testResponseSerializationIncludesYearOverYearData() {
        // Arrange
        YearOverYearData yearOverYearData = createTestYearOverYearData();
        Response response = new Response();
        response.setYearOverYearData(yearOverYearData);

        // Act
        String jsonResponse = serializeResponseToJson(response);

        // Assert
        assertTrue(jsonResponse.contains("expectedFieldName"), "JSON should contain the YearOverYearData field");
        // Additional assertions to verify the content of the YearOverYearData in the JSON
    }

    // Additional tests for deserialization, edge cases, etc.

    private YearOverYearData createTestYearOverYearData() {
        // Create and return a test YearOverYearData object
    }

    private String serializeResponseToJson(Response response) {
        // Serialize the Response object to a JSON string and return it
    }
}

For YearOverYearDataTests.java:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class YearOverYearDataTests {

    @Test
    void testCalculationOnJanuaryFirst() {
        // Implement test logic
    }

    @Test
    void testCalculationOnNonJanuaryFirst() {
        // Implement test logic
    }

    @Test
    void testCurrentYearProfitCalculation() {
        // Implement test logic
    }

    @Test
    void testLeapYearHandling() {
        // Implement test logic
    }

    @Test
    void testAllFieldsFilled() {
        // Implement test logic
    }

    @Test
    void testEdgeCases() {
        // Implement test logic
    }

    // Add setup and teardown methods if needed
}

Remember to replace placeholders with actual logic and field names, and ensure that all methods are properly tested with unit tests.

Changes on YearOverYearDataService.java To implement the logic in `YearOverYearDataService.java`, follow these steps: 1. **Define the YearOverYearData Class Structure:** Ensure that the `YearOverYearData` class has fields to store the necessary data, such as previous year's profit, current year's profit so far, and any other relevant metrics. 2. **Implement Data Calculation Logic:** Create methods within `YearOverYearDataService` to calculate the year over year data. This will likely involve accessing historical financial data, calculating profits, and comparing them year over year. 3. **Implement Date Check Logic:** Add logic to determine if the current date is the first of January. This can be done using Java's `Calendar` or `LocalDate` classes. 4. **Implement Current Year Profit Calculation:** Write a method to calculate the profit for the current year so far. This will involve determining the start of the current fiscal year and calculating the profit from that date to the current date. 5. **Integrate with the Response Class:** Once the `YearOverYearData` is calculated, ensure that it is added to the `Response` class. This will likely involve modifying the `Response` class to include a new field for `YearOverYearData`. 6. **Testing:** Write unit tests in a separate file (`YearOverYearDataTests.java`) to ensure the calculations are correct and that the data is only calculated on the first of January. Here is a basic outline of what the `YearOverYearDataService.java` file might look like: ```java import java.time.LocalDate; import java.util.Optional; public class YearOverYearDataService { public Optional calculateYearOverYearData() { if (isFirstOfJanuary()) { YearOverYearData data = new YearOverYearData(); // Calculate previous year's profit data.setPreviousYearProfit(calculatePreviousYearProfit()); // Calculate current year's profit so far data.setCurrentYearProfitSoFar(calculateCurrentYearProfitSoFar()); // ... set other fields as necessary return Optional.of(data); } return Optional.empty(); } private boolean isFirstOfJanuary() { LocalDate today = LocalDate.now(); return today.getDayOfMonth() == 1 && today.getMonthValue() == 1; } private double calculatePreviousYearProfit() { // Logic to calculate last year's profit // This might involve querying a database or accessing a financial API return 0.0; // Placeholder for actual calculation } private double calculateCurrentYearProfitSoFar() { // Logic to calculate the profit for the current year up to today return 0.0; // Placeholder for actual calculation } // Additional methods as needed for calculations } ``` Remember to replace the placeholder calculation logic with actual implementations that access your data sources and perform the necessary calculations. Also, ensure that all methods are properly tested with unit tests in the `YearOverYearDataTests.java` file.
Changes on Response.java To address the task for the `Response.java` file, we need to add a new field for the `YearOverYearData` and ensure it is properly serialized. Here's a practical proposal to implement this: 1. Define the `YearOverYearData` field within the `Response` class. 2. Implement getter and setter methods for the new field. 3. Ensure that the `YearOverYearData` field is included in any serialization logic, such as when converting the `Response` object to JSON (if applicable). 4. Update the `Response` class's documentation to include the new field. Here's an example of how the `Response.java` file might look after these changes: ```java import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @JsonInclude(JsonInclude.Include.NON_NULL) // Include only non-null fields in the JSON public class Response { // Other fields that are already part of the Response class // ... // The new YearOverYearData field private YearOverYearData yearOverYearData; // Constructor, getters, setters, and other methods for existing fields // ... // Getter for the YearOverYearData field public YearOverYearData getYearOverYearData() { return yearOverYearData; } // Setter for the YearOverYearData field public void setYearOverYearData(YearOverYearData yearOverYearData) { this.yearOverYearData = yearOverYearData; } // Override the toString method if necessary to include the YearOverYearData field @Override public String toString() { // Implement the logic to return a string representation of the Response object, // including the YearOverYearData field. // ... } // If using a custom serializer, ensure that the YearOverYearData field is included // in the serialization process. // ... } // Don't forget to import the YearOverYearData class at the top of the file ``` After implementing the above changes, you should ensure that the `Response` object is correctly serialized with the new `YearOverYearData` field when it is returned to the client. This might involve updating existing serialization logic or writing new code to handle the serialization of the `YearOverYearData` field. Finally, make sure to test the serialization process, either manually or by writing automated tests, to confirm that the `YearOverYearData` is included in the serialized `Response` object as expected.
Changes on ScheduledTasks.java To address the task for the `ScheduledTasks.java` file, we need to create a scheduled task that will trigger the calculation of the year over year data on the first of January each year. Here's a practical proposal on how to implement this: 1. First, we need to use a scheduling framework like `@Scheduled` annotation in Spring Framework or `java.util.Timer` and `java.util.TimerTask` for plain Java applications. 2. We will use the `@Scheduled` annotation from Spring as it provides a more straightforward approach for this task. 3. We need to ensure that the `ScheduledTasks` class is annotated with `@Component` to be detected by Spring's component scanning. 4. We will create a method that will be executed by the scheduler. This method will call the service that calculates the year over year data. 5. We will use a cron expression to schedule the task to run at midnight on the first of January every year. Here is an example implementation using Spring's `@Scheduled`: ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import com.example.service.YearOverYearDataService; @Component public class ScheduledTasks { private final YearOverYearDataService yearOverYearDataService; @Autowired public ScheduledTasks(YearOverYearDataService yearOverYearDataService) { this.yearOverYearDataService = yearOverYearDataService; } // Runs at midnight on the first of January every year @Scheduled(cron = "0 0 0 1 1 *") public void calculateYearOverYearData() { yearOverYearDataService.calculateAndStoreYearOverYearData(); } } ``` In the above code: - `@Component` makes sure that Spring detects this class as a bean. - `@Autowired` is used to inject the `YearOverYearDataService` into this scheduled task class. - `@Scheduled(cron = "0 0 0 1 1 *")` defines the cron expression for the scheduled task. The cron expression here stands for "At 00:00:00 on the 1st day of the 1st month". Make sure that the `YearOverYearDataService` has a method `calculateAndStoreYearOverYearData()` that performs the actual calculation and storage of the year over year data. Please note that the cron expression is based on the server's local time zone. If you need to run the task in a specific time zone, you can add a `zone` attribute to the `@Scheduled` annotation. Lastly, don't forget to enable scheduling in your Spring application by adding `@EnableScheduling` to one of your configuration classes.
Changes on ResponseTests.java Since `ResponseTests.java` is a new file, we need to create unit tests from scratch to verify that the `YearOverYearData` is correctly added to the `Response` object and serialized as expected. Here's a practical proposal for implementing the unit tests: 1. **Setup Test Environment**: Import necessary testing libraries such as JUnit and any mocking frameworks like Mockito if needed. 2. **Create Test Data**: Define test data for `YearOverYearData` that will be used to instantiate a `Response` object. This data should reflect various scenarios, including edge cases. 3. **Test Addition of YearOverYearData to Response**: Write a test to ensure that when a `Response` object is created with `YearOverYearData`, the data is correctly assigned to the appropriate field within the `Response` object. 4. **Test Serialization**: Write tests to check that the `Response` object, when serialized (e.g., to JSON), includes the `YearOverYearData` with the correct values. This may involve using a JSON library to serialize the `Response` object and then asserting the expected structure and data in the JSON string. 5. **Test Deserialization**: Optionally, write tests to ensure that a `Response` object can be correctly deserialized from a JSON string that includes `YearOverYearData`. 6. **Edge Cases**: Include tests for edge cases, such as what happens if `YearOverYearData` is null or if certain fields within `YearOverYearData` are missing. 7. **Test Year Change**: Since the data should only be filled on the first of January, write a test that simulates the change of year and verifies that the `YearOverYearData` is only added to the `Response` on this date. Here's a skeleton of what the `ResponseTests.java` file might look like: ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class ResponseTests { @Test void testYearOverYearDataAddedToResponse() { // Arrange YearOverYearData yearOverYearData = createTestYearOverYearData(); Response response = new Response(); // Act response.setYearOverYearData(yearOverYearData); // Assert assertNotNull(response.getYearOverYearData(), "YearOverYearData should not be null"); assertEquals(yearOverYearData, response.getYearOverYearData(), "YearOverYearData should match the test data"); } @Test void testResponseSerializationIncludesYearOverYearData() { // Arrange YearOverYearData yearOverYearData = createTestYearOverYearData(); Response response = new Response(); response.setYearOverYearData(yearOverYearData); // Act String jsonResponse = serializeResponseToJson(response); // Assert assertTrue(jsonResponse.contains("expectedFieldName"), "JSON should contain the YearOverYearData field"); // Additional assertions to verify the content of the YearOverYearData in the JSON } // Additional tests for deserialization, edge cases, etc. private YearOverYearData createTestYearOverYearData() { // Create and return a test YearOverYearData object } private String serializeResponseToJson(Response response) { // Serialize the Response object to a JSON string and return it } } ``` Remember to replace `"expectedFieldName"` with the actual field name used for `YearOverYearData` in the `Response` class. Also, implement the `createTestYearOverYearData` and `serializeResponseToJson` helper methods with the appropriate logic for your specific use case.
Changes on YearOverYearDataTests.java To solve the task for the `YearOverYearDataTests.java` file, we need to write unit tests that validate the functionality of the `YearOverYearDataService`. Here's a concrete practical proposal for implementing the unit tests: 1. **Test YearOverYearData Calculation on January 1st:** - Create a test that sets the system date to January 1st for multiple years. - Mock the data source to return historical data for the previous years. - Call the method in `YearOverYearDataService` that calculates the year over year data. - Assert that the calculated data matches the expected year over year data. 2. **Test YearOverYearData Calculation on Non-January 1st:** - Create a test that sets the system date to a day other than January 1st. - Mock the data source to return historical data. - Call the method in `YearOverYearDataService` that calculates the year over year data. - Assert that the method does not perform the calculation or returns a specific value indicating that the calculation is not due. 3. **Test Current Year Profit Calculation:** - Create a test that sets the system date to various days within the current year. - Mock the data source to return data for the current year up to the current date. - Call the method in `YearOverYearDataService` that calculates the profit for the current year so far. - Assert that the calculated profit matches the expected profit for the year to date. 4. **Test Leap Year Handling:** - Create a test that sets the system date to February 29th on a leap year. - Mock the data source to return data for the leap year and the previous year. - Call the method in `YearOverYearDataService` that calculates the year over year data. - Assert that the leap year is handled correctly and the calculated data is accurate. 5. **Test Data Filling for All Fields:** - Create a test that verifies all fields in the `YearOverYearData` class are filled with appropriate values. - Mock the data source to return comprehensive data that covers all fields. - Call the method in `YearOverYearDataService` that populates the `YearOverYearData` object. - Assert that all fields are filled and no fields are left null or with default values. 6. **Test Edge Cases:** - Create tests for edge cases such as the transition from December 31st to January 1st, handling of time zones, and any other business rules that may affect the calculation. 7. **Setup and Teardown:** - Implement setup and teardown methods if necessary to configure the testing environment before each test and clean up after each test. Here's a skeleton of what the unit test class might look like: ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class YearOverYearDataTests { @Test void testCalculationOnJanuaryFirst() { // Implement test logic } @Test void testCalculationOnNonJanuaryFirst() { // Implement test logic } @Test void testCurrentYearProfitCalculation() { // Implement test logic } @Test void testLeapYearHandling() { // Implement test logic } @Test void testAllFieldsFilled() { // Implement test logic } @Test void testEdgeCases() { // Implement test logic } // Add setup and teardown methods if needed } ``` Remember to use a mocking framework like Mockito to mock dependencies and use JUnit or another testing framework for the assertions. This will ensure that the tests are isolated and do not rely on the actual data source or system time.
maartenn commented 11 months ago

You didn't understand the code, in the AddressController inside the calculateDailyDataList method I loop over a list of prices with their timestamps). I fill those for everyday, but I want you to write a methdo to fill the YearOverYearData in there with the description I gave you. please note that the timestamp in relevantPrices map are not truncated to midnight. Also please implement the logic to calculate the correct values.

@autopilot

codeautopilot[bot] commented 11 months ago

You didn't understand the code, in the AddressController inside the calculateDailyDataList method I loop over a list of prices with their timestamps). I fill those for everyday, but I want you to write a methdo to fill the YearOverYearData in there with the description I gave you. please note that the timestamp in relevantPrices map are not truncated to midnight. Also please implement the logic to calculate the correct values.

@autopilot

I apologize for the confusion earlier. Based on your description, you need a method within the AddressController's calculateDailyDataList method that will fill the YearOverYearData object with the appropriate values. Here's how you can implement this:

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Map;
import java.util.TreeMap;

public class AddressController {

    // Assuming relevantPrices is a TreeMap with keys as timestamps and values as prices
    private TreeMap<LocalDate, Double> relevantPrices;

    // Other fields and methods of AddressController
    // ...

    public void calculateDailyDataList() {
        // Existing logic to loop over prices and timestamps
        // ...

        // Now, let's fill the YearOverYearData
        YearOverYearData yearOverYearData = fillYearOverYearData(relevantPrices);
        // Add the yearOverYearData to your daily data list or response as needed
    }

    private YearOverYearData fillYearOverYearData(TreeMap<LocalDate, Double> prices) {
        YearOverYearData data = new YearOverYearData();
        LocalDate today = LocalDate.now(ZoneId.systemDefault());
        LocalDate startOfCurrentYear = today.withDayOfYear(1);
        LocalDate startOfPreviousYear = startOfCurrentYear.minusYears(1);

        double previousYearProfit = calculateProfit(prices, startOfPreviousYear, startOfCurrentYear);
        double currentYearProfitSoFar = calculateProfit(prices, startOfCurrentYear, today);

        data.setPreviousYearProfit(previousYearProfit);
        data.setCurrentYearProfitSoFar(currentYearProfitSoFar);
        // Set other fields as necessary

        return data;
    }

    private double calculateProfit(TreeMap<LocalDate, Double> prices, LocalDate start, LocalDate end) {
        // Assuming the prices map is sorted by date
        Map.Entry<LocalDate, Double> startPriceEntry = prices.floorEntry(start);
        Map.Entry<LocalDate, Double> endPriceEntry = prices.lowerEntry(end);

        if (startPriceEntry != null && endPriceEntry != null) {
            double startPrice = startPriceEntry.getValue();
            double endPrice = endPriceEntry.getValue();
            return endPrice - startPrice; // This is a simplified profit calculation
        }
        return 0.0;
    }

    // Additional methods as needed
}

In this code snippet:

Please adjust the calculateProfit method according to your actual profit calculation logic, as the above is a simplified example. Also, ensure that the relevantPrices map is sorted by date, as the floorEntry and lowerEntry methods rely on this ordering.

Remember to replace the placeholder methods and fields with actual implementations that fit your application's architecture and data structures.

maartenn commented 11 months ago

some remarks;

codeautopilot[bot] commented 11 months ago

some remarks;

  • In the first loop in calculateDailyDataList you can see the variable day, you should check if that's the first of january;
  • The profit should be calculated on the total portfolio value between the first of each year. (thats why its called year over year) . For the first year you don't have to calculate the percent profit because there the previous year would be 0. But as I mentioned before, please als calculate for the next year. So if latest price entry is on Dec 1. Then calculate the year of year profit from 1 jan until 1 dec.
  • feel free to do some refactors to optimize the code. @autopilot

Understood, let's refactor the calculateDailyDataList method to include the logic for calculating the Year over Year (YoY) profit. We'll check if the current day in the loop is the first of January and calculate the profit based on the total portfolio value between the first of each year. Additionally, we'll calculate the profit for the current year up to the latest available date. Here's the updated method:

import java.time.LocalDate;
import java.util.NavigableMap;
import java.util.TreeMap;

public class AddressController {

    private TreeMap<LocalDate, Double> relevantPrices;

    // Other fields and methods of AddressController
    // ...

    public void calculateDailyDataList() {
        YearOverYearData yearOverYearData = new YearOverYearData();
        LocalDate lastEntryDate = relevantPrices.lastKey();
        LocalDate startOfCurrentYear = LocalDate.of(lastEntryDate.getYear(), 1, 1);
        LocalDate startOfPreviousYear = startOfCurrentYear.minusYears(1);

        for (Map.Entry<LocalDate, Double> entry : relevantPrices.entrySet()) {
            LocalDate day = entry.getKey();
            // Check if it's the first of January
            if (day.equals(startOfCurrentYear) || day.equals(startOfPreviousYear)) {
                // Calculate YoY profit
                double profit = calculateYearOverYearProfit(relevantPrices, startOfPreviousYear, day);
                if (day.equals(startOfCurrentYear)) {
                    yearOverYearData.setPreviousYearProfit(profit);
                } else {
                    yearOverYearData.setCurrentYearProfitSoFar(profit);
                }
            }
        }

        // Add the yearOverYearData to your daily data list or response as needed
    }

    private double calculateYearOverYearProfit(NavigableMap<LocalDate, Double> prices, LocalDate start, LocalDate end) {
        Double startValue = prices.get(start);
        Double endValue = prices.get(end);

        if (startValue != null && endValue != null) {
            return (endValue - startValue) / startValue;
        }
        return 0.0;
    }

    // Additional methods as needed
}

In this updated code:

Please note that this is a simplified example, and you may need to adjust the logic to fit the specifics of your portfolio value calculation and the structure of your data. Additionally, ensure that the relevantPrices map contains the portfolio values on the first of each year for accurate calculations.