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);
}
}
コメント