forked from openpredictionmarkets/socialpredict
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Fees to Buying and Selling Bets (openpredictionmarkets#287)
* Drafting out fees deduction functions and capability. * Attempting working version of initialBetFee function. * Draft adding fees. * Update, adding tests. * Adding working test for GetBetsForMarket * Successful fee util test. * Fees added on backend, tests passing. * Updating such that user record submitted. However fee summing not working yet evidently. * Working fees, at least initial fees. * Updating test scenario passing, made more clear. * Update Dockerfile We need to switch to 3.0.14-1~deb12u2 * Removing logging. * Adding fees, including communicating fees on front end. * totalBetCount to userBetCount so as not to misconstrue meaning. * Simplifying function, test passed. * Simplifying naming. * Updating new function name in test * Changing test to got before want convention. * Update marketid variable * Add combined fee structure for more througough test. * Reverting sale amount to 1 share. * Reversing got want --------- Co-authored-by: Osnat Katz Moon <[email protected]>
- Loading branch information
Showing
18 changed files
with
430 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package betutils | ||
|
||
import ( | ||
"log" | ||
"socialpredict/handlers/tradingdata" | ||
"socialpredict/models" | ||
"socialpredict/setup" | ||
|
||
"gorm.io/gorm" | ||
) | ||
|
||
// appConfig holds the loaded application configuration accessible within the package | ||
var appConfig *setup.EconomicConfig | ||
|
||
func init() { | ||
var err error | ||
appConfig, err = setup.LoadEconomicsConfig() | ||
if err != nil { | ||
log.Fatalf("Failed to load configuration: %v", err) | ||
} | ||
} | ||
|
||
// Get initial bet fee, if applicable, for user on market. | ||
// If this is the first bet on this market for the user, apply a fee. | ||
func getUserInitialBetFee(db *gorm.DB, marketID uint, user *models.User) int64 { | ||
// Fetch bets for the market | ||
allBetsOnMarket := tradingdata.GetBetsForMarket(db, marketID) | ||
|
||
// Check if the user has placed any bets on this market | ||
for _, bet := range allBetsOnMarket { | ||
if bet.Username == user.Username { | ||
// User has placed a bet, so no initial fee is applicable | ||
return 0 | ||
} | ||
} | ||
|
||
// This is the user's first bet on this market, apply the initial bet fee | ||
return appConfig.Economics.Betting.BetFees.InitialBetFee | ||
} | ||
|
||
func getTransactionFee(betRequest models.Bet) int64 { | ||
|
||
var transactionFee int64 | ||
|
||
// if amount > 0, buying share, else selling share | ||
if betRequest.Amount > 0 { | ||
transactionFee = appConfig.Economics.Betting.BetFees.BuySharesFee | ||
} else { | ||
transactionFee = appConfig.Economics.Betting.BetFees.SellSharesFee | ||
} | ||
|
||
return transactionFee | ||
} | ||
|
||
func GetBetFees(db *gorm.DB, user *models.User, betRequest models.Bet) int64 { | ||
|
||
MarketID := betRequest.MarketID | ||
|
||
initialBetFee := getUserInitialBetFee(db, MarketID, user) | ||
transactionFee := getTransactionFee(betRequest) | ||
|
||
sumOfBetFees := initialBetFee + transactionFee | ||
|
||
return sumOfBetFees | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
package betutils | ||
|
||
import ( | ||
"socialpredict/models" | ||
"socialpredict/setup" | ||
"testing" | ||
"time" | ||
|
||
"gorm.io/driver/sqlite" | ||
"gorm.io/gorm" | ||
) | ||
|
||
func mockEconomicConfig() *setup.EconomicConfig { | ||
return &setup.EconomicConfig{ | ||
Economics: struct { | ||
MarketCreation struct { | ||
InitialMarketProbability float64 `yaml:"initialMarketProbability"` | ||
InitialMarketSubsidization int64 `yaml:"initialMarketSubsidization"` | ||
InitialMarketYes int64 `yaml:"initialMarketYes"` | ||
InitialMarketNo int64 `yaml:"initialMarketNo"` | ||
} `yaml:"marketcreation"` | ||
MarketIncentives struct { | ||
CreateMarketCost int64 `yaml:"createMarketCost"` | ||
TraderBonus int64 `yaml:"traderBonus"` | ||
} `yaml:"marketincentives"` | ||
User struct { | ||
InitialAccountBalance int64 `yaml:"initialAccountBalance"` | ||
MaximumDebtAllowed int64 `yaml:"maximumDebtAllowed"` | ||
} `yaml:"user"` | ||
Betting struct { | ||
MinimumBet int64 `yaml:"minimumBet"` | ||
BetFees struct { | ||
InitialBetFee int64 `yaml:"initialBetFee"` | ||
BuySharesFee int64 `yaml:"buySharesFee"` | ||
SellSharesFee int64 `yaml:"sellSharesFee"` | ||
} `yaml:"betFees"` | ||
} `yaml:"betting"` | ||
}{ | ||
MarketCreation: struct { | ||
InitialMarketProbability float64 `yaml:"initialMarketProbability"` | ||
InitialMarketSubsidization int64 `yaml:"initialMarketSubsidization"` | ||
InitialMarketYes int64 `yaml:"initialMarketYes"` | ||
InitialMarketNo int64 `yaml:"initialMarketNo"` | ||
}{ | ||
InitialMarketProbability: 0.5, | ||
InitialMarketSubsidization: 10, | ||
InitialMarketYes: 0, | ||
InitialMarketNo: 0, | ||
}, | ||
MarketIncentives: struct { | ||
CreateMarketCost int64 `yaml:"createMarketCost"` | ||
TraderBonus int64 `yaml:"traderBonus"` | ||
}{ | ||
CreateMarketCost: 10, | ||
TraderBonus: 1, | ||
}, | ||
User: struct { | ||
InitialAccountBalance int64 `yaml:"initialAccountBalance"` | ||
MaximumDebtAllowed int64 `yaml:"maximumDebtAllowed"` | ||
}{ | ||
InitialAccountBalance: 1000, | ||
MaximumDebtAllowed: 500, | ||
}, | ||
Betting: struct { | ||
MinimumBet int64 `yaml:"minimumBet"` | ||
BetFees struct { | ||
InitialBetFee int64 `yaml:"initialBetFee"` | ||
BuySharesFee int64 `yaml:"buySharesFee"` | ||
SellSharesFee int64 `yaml:"sellSharesFee"` | ||
} `yaml:"betFees"` | ||
}{ | ||
MinimumBet: 1, | ||
BetFees: struct { | ||
InitialBetFee int64 `yaml:"initialBetFee"` | ||
BuySharesFee int64 `yaml:"buySharesFee"` | ||
SellSharesFee int64 `yaml:"sellSharesFee"` | ||
}{ | ||
InitialBetFee: 1, | ||
BuySharesFee: 1, | ||
SellSharesFee: 0, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func TestGetUserInitialBetFee(t *testing.T) { | ||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) | ||
if err != nil { | ||
t.Fatalf("Failed to connect to the database: %v", err) | ||
} | ||
if err := db.AutoMigrate(&models.Bet{}, &models.User{}); err != nil { | ||
t.Fatalf("Failed to migrate models: %v", err) | ||
} | ||
|
||
appConfig = mockEconomicConfig() | ||
user := &models.User{Username: "testuser", AccountBalance: 1000, ApiKey: "unique_api_key_1"} | ||
if err := db.Create(user).Error; err != nil { | ||
t.Fatalf("Failed to save user to database: %v", err) | ||
} | ||
|
||
marketID := uint(1) | ||
|
||
// getUserInitialBetFee function to include both initial and buy share fees | ||
// For testing purpose, assuming getUserInitialBetFee function does this calculation correctly | ||
initialBetFee := getUserInitialBetFee(db, marketID, user) + appConfig.Economics.Betting.BetFees.BuySharesFee | ||
wantFee := appConfig.Economics.Betting.BetFees.InitialBetFee + appConfig.Economics.Betting.BetFees.BuySharesFee | ||
if initialBetFee != wantFee { | ||
t.Errorf("getUserInitialBetFee(db, %d, %s) = %d, want %d", marketID, user.Username, initialBetFee, wantFee) | ||
} | ||
|
||
// Place a bet for the user on Market 1 | ||
bet := models.Bet{Username: "testuser", MarketID: marketID, Amount: 100, PlacedAt: time.Now()} | ||
if err := db.Create(&bet).Error; err != nil { | ||
t.Fatalf("Failed to save bet to database: %v", err) | ||
} | ||
|
||
// Scenario 2: User places another bet on Market 1 where they already have a bet | ||
initialBetFee = getUserInitialBetFee(db, marketID, user) | ||
wantFee = 0 | ||
if initialBetFee != wantFee { | ||
t.Errorf("getUserInitialBetFee(db, %d, %s) = %d, want %d after placing a bet", marketID, user.Username, initialBetFee, wantFee) | ||
} | ||
|
||
// Update the market ID for a new scenario | ||
marketID = 2 | ||
|
||
// Scenario 3: User places a bet on Market 2 where they have no prior bets | ||
initialBetFee = getUserInitialBetFee(db, marketID, user) | ||
if initialBetFee != appConfig.Economics.Betting.BetFees.InitialBetFee { | ||
t.Errorf("getUserInitialBetFee(db, %d, %s) = %d, want %d", marketID, user.Username, initialBetFee, appConfig.Economics.Betting.BetFees.InitialBetFee) | ||
} | ||
} | ||
|
||
func TestGetTransactionFee(t *testing.T) { | ||
// Mock the appConfig with test data | ||
appConfig = mockEconomicConfig() | ||
|
||
// Test buy scenario | ||
buyBet := models.Bet{Amount: 100} | ||
transactionFee := getTransactionFee(buyBet) | ||
if transactionFee != appConfig.Economics.Betting.BetFees.BuySharesFee { | ||
t.Errorf("Expected buy transaction fee to be %d, got %d", appConfig.Economics.Betting.BetFees.BuySharesFee, transactionFee) | ||
} | ||
|
||
// Test sell scenario | ||
sellBet := models.Bet{Amount: -100} | ||
transactionFee = getTransactionFee(sellBet) | ||
if transactionFee != appConfig.Economics.Betting.BetFees.SellSharesFee { | ||
t.Errorf("Expected sell transaction fee to be %d, got %d", appConfig.Economics.Betting.BetFees.SellSharesFee, transactionFee) | ||
} | ||
} | ||
|
||
func TestGetSumBetFees(t *testing.T) { | ||
// Set up in-memory SQLite database | ||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) | ||
if err != nil { | ||
t.Fatalf("Failed to connect to the database: %v", err) | ||
} | ||
|
||
// Migrate the Bet model | ||
db.AutoMigrate(&models.Bet{}) | ||
|
||
// Mock the appConfig with test data | ||
appConfig = mockEconomicConfig() | ||
|
||
// Create a test user | ||
user := &models.User{Username: "testuser"} | ||
|
||
// Scenario 1: User has no bets, buys shares, gets initial fee | ||
buyBet := models.Bet{MarketID: 1, Amount: 100} | ||
sumOfBetFees := GetBetFees(db, user, buyBet) | ||
expectedSum := appConfig.Economics.Betting.BetFees.InitialBetFee + | ||
appConfig.Economics.Betting.BetFees.BuySharesFee | ||
if sumOfBetFees != expectedSum { | ||
t.Errorf("Expected sum of bet fees to be %d, got %d", expectedSum, sumOfBetFees) | ||
} | ||
|
||
// Create a test bet | ||
bets := []models.Bet{ | ||
{Username: "testuser", MarketID: 1, Amount: 100, PlacedAt: time.Now()}, | ||
} | ||
db.Create(&bets) | ||
|
||
// Scenario 2: User has one bet, buys shares | ||
sumOfBetFees = GetBetFees(db, user, buyBet) | ||
expectedSum = appConfig.Economics.Betting.BetFees.BuySharesFee | ||
if sumOfBetFees != expectedSum { | ||
t.Errorf("Expected sum of bet fees to be %d, got %d", expectedSum, sumOfBetFees) | ||
} | ||
|
||
// Scenario 3: User has one bet, sells shares | ||
sellBet := models.Bet{MarketID: 1, Amount: -1} | ||
sumOfBetFees = GetBetFees(db, user, sellBet) | ||
expectedSum = appConfig.Economics.Betting.BetFees.SellSharesFee | ||
if sumOfBetFees != expectedSum { | ||
t.Errorf("Expected sum of bet fees to be %d, got %d", expectedSum, sumOfBetFees) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package betshandlers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.