DRステップ7最終日前準備

package simplex.bn25.datemasa335859.trading.a; 

import simplex.bn25.datemasa335859.trading.common.DisplayFormatter; // 数値フォーマットや文字列処理のためのユーティリティクラス
import simplex.bn25.datemasa335859.trading.stock.TickerManager; // TickerとTicker Nameのマッピングを管理するクラス

import java.math.BigDecimal; // 精度の高い数値計算用
import java.util.Map; // データをキーと値のペアで格納するためのインターフェース
import java.util.TreeMap; // キーを昇順にソートして格納するためのマップ

public class PositionCalculator { // ポジション情報を計算して表示するクラス
    private final TradeDataLoader tradeDataLoader; // 取引データをロードするための依存オブジェクト

    public PositionCalculator(TradeDataLoader tradeDataLoader) { // コンストラクタでTradeDataLoaderを受け取る
        this.tradeDataLoader = 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(); // 現在のエントリーのTickerコードを取得
            Position position = entry.getValue(); // 現在のエントリーのポジション情報を取得

            String tickerName = TickerManager.getTickerToNameMap().getOrDefault(ticker, "Unknown"); // Tickerに対応するTicker Nameを取得(なければ"Unknown")

            BigDecimal marketPrice = MarketPriceLoader.getMarketPrice(ticker); // Tickerに対応する時価情報を取得

            BigDecimal valuation = marketPrice != null ? position.calculateValuation(marketPrice) : null; // 時価情報がある場合に評価額を計算
            BigDecimal unrealizedPnL = marketPrice != null ? position.calculateUnrealizedPnL(marketPrice) : null; // 時価情報がある場合に評価損益を計算

            String realizedPnL = position.quantity() == 0 ? "N/A" : DisplayFormatter.formatDecimal(position.getRealizedPnL()); // Quantityが0の場合はRealized PnLを"N/A"に設定、それ以外はフォーマット

            // 表形式でポジション情報を表示
            System.out.printf("| %-6s | %-29s | %10s | %16s | %14s | %-11s | %-14s |\n",
                    ticker, // Tickerコードを表示
                    DisplayFormatter.truncateString(tickerName, 25), // Ticker Nameを25文字に切り詰めて表示
                    DisplayFormatter.formatNumber(position.quantity()), // Quantityをカンマ区切りで表示
                    DisplayFormatter.formatDecimal(position.getAverageUnitPrice()), // 平均取得単価を小数点第2位まで表示
                    realizedPnL, // Realized PnLを表示
                    valuation != null ? DisplayFormatter.formatDecimal(valuation) : "N/A", // Valuationを表示(時価情報がない場合はN/A)
                    unrealizedPnL != null ? DisplayFormatter.formatDecimal(unrealizedPnL) : "N/A"); // Unrealized PnLを表示(時価情報がない場合はN/A)
        }

        // 表のフッターを表示
        System.out.println("|===============================================================================================================|");
    }
}
package simplex.bn25.datemasa335859.trading.trade;

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

import java.util.Scanner;

public class TradeInputPrompter {
    public static int promptForQuantity(Scanner scanner, Position position, String side) {
        int quantity;
        while (true) {
            System.out.print("数量を入力してください(100株単位):");
            try {
                quantity = Integer.parseInt(scanner.nextLine().trim());
                if (quantity <= 0 || quantity % 100 != 0) {
                    System.out.println("エラー: 数量は100株単位の正の数で入力してください。");
                    continue;
                }

                // 売却の場合、保有株式数を超えないかチェック
                if ("SELL".equalsIgnoreCase(side) && position != null && position.quantity() < quantity) {
                    System.out.println("エラー: 保有株式数を超える売却は許可されていません。");
                    continue;
                }

                return quantity;
            } catch (NumberFormatException e) {
                System.out.println("エラー: 無効な数値です。再度入力してください。");
            }
        }
    }
}
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);

        // 保有ポジションを取得
        Position position = currentPositions.get(ticker);

        // 取引日時のバリデーション
        if (position != null && (tradeDate.isEqual(position.getLastTradeTime()) || tradeDate.isBefore(position.getLastTradeTime()))) {
            System.out.println("エラー: 取引日時が既存の取引日時と同じか過去です。");
            return null; // 入力を拒否
        }

        // 取引数量を取得(数量の時点でエラーを出力)
        int quantity = TradeInputPrompter.promptForQuantity(scanner, position, side);

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

        LocalDateTime entryTimestamp = LocalDateTime.now();

        return new Trade(tradeDate, ticker, tickerName, side, quantity, tradePrice, entryTimestamp);
    }
}
package simplex.bn25.datemasa335859.trading.common;

import java.math.BigDecimal;
import java.text.NumberFormat;

public class DisplayFormatter {

    // 文字列を最大長に切り詰め、必要に応じて"..."を追加する
    public static String truncateString(String str, int maxLength) {
        if (str.length() > maxLength) {
            return str.substring(0, maxLength - 3) + "...";
        }
        return str;
    }

    // 数字をカンマ区切りでフォーマットする
    public static String formatNumber(long number) {
        return NumberFormat.getNumberInstance().format(number);
    }

    // 小数点以下第2位まで四捨五入してフォーマットする
    public static String formatDecimal(BigDecimal number) {
        if (number == null) {
            return "N/A";
        }
        return NumberFormat.getNumberInstance().format(number.setScale(2, BigDecimal.ROUND_HALF_UP));
    }
}

下記コードは内容把握用

package simplex.bn25.datemasa335859.trading.a; // パッケージ名を指定

import java.io.BufferedReader; // CSVファイルの読み込みに使用
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<>(); // 銘柄コードと時価のマッピング

    public static void loadMarketPrices() { // CSVファイルから時価データを読み込むメソッド
        try (BufferedReader br = new BufferedReader(new FileReader(MARKET_PRICE_FILE_PATH))) { // ファイルを読み込む
            MARKET_PRICES.clear(); // マップをクリアしてデータをリロード
            br.readLine(); // ヘッダー行をスキップ
            String line;
            while ((line = br.readLine()) != null) { // ファイルの各行を処理
                String[] data = line.split(","); // カンマ区切りで分割
                if (data.length == 2) { // データ列が2つあるか確認
                    String ticker = data[0].trim(); // 銘柄コードを取得
                    String priceString = data[1].trim(); // 時価を取得
                    if (isValidNumber(priceString)) { // 時価が有効な数値か確認
                        MARKET_PRICES.put(ticker, new BigDecimal(priceString)); // 銘柄コードと時価をマップに登録
                    }
                }
            }
        } 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; // 成功した場合はtrueを返す
        } catch (NumberFormatException e) {
            return false; // 数値でない場合はfalseを返す
        }
    }
}
package simplex.bn25.datemasa335859.trading.a; // パッケージ名を指定

import simplex.bn25.datemasa335859.trading.trade.Trade; // Tradeクラスをインポート
import java.math.BigDecimal; // 精度の高い数値計算用
import java.math.RoundingMode; // 数値の丸め処理
import java.time.LocalDateTime; // 日時管理用

public class Position { // 銘柄ごとのポジションを管理するクラス
    private long quantity; // 保有数量
    private BigDecimal averageUnitPrice = BigDecimal.ZERO; // 平均取得単価
    private BigDecimal realizedPnL = BigDecimal.ZERO; // 実現損益
    private LocalDateTime lastTradeTime; // 最後の取引日時

    public Position(String ticker, String tickerName, long quantity) { // コンストラクタ
        this.quantity = quantity; // 保有数量を初期化
    }

    public void processTrade(Trade trade) { // 取引を処理してポジションを更新するメソッド
        BigDecimal tradeValue = trade.tradePrice().multiply(BigDecimal.valueOf(trade.quantity())); // 取引の総額を計算
        if ("BUY".equals(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".equals(trade.side())) { // 売り取引の場合
            realizedPnL = realizedPnL.add(tradeValue.subtract(averageUnitPrice.multiply(BigDecimal.valueOf(trade.quantity())))); // 実現損益を更新
            quantity -= trade.quantity(); // 保有数量を減少
        }
        lastTradeTime = trade.tradeDate(); // 最後の取引日時を更新
    }

    public BigDecimal calculateValuation(BigDecimal marketPrice) { // 評価額を計算
        return marketPrice != null ? marketPrice.multiply(BigDecimal.valueOf(quantity)) : null; // 時価が存在する場合のみ計算
    }

    public BigDecimal calculateUnrealizedPnL(BigDecimal marketPrice) { // 評価損益を計算
        BigDecimal valuation = calculateValuation(marketPrice); // 評価額を取得
        return valuation != null ? valuation.subtract(averageUnitPrice.multiply(BigDecimal.valueOf(quantity))) : null; // 評価損益を計算
    }

    // 各フィールドのゲッター
    public long quantity() { return quantity; }
    public BigDecimal getRealizedPnL() { return realizedPnL; }
    public BigDecimal getAverageUnitPrice() { return averageUnitPrice; }
    public LocalDateTime getLastTradeTime() { return lastTradeTime; }
}
package simplex.bn25.datemasa335859.trading.a; // パッケージ名を指定

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(); // 現在の取引データからTickerコードを取得
            String tickerName = trade.tickerName(); // 現在の取引データからTicker名を取得

            positions.putIfAbsent(ticker, new Position(ticker, tickerName, 0L)); // 初めてのTickerの場合、新しいPositionを作成してマップに追加

            Position currentPosition = positions.get(ticker); // 現在のTickerに対応するPositionを取得
            currentPosition.processTrade(trade); // 現在の取引をポジションに反映
        }

        return positions; // 計算した全てのポジションを返す
    }
}

コメント

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