First Commit

This commit is contained in:
Theo Tappe
2026-05-13 14:19:41 +02:00
commit 1eaa7d0896
89 changed files with 6206 additions and 0 deletions
@@ -0,0 +1,108 @@
package win.tap_tap.papertrader
import kotlinx.serialization.Serializable
import kotlin.math.roundToLong
fun Double.toPercentString(relative: Boolean = false): String {
var shifted = (this * 10000).toInt() / 100.0
if (relative) shifted -= 100F
val string = shifted.toString()
val parts = string.split(".")
val decimals = parts.getOrNull(1)?.padEnd(2, '0')?.take(2) ?: "00"
return "${parts[0]}.$decimals%"
}
fun getRelativePercentage(currentValue: Cash, startValue: Cash): String {
if (startValue.asCents() == 0L) {
return 0.0.toPercentString()
}
val percentages = (currentValue.asCents().toDouble() / startValue.asCents()
.toDouble()
).toPercentString(relative = true)
return percentages
}
@Serializable
data class Cash(
val euro: Long,
val cent: Long,
) {
fun asCents(): Long {
return euro * 100 + cent
}
override fun toString(): String {
return "$euro,$cent"
}
operator fun times(other: Double): Cash {
return fromCents((asCents() * other).roundToLong())
}
operator fun times(other: Cash): Cash {
return fromCents(asCents() * other.asCents())
}
operator fun plus(other: Cash): Cash {
return fromCents(asCents() + other.asCents())
}
operator fun minus(other: Cash): Cash {
return fromCents(asCents() - other.asCents())
}
companion object {
fun fromCents(cents: Long): Cash {
return Cash(cents / 100, cents % 100)
}
fun fromFloat(cash: Float): Cash {
return Cash(cash.toLong(), (cash % 1).toLong() * 100L)
}
fun zero(): Cash {
return Cash(0, 0)
}
}
}
@Serializable
data class Asset(
val id: Long, val symbol: String, val name: String,
)
fun List<Position>.currentValue(): Cash {
return Cash.fromCents(this.sumOf { it.currentPrice.asCents() * it.amount }.roundToLong())
}
fun List<Position>.buyValue(): Cash {
return Cash.fromCents(this.sumOf { it.buyPrice.asCents() * it.amount }.roundToLong())
}
@Serializable
data class Position(
val id: Long,
val asset: Asset,
val buyPrice: Cash,
val currentPrice: Cash,
val amount: Double,
) {
fun currentMarketValue(): Cash {
return Cash.fromCents((currentPrice.asCents() * amount).roundToLong())
}
fun buyMarketValue(): Cash {
return Cash.fromCents((buyPrice.asCents() * amount).roundToLong())
}
}
@Serializable
data class Challenge(
val id: Long, val name: String, val cash: Cash
)
@Serializable
data class Participant(val user: User, val cash: Cash)
@Serializable
data class User(val id: Long, val name: String)
@@ -0,0 +1,77 @@
package win.tap_tap.papertrader
import kotlinx.serialization.Serializable
// Single Requests
@Serializable
data class RequestAddParticipant(val participantId: Long, val challengeId: Long)
@Serializable
data class RequestRegisterUser(val username: String, val password: String, val passwordConfirm: String)
@Serializable
data class RequestAssetBuy(val assetId: Long, val cash: Cash, val challengeId: Long)
@Serializable
data class RequestAssetSell(val positionId: Long)
@Serializable
data class RequestLeaveChallenge(val challengeId: Long)
@Serializable
data class RequestChallengeKick(val challengeId: Long, val userId: Long)
// Single Responses
@Serializable
data class ResponseGetChallenges(val challenges: List<Challenge>)
@Serializable
data class ResponseGetStocks(val stocks: List<Asset>)
// Requests + Responses
@Serializable
data class RequestLoginUser(val username: String, val password: String)
@Serializable
data class ResponseLogin(val accessToken: String, val refreshToken: String, val expiresIn: Long)
@Serializable
data class RequestLogoutUser(val accessToken: String, val refreshToken: String)
@Serializable
data class RequestCreateChallenge(val name: String, val euros: Long, val cents: Long)
@Serializable
data class RequestTokenRefresh(val refreshToken: String)
@Serializable
data class ResponseTokenRefresh(val accessToken: String, val expiresIn: Long)
@Serializable
data class RequestGetParticipants(val challengeId: Long)
@Serializable
data class ResponseGetParticipants(val userIsCreator: Boolean, val user: Participant, val participants: List<User>)
@Serializable
data class RequestGetMissingParticipants(val challengeId: Long)
@Serializable
data class ResponseGetMissingParticipants(val users: List<User>)
@Serializable
data class RequestChallengeData(val challengeId: Long)
@Serializable
data class ResponseChallengeData(
val userIsCreator: Boolean,
val user: Participant,
val positions: Map<Participant, List<Position>>
)
@@ -0,0 +1,28 @@
package win.tap_tap.papertrader
//const val API_ENDPOINT = "http://127.0.0.1:8080"
const val API_ENDPOINT = "https://papertrader.tap-tap.win"
enum class Endpoints(val path: String) {
LOGIN("/api/login"),
REGISTER("/api/register"),
LOGOUT("/api/logout"),
GET_CHALLENGES("/api/get-challenges"),
REFRESH_TOKEN("/api/refresh-token"),
// GET_PARTICIPANTS("/api/get-participants"),
CREATE_CHALLENGE("/api/create-challenge"),
GET_MISSING_PARTICIPANTS("/api/get-missing-participants"),
ADD_PARTICIPANT("/api/add-participant"),
GET_STOCKS("/api/get-stocks"),
ASSET_BUY("/api/asset/buy"),
ASSET_SELL("/api/asset/sell"),
CHALLENGE_LEAVE("/api/challenge/leave"),
CHALLENGE_KICK("/api/challenge/kick"),
GET_CHALLENGE_DATA("/api/challenge/data"),
USER_DELETE("/api/user/delete");
fun full(): String = API_ENDPOINT + this.path
fun endpoint(): String = this.path
}