DR 昇順・実現損益

package simplex.bn25.datemasa335859.trading.a;

import simplex.bn25.datemasa335859.trading.stock.TickerManager;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.TreeMap;

public class PositionCalculator {
    private final TradeDataLoader tradeDataLoader;

    // コンストラクタでTradeDataLoaderのインスタンスを受け取る
    public PositionCalculator(TradeDataLoader tradeDataLoader) {
        this.tradeDataLoader = tradeDataLoader;
    }

    // 保有ポジションを計算し、表示するメソッド
    public void displayPositions() {
        Map<String, Position> positions = new TreeMap<>(tradeDataLoader.calculatePositions()); // Tickerで昇順ソート

        if (positions.isEmpty()) {
            System.out.println("取引データが登録されていません。");
            return;
        }

        // 表のヘッダーを表示
        System.out.println("|===============================================================================================================|");
        System.out.println("| Ticker | Product Name                 | Quantity   | Avg Unit Price   | Realized PnL   | Valuation   | Unrealized PnL |");
        System.out.println("|--------+-------------------------------+------------+------------------+----------------+-------------+----------------|");

        // 各ポジションについて情報を表示
        for (Map.Entry<String, Position> entry : positions.entrySet()) {
            String ticker = entry.getKey();
            Position position = entry.getValue();

            // Ticker Nameの取得
            String tickerName = TickerManager.getTickerToNameMap().getOrDefault(ticker, "Unknown");

            // 時価情報を取得
            BigDecimal marketPrice = MarketPriceLoader.getMarketPrice(ticker);

            // 各項目の計算
            BigDecimal valuation = marketPrice != null
                    ? position.calculateValuation(marketPrice).setScale(2, RoundingMode.HALF_UP)
                    : null;

            BigDecimal unrealizedPnL = marketPrice != null
                    ? position.calculateUnrealizedPnL(marketPrice).setScale(2, RoundingMode.HALF_UP)
                    : null;

            BigDecimal realizedPnL = position.getRealizedPnL().setScale(2, RoundingMode.HALF_UP);

            // 表形式で出力
            System.out.printf("| %-6s | %-29s | %10d | %,16.2f | %,14.2f | %-11s | %-14s |\n",
                    ticker,
                    tickerName.length() > 25 ? tickerName.substring(0, 22) + "..." : tickerName,
                    position.quantity(),
                    position.getAverageUnitPrice(),
                    realizedPnL,
                    valuation != null ? valuation : "N/A",
                    unrealizedPnL != null ? unrealizedPnL : "N/A");
        }

        // 表のフッターを表示
        System.out.println("|===============================================================================================================|");
    }
}
public void processTrade(Trade trade) {
    BigDecimal tradeValue = trade.tradePrice().multiply(BigDecimal.valueOf(trade.quantity()));

    if ("BUY".equalsIgnoreCase(trade.side())) {
        averageUnitPrice = (averageUnitPrice.multiply(BigDecimal.valueOf(quantity)).add(tradeValue))
                .divide(BigDecimal.valueOf(quantity + trade.quantity()), 2, RoundingMode.HALF_UP);
        quantity += trade.quantity();
    } else if ("SELL".equalsIgnoreCase(trade.side())) {
        BigDecimal profit = tradeValue.subtract(averageUnitPrice.multiply(BigDecimal.valueOf(trade.quantity())));
        realizedPnL = realizedPnL.add(profit);
        quantity -= trade.quantity();
    }

    lastTradeTime = trade.tradeDate();
}
package simplex.bn25.datemasa335859.trading.a;

import simplex.bn25.datemasa335859.trading.a.Position;
import simplex.bn25.datemasa335859.trading.trade.Trade;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PositionCalculatorHelper {

    // 取引データから保有ポジションを計算するメソッド
    public static Map<String, Position> calculatePositionsFromTrades(List<Trade> trades) {
        Map<String, Position> positions = new HashMap<>();

        for (Trade trade : trades) {
            String ticker = trade.ticker();
            String tickerName = trade.tickerName();

            // 銘柄コードが初めて登場した場合、新しいPositionオブジェクトを作成してマップに追加
            positions.putIfAbsent(ticker, new Position(ticker, tickerName, 0L));

            // 現在のポジションを取得し、取引を処理
            Position currentPosition = positions.get(ticker);
            currentPosition.processTrade(trade);
        }

        return positions; // 全銘柄のポジションを返す
    }
}
package simplex.bn25.datemasa335859.trading.a;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

public class MarketPriceLoader {
    private static final String MARKET_PRICE_FILE_PATH = "trading-app/src/market_price.csv"; // 時価データのCSVファイルパス
    private static final Map<String, BigDecimal> MARKET_PRICES = new HashMap<>(); // 銘柄コードと時価のマッピング

    // CSVファイルから時価データを読み込むメソッド
    public static void loadMarketPrices() {
        try (BufferedReader br = new BufferedReader(new FileReader(MARKET_PRICE_FILE_PATH))) {
            MARKET_PRICES.clear(); // 既存の時価データをクリアして再読み込み
            String line;

            // ヘッダー行をスキップ
            br.readLine();

            while ((line = br.readLine()) != null) {
                String[] data = line.split(","); // CSVをカンマで分割
                if (data.length == 2) { // 列数が2であることを確認
                    String ticker = data[0].trim(); // 銘柄コードを取得
                    String priceString = data[1].trim(); // 時価を取得

                    if (isValidNumber(priceString)) { // 有効な数値かを確認
                        try {
                            BigDecimal price = new BigDecimal(priceString); // 有効な場合のみBigDecimalを作成
                            MARKET_PRICES.put(ticker, price); // マップに保存
                        } catch (NumberFormatException e) {
                            System.out.println("エラー: 不正な時価データが検出されました。スキップします。行: " + line);
                        }
                    } else {
                        System.out.println("エラー: 不正な時価データが検出されました。スキップします。行: " + line);
                    }
                }
            }
        } catch (IOException e) {
            System.out.println("エラー: 時価データの読み込みに失敗しました。");
        }
    }

    // 銘柄コードに対応する時価を取得するメソッド
    public static BigDecimal getMarketPrice(String ticker) {
        return MARKET_PRICES.get(ticker); // 銘柄コードから時価を取得
    }

    // 入力文字列が有効な数値かどうかを確認するユーティリティメソッド
    private static boolean isValidNumber(String value) {
        try {
            new BigDecimal(value); // BigDecimalの作成を試みる
            return true; // 成功したら有効
        } catch (NumberFormatException e) {
            return false; // 数値として無効
        }
    }
}
package simplex.bn25.datemasa335859.trading.trade;

import simplex.bn25.datemasa335859.trading.a.Position;
import simplex.bn25.datemasa335859.trading.common.DateInputValidator;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

public class TradeInputHandler {

    // 取引を登録するメソッド
    public static Trade registerTrade(Set<String> validTickers, Map<String, String> tickerToNameMap, Map<String, Position> currentPositions) {
        Scanner scanner = new Scanner(System.in);

        // 取引日時を入力
        LocalDateTime tradeDate = DateInputValidator.promptForValidDate(scanner);

        // 銘柄コードと銘柄名を取得
        String ticker = TradeInputPrompter.promptForValidTickerAndName(validTickers, tickerToNameMap, scanner);
        String tickerName = tickerToNameMap.get(ticker);

        // 売買区分を取得
        String side = TradeInputPrompter.promptForSide(scanner);

        // 取引数量を取得
        int quantity = TradeInputPrompter.promptForQuantity(scanner);

        // 取引単価を取得
        BigDecimal tradePrice = TradeInputPrompter.promptForTradePrice(scanner);

        LocalDateTime entryTimestamp = LocalDateTime.now();

        // 保有ポジションのチェック
        Position position = currentPositions.get(ticker);
        if ("SELL".equalsIgnoreCase(side)) {
            if (position == null || position.quantity() < quantity) {
                System.out.println("エラー: 保有株式数を超える売却は許可されていません。");
                return null; // 入力を拒否
            }
        }

        return new Trade(tradeDate, ticker, tickerName, side, quantity, tradePrice, entryTimestamp);
    }
}

コメント

タイトルとURLをコピーしました