New implementation of merchant, based on stock.
This commit is contained in:
@@ -28,7 +28,7 @@ public class CachedMerchantDAO implements MerchantDAO, Cached {
|
||||
@Override
|
||||
public void addTransaction(String blueprintId, float price, Date date, TransactionType transactionType) {
|
||||
_delegate.addTransaction(blueprintId, price, date, transactionType);
|
||||
_blueprintIdLastTransaction.put(blueprintId, new Transaction(date, price, transactionType));
|
||||
_blueprintIdLastTransaction.remove(blueprintId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -81,7 +81,7 @@ public class DbMerchantDAO implements MerchantDAO {
|
||||
try {
|
||||
Connection connection = _dbAccess.getDataSource().getConnection();
|
||||
try {
|
||||
PreparedStatement statement = connection.prepareStatement("select blueprint_id, transaction_price, transaction_date, transaction_type from merchant_data where blueprint_id=?");
|
||||
PreparedStatement statement = connection.prepareStatement("select blueprint_id, transaction_price, transaction_date, transaction_type, buy_count-sell_count from merchant_data where blueprint_id=?");
|
||||
try {
|
||||
statement.setString(1, blueprintId);
|
||||
ResultSet rs = statement.executeQuery();
|
||||
@@ -90,8 +90,9 @@ public class DbMerchantDAO implements MerchantDAO {
|
||||
float price = rs.getFloat(2);
|
||||
Date date = rs.getTimestamp(3);
|
||||
String type = rs.getString(4);
|
||||
int stock = rs.getInt(5);
|
||||
|
||||
return new Transaction(date, price, TransactionType.valueOf(type));
|
||||
return new Transaction(date, price, TransactionType.valueOf(type), stock);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ public interface MerchantDAO {
|
||||
private float _price;
|
||||
private Date _date;
|
||||
private TransactionType _transactionType;
|
||||
private int _stock;
|
||||
|
||||
public Transaction(Date date, float price, TransactionType transactionType) {
|
||||
public Transaction(Date date, float price, TransactionType transactionType, int stock) {
|
||||
_date = date;
|
||||
_price = price;
|
||||
_transactionType = transactionType;
|
||||
_stock = stock;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
@@ -33,5 +35,9 @@ public interface MerchantDAO {
|
||||
public TransactionType getTransactionType() {
|
||||
return _transactionType;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return _stock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
||||
|
||||
public class ParametrizedMerchant implements Merchant {
|
||||
private static final int BOOSTER_PRICE = 1000;
|
||||
private static final long DAY = BOOSTER_PRICE * 60 * 60 * 24;
|
||||
private static final long DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
private Date _merchantSetupDate;
|
||||
|
||||
@@ -82,7 +82,7 @@ public class ParametrizedMerchant implements Merchant {
|
||||
Integer basePrice = getBasePrice(blueprintId);
|
||||
if (basePrice == null)
|
||||
return null;
|
||||
lastTrans = new MerchantDAO.Transaction(_merchantSetupDate, basePrice, MerchantDAO.TransactionType.SELL);
|
||||
lastTrans = new MerchantDAO.Transaction(_merchantSetupDate, basePrice, MerchantDAO.TransactionType.SELL, 0);
|
||||
}
|
||||
if (lastTrans.getDate().getTime() + _priceRevertTimeMs > currentTime.getTime()) {
|
||||
if (lastTrans.getTransactionType() == MerchantDAO.TransactionType.SELL) {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.gempukku.lotro.merchant;
|
||||
|
||||
import com.gempukku.lotro.cards.packs.RarityReader;
|
||||
import com.gempukku.lotro.cards.packs.SetRarity;
|
||||
import com.gempukku.lotro.db.MerchantDAO;
|
||||
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StorageBasedMerchant implements Merchant {
|
||||
private static final int BOOSTER_PRICE = 1000;
|
||||
private static final long DAY = 1000 * 60 * 60 * 24;
|
||||
private static final float _profitMargin = 0.7f;
|
||||
private static final int DOUBLE_AFTER_DAYS = 60;
|
||||
private static final int MAX_STOCK_BUY = 100;
|
||||
private static final int FOIL_PRICE_MULTIPLIER = 2;
|
||||
private static final int MIN_SELL_PRICE = 2;
|
||||
private static final int MIN_BUY_PRICE = 1;
|
||||
private static final double PRICE_SHIFT_AFTER_TRANSACTION = 1.05;
|
||||
|
||||
private LotroCardBlueprintLibrary _library;
|
||||
private MerchantDAO _merchantDao;
|
||||
private Map<Integer, SetRarity> _rarity = new HashMap<Integer, SetRarity>();
|
||||
private Date _merchantSetupDate;
|
||||
|
||||
public StorageBasedMerchant(LotroCardBlueprintLibrary library, MerchantDAO merchantDao, Date merchantSetupDate) {
|
||||
_library = library;
|
||||
_merchantDao = merchantDao;
|
||||
_merchantSetupDate = merchantSetupDate;
|
||||
RarityReader rarityReader = new RarityReader();
|
||||
for (int i = 0; i <= 19; i++)
|
||||
_rarity.put(i, rarityReader.getSetRarity(String.valueOf(i)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cardBought(String blueprintId, Date currentTime, int price) {
|
||||
if (blueprintId.endsWith("*"))
|
||||
price = price / FOIL_PRICE_MULTIPLIER;
|
||||
blueprintId = _library.getBaseBlueprintId(blueprintId);
|
||||
_merchantDao.addTransaction(blueprintId, (price / _profitMargin), currentTime, MerchantDAO.TransactionType.BUY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cardSold(String blueprintId, Date currentTime, int price) {
|
||||
_merchantDao.addTransaction(blueprintId, price, currentTime, MerchantDAO.TransactionType.SELL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCardSellPrice(String blueprintId, Date currentTime) {
|
||||
blueprintId = _library.getBaseBlueprintId(blueprintId);
|
||||
|
||||
MerchantDAO.Transaction lastTransaction = _merchantDao.getLastTransaction(blueprintId);
|
||||
|
||||
if (lastTransaction == null || lastTransaction.getStock() < 0)
|
||||
return null;
|
||||
|
||||
Double normalPrice = getNormalPrice(lastTransaction, blueprintId, currentTime);
|
||||
if (normalPrice == null)
|
||||
return null;
|
||||
|
||||
return Math.max(MIN_SELL_PRICE, (int) Math.ceil(normalPrice));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCardBuyPrice(String blueprintId, Date currentTime) {
|
||||
boolean foil = false;
|
||||
if (blueprintId.endsWith("*"))
|
||||
foil = true;
|
||||
|
||||
blueprintId = _library.getBaseBlueprintId(blueprintId);
|
||||
|
||||
MerchantDAO.Transaction lastTransaction = _merchantDao.getLastTransaction(blueprintId);
|
||||
|
||||
if (lastTransaction != null && lastTransaction.getStock() >= MAX_STOCK_BUY && !foil)
|
||||
return null;
|
||||
|
||||
Double normalPrice = getNormalPrice(lastTransaction, blueprintId, currentTime);
|
||||
if (normalPrice == null)
|
||||
return null;
|
||||
|
||||
int price = Math.max(MIN_BUY_PRICE, (int) Math.floor(_profitMargin * normalPrice));
|
||||
|
||||
if (foil)
|
||||
return FOIL_PRICE_MULTIPLIER * price;
|
||||
return price;
|
||||
}
|
||||
|
||||
public Double getNormalPrice(MerchantDAO.Transaction lastTransaction, String blueprintId, Date currentTime) {
|
||||
float basePrice;
|
||||
float daysSinceLastTransaction;
|
||||
int stock;
|
||||
if (lastTransaction == null) {
|
||||
Integer priceBasedOnRarity = getBasePrice(blueprintId);
|
||||
if (priceBasedOnRarity == null)
|
||||
return null;
|
||||
basePrice = priceBasedOnRarity.floatValue();
|
||||
daysSinceLastTransaction = (currentTime.getTime() - _merchantSetupDate.getTime()) / (DAY * 1f);
|
||||
stock = 0;
|
||||
} else {
|
||||
basePrice = lastTransaction.getPrice();
|
||||
if (lastTransaction.getTransactionType() == MerchantDAO.TransactionType.BUY)
|
||||
basePrice/= PRICE_SHIFT_AFTER_TRANSACTION;
|
||||
else
|
||||
basePrice*= PRICE_SHIFT_AFTER_TRANSACTION;
|
||||
daysSinceLastTransaction = (currentTime.getTime() - lastTransaction.getDate().getTime()) / (DAY * 1f);
|
||||
stock = lastTransaction.getStock();
|
||||
}
|
||||
|
||||
if (stock > 0)
|
||||
return basePrice / (1+ Math.pow(daysSinceLastTransaction/DOUBLE_AFTER_DAYS, 1.5));
|
||||
else
|
||||
return basePrice * (1+ Math.pow(daysSinceLastTransaction/DOUBLE_AFTER_DAYS, 1.5));
|
||||
|
||||
}
|
||||
|
||||
private Integer getBasePrice(String blueprintId) {
|
||||
int underscoreIndex = blueprintId.indexOf("_");
|
||||
if (underscoreIndex == -1)
|
||||
return null;
|
||||
SetRarity rarity = _rarity.get(Integer.parseInt(blueprintId.substring(0, blueprintId.indexOf("_"))));
|
||||
String cardRarity = rarity.getCardRarity(blueprintId);
|
||||
if (cardRarity.equals("X"))
|
||||
return 3 * BOOSTER_PRICE;
|
||||
if (cardRarity.equals("R") || cardRarity.equals("P"))
|
||||
return BOOSTER_PRICE;
|
||||
if (cardRarity.equals("U") || cardRarity.equals("S"))
|
||||
return BOOSTER_PRICE / 3;
|
||||
if (cardRarity.equals("C"))
|
||||
return BOOSTER_PRICE / 7;
|
||||
throw new RuntimeException("Unknown rarity for priced card: " + cardRarity);
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -10,18 +10,23 @@ public class MockMerchantDAO implements MerchantDAO {
|
||||
private Map<String, Float> _prices = new HashMap<String, Float>();
|
||||
private Map<String, Date> _dates = new HashMap<String, Date>();
|
||||
private Map<String, TransactionType> _transactionTypes = new HashMap<String, TransactionType>();
|
||||
private Map<String, Integer> _stock = new HashMap<String, Integer>();
|
||||
|
||||
@Override
|
||||
public void addTransaction(String blueprintId, float price, Date date, TransactionType transactionType) {
|
||||
_prices.put(blueprintId, price);
|
||||
_dates.put(blueprintId, date);
|
||||
_transactionTypes.put(blueprintId, transactionType);
|
||||
Integer previousStock = _stock.get(blueprintId);
|
||||
if (previousStock == null)
|
||||
previousStock = 0;
|
||||
_stock.put(blueprintId, previousStock + ((transactionType == TransactionType.SELL) ? -1 : 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction getLastTransaction(String blueprintId) {
|
||||
if (_dates.containsKey(blueprintId))
|
||||
return new Transaction(_dates.get(blueprintId), _prices.get(blueprintId), _transactionTypes.get(blueprintId));
|
||||
return new Transaction(_dates.get(blueprintId), _prices.get(blueprintId), _transactionTypes.get(blueprintId), _stock.get(blueprintId));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.gempukku.lotro.merchant;
|
||||
|
||||
import com.gempukku.lotro.db.MerchantDAO;
|
||||
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class StorageBasedMerchantTest {
|
||||
private StorageBasedMerchant _merchant;
|
||||
private static final long DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Date setupDate = new Date(0);
|
||||
MerchantDAO merchantDao = new MockMerchantDAO();
|
||||
|
||||
_merchant = new StorageBasedMerchant(new LotroCardBlueprintLibrary(), merchantDao, setupDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardTradingWithNoStock() {
|
||||
assertNull(_merchant.getCardSellPrice("1_1", new Date(0)));
|
||||
assertNotNull(_merchant.getCardBuyPrice("1_1", new Date(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardTradingWithSomeStock() {
|
||||
_merchant.cardBought("1_1", new Date(0), 1000);
|
||||
assertNotNull(_merchant.getCardSellPrice("1_1", new Date(0)));
|
||||
assertNotNull(_merchant.getCardBuyPrice("1_1", new Date(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardTradingWithFullStock() {
|
||||
for (int i=0; i<100; i++)
|
||||
_merchant.cardBought("1_1", new Date(0), 1000);
|
||||
assertNotNull(_merchant.getCardSellPrice("1_1", new Date(0)));
|
||||
assertNull(_merchant.getCardBuyPrice("1_1", new Date(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardPriceLowersAfterMerchantBuys() {
|
||||
_merchant.cardBought("1_1", new Date(0), 700);
|
||||
int cardInitialPrice = _merchant.getCardSellPrice("1_1", new Date(0));
|
||||
assertEqualsMoreOrLess((int) (1000 / 1.05f), cardInitialPrice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardPriceRisesAfterMerchantSells() {
|
||||
// Setup some stock
|
||||
_merchant.cardBought("1_1", new Date(0), 700);
|
||||
_merchant.cardBought("1_1", new Date(0), 700);
|
||||
|
||||
_merchant.cardSold("1_1", new Date(0), 1000);
|
||||
int cardInitialPrice = _merchant.getCardSellPrice("1_1", new Date(0));
|
||||
assertEqualsMoreOrLess((int) (1000*1.05f), cardInitialPrice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardPriceRisesOverTimeWhenNoStock() {
|
||||
int initialPrice = _merchant.getCardBuyPrice("1_1", new Date(0));
|
||||
assertTrue(initialPrice < _merchant.getCardBuyPrice("1_1", new Date(DAY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cardPriceLowersOverTimeWhenInStock() {
|
||||
_merchant.cardBought("1_1", new Date(0), 700);
|
||||
int initialPrice = _merchant.getCardBuyPrice("1_1", new Date(0));
|
||||
assertTrue(initialPrice > _merchant.getCardBuyPrice("1_1", new Date(DAY)));
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void plotPricesAfterTransactions() {
|
||||
Date setupDate = new Date(-1000 * 60 * 60 * 24 * 50L);
|
||||
Date firstTrans = new Date(0);
|
||||
|
||||
MerchantDAO merchantDao = new MockMerchantDAO();
|
||||
|
||||
StorageBasedMerchant merchant = new StorageBasedMerchant(new LotroCardBlueprintLibrary(), merchantDao, setupDate);
|
||||
|
||||
merchant.cardSold("1_1", firstTrans, 1000);
|
||||
merchant.cardBought("1_2", firstTrans, 700);
|
||||
|
||||
long hour = 1000 * 60 * 60;
|
||||
|
||||
System.out.println("-2,1000,700,1000,700");
|
||||
for (long time = 0; time < hour * 24 * 35L; time += hour * 2) {
|
||||
System.out.println(time / hour + "," + merchant.getCardSellPrice("1_1", new Date(time)) + "," + merchant.getCardBuyPrice("1_1", new Date(time))
|
||||
+ "," + merchant.getCardSellPrice("1_2", new Date(time)) + "," + merchant.getCardBuyPrice("1_2", new Date(time)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// @Test
|
||||
public void plotPriceEasingFromSetup() {
|
||||
Date setupDate = new Date(0);
|
||||
|
||||
MerchantDAO merchantDao = new MockMerchantDAO();
|
||||
|
||||
StorageBasedMerchant merchant = new StorageBasedMerchant(new LotroCardBlueprintLibrary(), merchantDao, setupDate);
|
||||
|
||||
long hour = 1000 * 60 * 60;
|
||||
|
||||
for (long time = 0; time < hour * 24 * 35L; time += hour * 2)
|
||||
System.out.println(time / hour + "," + merchant.getCardSellPrice("1_1", new Date(time)) + "," + merchant.getCardBuyPrice("1_1", new Date(time)));
|
||||
}
|
||||
|
||||
private void assertEqualsMoreOrLess(int expected, int given) {
|
||||
assertTrue("Expected " + expected + " more or less, but received " + given, given <= expected + 1 && given >= expected - 1);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user