Implement min_amount max_amount model props and validation

This commit is contained in:
Reckless_Satoshi 2022-03-21 16:27:36 -07:00
parent 2998c265d6
commit bf80986005
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 134 additions and 53 deletions

View File

@ -87,24 +87,56 @@ class Logics:
return True, None, None
def validate_order_size(order):
"""Validates if order is withing limits in satoshis at t0"""
if order.t0_satoshis > MAX_TRADE:
return False, {
"bad_request":
"Your order is too big. It is worth " +
"{:,}".format(order.t0_satoshis) +
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
" Sats"
}
if order.t0_satoshis < MIN_TRADE:
return False, {
"bad_request":
"Your order is too small. It is worth " +
"{:,}".format(order.t0_satoshis) +
" Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
" Sats"
}
@classmethod
def validate_order_size(cls, order):
"""Validates if order size in Sats is within limits at t0"""
if not order.has_range:
if order.t0_satoshis > MAX_TRADE:
return False, {
"bad_request":
"Your order is too big. It is worth " +
"{:,}".format(order.t0_satoshis) +
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
" Sats"
}
if order.t0_satoshis < MIN_TRADE:
return False, {
"bad_request":
"Your order is too small. It is worth " +
"{:,}".format(order.t0_satoshis) +
" Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
" Sats"
}
elif order.has_range:
min_sats = cls.calc_sats(order.min_amount, order.currency.exchange_rate, order.premium)
max_sats = cls.calc_sats(order.max_amount, order.currency.exchange_rate, order.premium)
if min_sats > max_sats/1.5:
return False, {
"bad_request":
"min_sats*1.5 has to be smaller than max_sats"
}
elif max_sats > MAX_TRADE:
return False, {
"bad_request":
"Your order upper range value (max_amount) is too big. It is worth " +
"{:,}".format(max_sats) +
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
" Sats"
}
elif min_sats < MIN_TRADE:
return False, {
"bad_request":
"Your order lower range value (min_mount) is too small. It is worth " +
"{:,}".format(min_sats) +
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
" Sats"
}
elif min_sats < max_sats/5:
return False, {
"bad_request":
f"Your order amount range is too large. Max amount can only be 5 times bigger than min amount"
}
return True, None
def user_activity_status(last_seen):
@ -144,15 +176,19 @@ class Logics:
return (is_maker and order.type == Order.Types.SELL) or (
is_taker and order.type == Order.Types.BUY)
def satoshis_now(order):
def calc_sats(amount, exchange_rate, premium):
exchange_rate = float(exchange_rate)
premium_rate = exchange_rate * (1 + float(premium) / 100)
return (float(amount) /premium_rate) * 100 * 1000 * 1000
@classmethod
def satoshis_now(cls, order):
"""checks trade amount in sats"""
if order.is_explicit:
satoshis_now = order.satoshis
else:
exchange_rate = float(order.currency.exchange_rate)
premium_rate = exchange_rate * (1 + float(order.premium) / 100)
satoshis_now = (float(order.amount) /
premium_rate) * 100 * 1000 * 1000
amount = order.amount if order.amount != None else order.max_amount
satoshis_now = cls.calc_sats(amount, order.currency.exchange_rate, order.premium)
return int(satoshis_now)

View File

@ -192,14 +192,15 @@ class Order(models.Model):
currency = models.ForeignKey(Currency,
null=True,
on_delete=models.SET_NULL)
amount = models.DecimalField(max_digits=16,
decimal_places=8,
validators=[MinValueValidator(0.00000001)])
amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
has_range = models.BooleanField(default=False, null=False, blank=False)
min_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
max_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
payment_method = models.CharField(max_length=35,
null=False,
default="not specified",
blank=True)
bondless_taker = models.BooleanField(default=False, null=False, blank=False)
# order pricing method. A explicit amount of sats, or a relative premium above/below market.
is_explicit = models.BooleanField(default=False, null=False)
# marked to market

View File

@ -14,10 +14,14 @@ class ListOrderSerializer(serializers.ModelSerializer):
"type",
"currency",
"amount",
"has_range",
"min_amount",
"max_amount",
"payment_method",
"is_explicit",
"premium",
"satoshis",
"bondless_taker"
"maker",
"taker",
)
@ -31,12 +35,16 @@ class MakeOrderSerializer(serializers.ModelSerializer):
"type",
"currency",
"amount",
"has_range",
"min_amount",
"max_amount",
"payment_method",
"is_explicit",
"premium",
"satoshis",
"public_duration",
"bond_size",
"bondless_taker",
)
class UpdateOrderSerializer(serializers.Serializer):

View File

@ -66,33 +66,64 @@ class MakerView(CreateAPIView):
},
status.HTTP_400_BAD_REQUEST,
)
# Only allow users who are not already engaged in an order
valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
if not valid:
return Response(context, status.HTTP_409_CONFLICT)
type = serializer.data.get("type")
currency = serializer.data.get("currency")
amount = serializer.data.get("amount")
has_range = serializer.data.get("has_range")
min_amount = serializer.data.get("min_amount")
max_amount = serializer.data.get("max_amount")
payment_method = serializer.data.get("payment_method")
premium = serializer.data.get("premium")
satoshis = serializer.data.get("satoshis")
is_explicit = serializer.data.get("is_explicit")
public_duration = serializer.data.get("public_duration")
bond_size = serializer.data.get("bond_size")
bondless_taker = serializer.data.get("bondless_taker")
# Optional params
if public_duration == None:
public_duration = PUBLIC_DURATION
if bond_size == None:
bond_size = BOND_SIZE
if public_duration == None: public_duration = PUBLIC_DURATION
if bond_size == None: bond_size = BOND_SIZE
if bondless_taker == None: bondless_taker = False
if has_range == None: has_range = False
valid, context, _ = Logics.validate_already_maker_or_taker(
request.user)
if not valid:
return Response(context, status.HTTP_409_CONFLICT)
# An order can either have an amount or a range (min_amount and max_amount)
if has_range:
amount = None
else:
min_amount = None
max_amount = None
# Either amount or min_max has to be specified.
if has_range and (min_amount == None or max_amount == None):
return Response(
{
"bad_request":
"You must specify min_amount and max_amount for a range order"
},
status.HTTP_400_BAD_REQUEST,
)
elif not has_range and amount == None:
return Response(
{
"bad_request":
"You must specify an order amount"
},
status.HTTP_400_BAD_REQUEST,
)
# Creates a new order
order = Order(
type=type,
currency=Currency.objects.get(id=currency),
amount=amount,
has_range=has_range,
min_amount=min_amount,
max_amount=max_amount,
payment_method=payment_method,
premium=premium,
satoshis=satoshis,
@ -102,9 +133,9 @@ class MakerView(CreateAPIView):
maker=request.user,
public_duration=public_duration,
bond_size=bond_size,
bondless_taker=bondless_taker,
)
# TODO move to Order class method when new instance is created!
order.last_satoshis = order.t0_satoshis = Logics.satoshis_now(order)
valid, context = Logics.validate_order_size(order)

View File

@ -78,9 +78,9 @@ export default class MakerPage extends Component {
.then((data) => this.setState({
limits:data,
loadingLimits:false,
minAmount: Number(data[this.state.currency]['max_amount']*0.25),
maxAmount: Number(data[this.state.currency]['max_amount']*0.75),
amount: ""})
minAmount: parseFloat(Number(data[this.state.currency]['max_amount']*0.25).toPrecision(2)),
maxAmount: parseFloat(Number(data[this.state.currency]['max_amount']*0.75).toPrecision(2))
})
& console.log(this.state.limits));
}
@ -131,8 +131,8 @@ export default class MakerPage extends Component {
}
this.setState({
minAmount: lowerValue,
maxAmount: upperValue,
minAmount: parseFloat(Number(lowerValue).toPrecision(2)),
maxAmount: parseFloat(Number(upperValue).toPrecision(2)),
});
}
@ -192,12 +192,16 @@ export default class MakerPage extends Component {
type: this.state.type,
currency: this.state.currency,
amount: this.state.amount,
has_range: this.state.enableAmountRange,
min_amount: this.state.minAmount,
max_amount: this.state.maxAmount,
payment_method: this.state.payment_method,
is_explicit: this.state.is_explicit,
premium: this.state.is_explicit ? null: this.state.premium,
satoshis: this.state.is_explicit ? this.state.satoshis: null,
public_duration: this.state.publicDuration,
bond_size: this.state.bondSize,
bondless_taker: this.state.allowBondless,
}),
};
fetch("/api/make/",requestOptions)
@ -253,6 +257,7 @@ export default class MakerPage extends Component {
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title="Amount of fiat to exchange for bitcoin">
<TextField
disabled = {this.state.enableAmountRange}
variant = {this.state.enableAmountRange ? 'filled' : 'outlined'}
error={this.state.amount <= 0 & this.state.amount != "" }
helperText={this.state.amount <= 0 & this.state.amount != "" ? 'Invalid' : null}
label="Amount"
@ -396,7 +401,7 @@ export default class MakerPage extends Component {
}else{
var max_amount = this.state.limits[this.state.currency]['max_amount']
}
return parseFloat(Number(max_amount).toPrecision(3))
return parseFloat(Number(max_amount).toPrecision(2))
}
getMinAmount = () => {
@ -405,7 +410,7 @@ export default class MakerPage extends Component {
}else{
var min_amount = this.state.limits[this.state.currency]['min_amount']
}
return parseFloat(Number(min_amount).toPrecision(3))
return parseFloat(Number(min_amount).toPrecision(2))
}
@ -417,14 +422,13 @@ export default class MakerPage extends Component {
<Grid item xs={12} align="center" spacing={1}>
<FormControl align="center">
<Tooltip enterDelay="800" enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game, increase for higher safety assurance"}>
<FormHelperText>
<Tooltip enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game (increase for higher safety assurance)"}>
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
Fidelity Bond Size <LockIcon sx={{height:20,width:20}}/>
</div>
</Tooltip>
</FormHelperText>
</Tooltip>
<Slider
sx={{width:220, align:"center"}}
aria-label="Bond Size (%)"
@ -462,7 +466,7 @@ export default class MakerPage extends Component {
<Grid item xs={12} align="center" spacing={1}>
<FormControl align="center">
<FormHelperText>
<Tooltip enterTouchDelay="0" title={"Let the taker chose an amount within a range"}>
<Tooltip enterTouchDelay="0" placement="top" align="center"title={"Let the taker chose an amount within the range"}>
<div align="center" style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked}) & (e.target.checked ? this.getLimits() : null)}/>
Amount Range
@ -475,12 +479,12 @@ export default class MakerPage extends Component {
<div style={{ display: this.state.loadingLimits == false ? '':'none'}}>
<Slider
disableSwap={true}
sx={{width:200, align:"center"}}
sx={{width:190, align:"center"}}
disabled={!this.state.enableAmountRange || this.state.loadingLimits}
value={[this.state.minAmount, this.state.maxAmount]}
step={this.getMaxAmount()/1000}
//valueLabelDisplay="auto"
valueLabelFormat={(x) => (Number(x).toPrecision(3)+" "+this.state.currencyCode)}
step={(this.getMaxAmount()-this.getMinAmount())/100}
valueLabelDisplay="auto"
valueLabelFormat={(x) => (parseFloat(Number(x).toPrecision(2))+" "+this.state.currencyCode)}
marks={this.state.limits == null?
null
:
@ -571,7 +575,8 @@ export default class MakerPage extends Component {
: ""}
<Typography component="subtitle2" variant="subtitle2">
<div align='center'>
Create a BTC {this.state.type==0 ? "buy":"sell"} order for {pn(this.state.amount)} {this.state.currencyCode}
Create a BTC {this.state.type==0 ? "buy":"sell"} order for {this.state.enableAmountRange & this.state.minAmount != null?
" "+this.state.minAmount+"-"+this.state.maxAmount : pn(this.state.amount)} {this.state.currencyCode}
{this.state.is_explicit ? " of " + pn(this.state.satoshis) + " Satoshis" :
(this.state.premium == 0 ? " at market price" :
(this.state.premium > 0 ? " at a " + this.state.premium + "% premium":" at a " + -this.state.premium + "% discount")

File diff suppressed because one or more lines are too long