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; // 計算した全てのポジションを返す
}
}
コメント