Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/market creation interface #327

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ae1cfa
Add modelstesting package to provide test doubles for db operations
ajlacey Sep 9, 2024
d36152f
Update feeutils tests to use new fake db for db operations
ajlacey Sep 9, 2024
c68bd98
Update getbets tests to use new db fake
ajlacey Sep 9, 2024
e17320b
Update dependency versions to fix migration error with postgres drive…
ajlacey Sep 10, 2024
7d19629
Update broken dependency and add FakeDB initialization testing
ajlacey Sep 13, 2024
b756c4a
Update test to fail immediately if the db doesn't start
ajlacey Sep 13, 2024
86be184
Update add user handler to use DI (#307)
ajlacey Sep 9, 2024
93e9a5a
Update Create Market Handler to use EconConfigLoader (#310)
ajlacey Sep 9, 2024
8abb0e4
Create CONTRIBUTING.md (#312)
pwdel Sep 9, 2024
497bb92
Update issue templates (#313)
pwdel Sep 9, 2024
96e25ac
Update gitignore to include backend test coverage html output (#316)
ajlacey Sep 13, 2024
e276a86
Add MarketCreationLoader interface and make EconomicConfig and Market…
ajlacey Sep 13, 2024
a6cdd33
Add argument to CalculateMarketProbabilitiesWPAM signature
ajlacey Sep 13, 2024
0fa7e43
fix spacing
ajlacey Sep 13, 2024
1e08813
Merge branch 'main' into feature/market_creation_interface
ajlacey Sep 14, 2024
b8e5b7f
Update positionsmath to use a market creation loader, and add an empt…
ajlacey Sep 14, 2024
4385b67
Use market creation loader in listbetshandler
ajlacey Sep 14, 2024
73951af
Use market creation loader in sellpositionhandler
ajlacey Sep 14, 2024
68f3348
Use market creation loader in marketdetailshandler
ajlacey Sep 14, 2024
1bed242
Use market creation loader in userpositionmarkethandler
ajlacey Sep 14, 2024
cde9a0c
Use market creation loader in positionshandler
ajlacey Sep 14, 2024
cdde9ec
Use market creation loader in marketprojectedprobability
ajlacey Sep 14, 2024
94f8d71
Use market creation loader in listmarkets
ajlacey Sep 14, 2024
11d4ba4
Use market creation loader in publicuserportfolio
ajlacey Sep 14, 2024
d224788
Use market creation loader in server handlerfuncs
ajlacey Sep 14, 2024
e441fc2
Fix codeql warning and remove impossible condition
ajlacey Sep 14, 2024
c7c9f27
Adjust error handling
ajlacey Sep 14, 2024
b0c6315
Merge branch 'main' into feature/market_creation_interface
ajlacey Sep 16, 2024
375e7f1
Merge branch 'main' into feature/market_creation_interface
ajlacey Sep 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 40 additions & 36 deletions backend/handlers/bets/listbetshandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"socialpredict/handlers/math/probabilities/wpam"
"socialpredict/handlers/tradingdata"
"socialpredict/models"
"socialpredict/setup"
"socialpredict/util"
"sort"
"strconv"
Expand All @@ -24,51 +25,54 @@ type BetDisplayInfo struct {
PlacedAt time.Time `json:"placedAt"`
}

func MarketBetsDisplayHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
marketIdStr := vars["marketId"]
func MarketBetsDisplayHandler(mcl setup.MarketCreationLoader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
marketIdStr := vars["marketId"]

// Convert marketId to uint
parsedUint64, err := strconv.ParseUint(marketIdStr, 10, 32)
if err != nil {
// handle error
}
// Convert marketId to uint
parsedUint64, err := strconv.ParseUint(marketIdStr, 10, 32)
if err != nil {
http.Error(w, "Invalid market ID", http.StatusBadRequest)
return
}

// Convert uint64 to uint safely.
marketIDUint := uint(parsedUint64)

// Database connection
db := util.GetDB()

// Fetch bets for the market
bets := tradingdata.GetBetsForMarket(db, marketIDUint)

// feed in the time created
// note we are not using GetPublicResponseMarketByID because of circular import
var market models.Market
result := db.Where("ID = ?", marketIdStr).First(&market)
if result.Error != nil {
// Handle error, for example:
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// Market not found
} else {
// Other error fetching market
// Convert uint64 to uint safely.
marketIDUint := uint(parsedUint64)

// Database connection
db := util.GetDB()

// Fetch bets for the market
bets := tradingdata.GetBetsForMarket(db, marketIDUint)

// feed in the time created
// note we are not using GetPublicResponseMarketByID because of circular import
var market models.Market
result := db.Where("ID = ?", marketIdStr).First(&market)
if result.Error != nil {
// Handle error, for example:
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// Market not found
} else {
// Other error fetching market
}
return // Make sure to return or appropriately handle the error
}
return // Make sure to return or appropriately handle the error
}

// Process bets and calculate market probability at the time of each bet
betsDisplayInfo := processBetsForDisplay(market.CreatedAt, bets, db)
// Process bets and calculate market probability at the time of each bet
betsDisplayInfo := processBetsForDisplay(mcl, market.CreatedAt, bets, db)

// Respond with the bets display information
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(betsDisplayInfo)
// Respond with the bets display information
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(betsDisplayInfo)
}
}

func processBetsForDisplay(marketCreatedAtTime time.Time, bets []models.Bet, db *gorm.DB) []BetDisplayInfo {
func processBetsForDisplay(mcl setup.MarketCreationLoader, marketCreatedAtTime time.Time, bets []models.Bet, db *gorm.DB) []BetDisplayInfo {

// Calculate probabilities using the fetched bets
probabilityChanges := wpam.CalculateMarketProbabilitiesWPAM(marketCreatedAtTime, bets)
probabilityChanges := wpam.CalculateMarketProbabilitiesWPAM(mcl, marketCreatedAtTime, bets)

var betsDisplayInfo []BetDisplayInfo

Expand Down
133 changes: 68 additions & 65 deletions backend/handlers/bets/sellpositionhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,87 +7,90 @@ import (
"socialpredict/handlers/positions"
"socialpredict/middleware"
"socialpredict/models"
"socialpredict/setup"
"socialpredict/util"
"strconv"
"time"
)

func SellPositionHandler(w http.ResponseWriter, r *http.Request) {
// Only allow POST requests
if r.Method != http.MethodPost {
http.Error(w, "Method not supported", http.StatusMethodNotAllowed)
return
}
func SellPositionHandler(mcl setup.MarketCreationLoader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Only allow POST requests
if r.Method != http.MethodPost {
http.Error(w, "Method not supported", http.StatusMethodNotAllowed)
return
}

db := util.GetDB() // Get the database connection
user, httperr := middleware.ValidateUserAndEnforcePasswordChangeGetUser(r, db)
if httperr != nil {
http.Error(w, httperr.Error(), httperr.StatusCode)
return
}
db := util.GetDB() // Get the database connection
user, httperr := middleware.ValidateUserAndEnforcePasswordChangeGetUser(r, db)
if httperr != nil {
http.Error(w, httperr.Error(), httperr.StatusCode)
return
}

var redeemRequest models.Bet
err := json.NewDecoder(r.Body).Decode(&redeemRequest)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var redeemRequest models.Bet
err := json.NewDecoder(r.Body).Decode(&redeemRequest)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// get the marketID in string format
marketIDStr := strconv.FormatUint(uint64(redeemRequest.MarketID), 10)
// get the marketID in string format
marketIDStr := strconv.FormatUint(uint64(redeemRequest.MarketID), 10)

// Validate the request similar to PlaceBetHandler
betutils.CheckMarketStatus(db, redeemRequest.MarketID)
// Validate the request similar to PlaceBetHandler
betutils.CheckMarketStatus(db, redeemRequest.MarketID)

// Calculate the net aggregate positions for the user
userNetPosition, err := positions.CalculateMarketPositionForUser_WPAM_DBPM(db, marketIDStr, user.Username)
if userNetPosition.NoSharesOwned == 0 && userNetPosition.YesSharesOwned == 0 {
http.Error(w, "No position found for the given market", http.StatusBadRequest)
return
}
// Calculate the net aggregate positions for the user
userNetPosition, _ := positions.CalculateMarketPositionForUser_WPAM_DBPM(mcl, db, marketIDStr, user.Username)
if userNetPosition.NoSharesOwned == 0 && userNetPosition.YesSharesOwned == 0 {
http.Error(w, "No position found for the given market", http.StatusBadRequest)
return
}

// Check if the user is trying to redeem more than they own
if (redeemRequest.Outcome == "YES" && redeemRequest.Amount > userNetPosition.YesSharesOwned) ||
(redeemRequest.Outcome == "NO" && redeemRequest.Amount > userNetPosition.NoSharesOwned) {
http.Error(w, "Redeem amount exceeds available position", http.StatusBadRequest)
return
}
// Check if the user is trying to redeem more than they own
if (redeemRequest.Outcome == "YES" && redeemRequest.Amount > userNetPosition.YesSharesOwned) ||
(redeemRequest.Outcome == "NO" && redeemRequest.Amount > userNetPosition.NoSharesOwned) {
http.Error(w, "Redeem amount exceeds available position", http.StatusBadRequest)
return
}

// Proceed with redemption logic
// For simplicity, we're just creating a negative bet to represent the sale
redeemRequest.Amount = -redeemRequest.Amount // Negate the amount to indicate sale
// Proceed with redemption logic
// For simplicity, we're just creating a negative bet to represent the sale
redeemRequest.Amount = -redeemRequest.Amount // Negate the amount to indicate sale

// Create a new Bet object
bet := models.Bet{
Username: user.Username,
MarketID: redeemRequest.MarketID,
Amount: redeemRequest.Amount,
PlacedAt: time.Now(), // Set the current time as the placement time
Outcome: redeemRequest.Outcome,
}
// Create a new Bet object
bet := models.Bet{
Username: user.Username,
MarketID: redeemRequest.MarketID,
Amount: redeemRequest.Amount,
PlacedAt: time.Now(), // Set the current time as the placement time
Outcome: redeemRequest.Outcome,
}

// Validate the final bet before putting into database
if err := betutils.ValidateSale(db, &bet); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Validate the final bet before putting into database
if err := betutils.ValidateSale(db, &bet); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Deduct the bet and switching sides fee amount from the user's balance
user.AccountBalance -= redeemRequest.Amount
// Deduct the bet and switching sides fee amount from the user's balance
user.AccountBalance -= redeemRequest.Amount

// Update the user's balance in the database
if err := db.Save(&user).Error; err != nil {
http.Error(w, "Error updating user balance: "+err.Error(), http.StatusInternalServerError)
return
}
// Update the user's balance in the database
if err := db.Save(&user).Error; err != nil {
http.Error(w, "Error updating user balance: "+err.Error(), http.StatusInternalServerError)
return
}

result := db.Create(&bet)
if result.Error != nil {
http.Error(w, result.Error.Error(), http.StatusInternalServerError)
return
}
result := db.Create(&bet)
if result.Error != nil {
http.Error(w, result.Error.Error(), http.StatusInternalServerError)
return
}

// Return a success response
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(redeemRequest)
// Return a success response
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(redeemRequest)
}
}
85 changes: 44 additions & 41 deletions backend/handlers/markets/listmarkets.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"socialpredict/handlers/tradingdata"
usersHandlers "socialpredict/handlers/users"
"socialpredict/models"
"socialpredict/setup"
"socialpredict/util"
"strconv"

Expand All @@ -30,55 +31,57 @@ type MarketOverview struct {
}

// ListMarketsHandler handles the HTTP request for listing markets.
func ListMarketsHandler(w http.ResponseWriter, r *http.Request) {
log.Println("ListMarketsHandler: Request received")
if r.Method != http.MethodGet {
http.Error(w, "Method is not supported.", http.StatusNotFound)
return
}

db := util.GetDB()
markets, err := ListMarkets(db)
if err != nil {
http.Error(w, "Error fetching markets", http.StatusInternalServerError)
return
}

var marketOverviews []MarketOverview
for _, market := range markets {
bets := tradingdata.GetBetsForMarket(db, uint(market.ID))
probabilityChanges := wpam.CalculateMarketProbabilitiesWPAM(market.CreatedAt, bets)
numUsers := models.GetNumMarketUsers(bets)
marketVolume := marketmath.GetMarketVolume(bets)
lastProbability := probabilityChanges[len(probabilityChanges)-1].Probability

creatorInfo := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername)
func ListMarketsHandler(mcl setup.MarketCreationLoader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("ListMarketsHandler: Request received")
if r.Method != http.MethodGet {
http.Error(w, "Method is not supported.", http.StatusNotFound)
return
}

// return the PublicResponse type with information about the market
marketIDStr := strconv.FormatUint(uint64(market.ID), 10)
publicResponseMarket, err := marketpublicresponse.GetPublicResponseMarketByID(db, marketIDStr)
db := util.GetDB()
markets, err := ListMarkets(db)
if err != nil {
http.Error(w, "Invalid market ID", http.StatusBadRequest)
http.Error(w, "Error fetching markets", http.StatusInternalServerError)
return
}

marketOverview := MarketOverview{
Market: publicResponseMarket,
Creator: creatorInfo,
LastProbability: lastProbability,
NumUsers: numUsers,
TotalVolume: marketVolume,
var marketOverviews []MarketOverview
for _, market := range markets {
bets := tradingdata.GetBetsForMarket(db, uint(market.ID))
probabilityChanges := wpam.CalculateMarketProbabilitiesWPAM(mcl, market.CreatedAt, bets)
numUsers := models.GetNumMarketUsers(bets)
marketVolume := marketmath.GetMarketVolume(bets)
lastProbability := probabilityChanges[len(probabilityChanges)-1].Probability

creatorInfo := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername)

// return the PublicResponse type with information about the market
marketIDStr := strconv.FormatUint(uint64(market.ID), 10)
publicResponseMarket, err := marketpublicresponse.GetPublicResponseMarketByID(db, marketIDStr)
if err != nil {
http.Error(w, "Invalid market ID", http.StatusBadRequest)
return
}

marketOverview := MarketOverview{
Market: publicResponseMarket,
Creator: creatorInfo,
LastProbability: lastProbability,
NumUsers: numUsers,
TotalVolume: marketVolume,
}
marketOverviews = append(marketOverviews, marketOverview)
}
marketOverviews = append(marketOverviews, marketOverview)
}

response := ListMarketsResponse{
Markets: marketOverviews,
}
response := ListMarketsResponse{
Markets: marketOverviews,
}

w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}

Expand Down
Loading
Loading